Чому rc.local не виконує всі мої команди, і що я можу зробити з цим?


35

У мене є такий rc.localсценарій:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sh /home/incero/startup_script.sh
/bin/chmod +x /script.sh
sh /script.sh

exit 0

Перший рядок startup_script.shфактично завантажує script.shфайл, на який /script.shйдеться у третьому рядку.

На жаль, виявляється, що це не робить сценарій виконуваним або запускає сценарій. Запуск rc.localфайлу вручну після запуску працює бездоганно. Чи не можна chmod запускати при запуску чи щось таке?

Відповіді:


70

Ви можете пропустити весь шлях до швидкого виправлення, але це не обов'язково найкращий варіант. Тож рекомендую спочатку все це прочитати.

rc.local не терпить помилок.

rc.localне забезпечує способу інтелектуального відновлення після помилок. Якщо якась команда не вдається, вона припиняється. Перший рядок #!/bin/sh -eвикликає його виконання в оболонці, на яку посилається -eпрапор. -eПрапор, що робить скрипт (в даному випадку rc.local) перестають працювати в перший раз , команда не буде виконана в ньому.

Ви хочете rc.localповодитись так. Якщо команда не вдається, ви не хочете, щоб вона продовжувалася з будь-якими іншими командами запуску, можливо, покладаючись на її успіх.

Тож якщо будь-яка команда не вдається, наступні команди не запускаються. Проблема тут полягає в тому, що /script.shвін не запустився (не те, що він не вдався, див. Нижче), тому, швидше за все, якась команда до того, як вона не вдалася. Але який?

Це було /bin/chmod +x /script.sh?

Ні.

chmodпрацює в будь-який час. За умови встановлення файлової системи, яка містить /bin, ви можете запустити /bin/chmod. І /binмонтується перед rc.localпробіжками.

Коли запускається як корінь, /bin/chmodрідко виходить з ладу. Він вийде з ладу, якщо файл, на якому працює, є лише для читання, і може вийти з ладу, якщо файлова система, на якій він не підтримує, не має дозволів. Ніякого ймовірного тут немає.

До речі, sh -eце єдина причина, яка насправді була б проблемою, якщо її chmodне вдалося. Коли ви запускаєте файл сценарію, явно викликаючи його інтерпретатора, не має значення, чи файл позначений виконуваним. Тільки якщо це сказало /script.shб, значення виконавчого файлу файлу має значення. Оскільки він говорить sh /script.sh, він не робить (якщо, звичайно, не /script.sh дзвонить сам під час його запуску, що може призвести до того, що він не може бути виконаним, але навряд чи він називає себе).

То що не вдалося?

sh /home/incero/startup_script.shне вдалося. Майже напевно.

Ми знаємо, що він запустився, тому що завантажив /script.sh.

(Інакше було б важливо переконатися, що він запустився, якщо якимось чином /binне був у PATH - rc.localне обов'язково такий же, PATHяк у вас, коли ви увійшли в систему. Якщо ви /binне були в rc.localшляху, це потрібно shзапустити як /bin/sh. Так як це було запущено, /binє в PATH, що означає, що ви можете запускати інші команди, які знаходяться в них /bin, не повністю кваліфікуючи їх імена. Наприклад, ви можете запускатись просто, chmodа не /bin/chmod. Однак, у відповідності зі своїм стилем в rc.local, я використовував повністю кваліфіковані імена для всіх команд, за винятком випадків sh, коли я пропоную вам запустити їх.)

Ми можемо бути впевнені, що /bin/chmod +x /script.shніколи не бігали (або ви побачите, що це /script.shбуло страчено). І ми знаємо, що також sh /script.shне працювали.

Але це завантажили /script.sh. Це вдалося! Як воно могло провалитися?

Два значення успіху

Є дві різні речі, які людина може означати, коли він / він каже, що команда успішна:

  1. Це робило те, що ти хотів, щоб це зробили.
  2. Він повідомив, що це досяг успіху.

І так це за невдачу. Коли людина каже, що команда не вдалася, це може означати:

  1. Це не зробило те, що ви хотіли.
  2. Він повідомив, що це не вдалося.

Сценарій, який запускається sh -e, як-от rc.local, зупинить виконання першого разу, коли команда повідомить, що не вдалося . Це не має значення, що насправді зробила команда.

Якщо ви не збираєтесь startup_script.shповідомляти про помилку, коли вона робить те, що ви хочете, це помилка startup_script.sh.

  • Деякі помилки заважають сценарію виконувати те, що ви хочете. Вони впливають на те, що програмісти називають його побічними ефектами .
  • А деякі помилки заважають скрипту правильно повідомляти про те, чи вдалося це. Вони впливають на те, що програмісти називають його поверненим значенням (що в даному випадку є статусом виходу ).

Швидше за startup_script.shвсе, він зробив усе, що повинен, окрім випадків, коли це не вдалося.

Як повідомляється про успіх чи невдачу

Сценарій - це список нульових або більше команд. Кожна команда має статус виходу. Якщо припустити, що фактично не працює сценарій (наприклад, якщо інтерпретатор не міг прочитати наступний рядок сценарію під час його запуску), статус виходу сценарію такий:

  • 0 (успіх), якщо скрипт був порожнім (тобто не мав команд).
  • N, якщо скрипт закінчився в результаті команди , де є якийсь вихідний код.exit NN
  • Код виходу останньої команди, яка запускалася в сценарії, в іншому випадку.

Коли виконується виконуваний файл, він повідомляє про власний код виходу - вони не тільки для сценаріїв. (А технічно, вихідні коди зі скриптів - це вихідні коди, повернені оболонками, які ними керують.)

Наприклад, якщо програма C закінчується exit(0);або return 0;за її main()функцією, код 0надається операційній системі, яка надає їй процес виклику (який, наприклад, може бути оболонкою, з якої запускалася програма).

0означає, що програма вдалася. Кожне інше число означає, що воно не вдалося. (Таким чином, різні цифри іноді можуть посилатися на різні причини невдачі програми.)

Команди означають збій

Іноді ви запускаєте програму з наміром, що вона вийде з ладу. У цих ситуаціях ви можете вважати його провал як успіх, навіть якщо програма не повідомляє про помилку. Наприклад, ви можете використовувати rmфайл, який, як ви підозрюєте, вже не існує, просто переконайтесь, що його видалено.

Щось подібне, мабуть, відбувається в startup_script.sh, перед тим, як воно перестане працювати. Останньою команди запуску в сценарії, ймовірно , повідомлення про це (навіть якщо його «провал» може бути абсолютно нормально і навіть необхідно), що робить звіт про помилку сценарію.

Тести означають збій

Один особливий вид команди - це тест , під яким я маю на увазі команду, яку виконують за її зворотним значенням, а не за її побічними ефектами. Тобто тест - це команда, яка виконується так, щоб її статус виходу можна було вивчити (і діяти).

Наприклад, припустимо, я забуваю, якщо 4 дорівнює 5. На щастя, я знаю сценарій оболонки:

if [ 4 -eq 5 ]; then
    echo "Yeah, they're totally the same."
fi

Тут тест [ -eq 5 ]не вдається, оскільки виходить 4 4 5 зрештою. Це не означає, що тест працював неправильно; це було. Завданням було перевірити, чи 4 = 5, а потім повідомити про успіх, якщо так, а провал - якщо ні.

Розумієте, в сценаріях оболонок успіх також може означати істину , а невдача також може означати хибність .

Незважаючи на те, що echoтвердження ніколи не виконується, ifблок в цілому повертає успіх.

Однак, мабуть, я написав це коротше:

[ 4 -eq 5 ] && echo "Yeah, they're totally the same."

Це звичайна стенограма. &&є булевим і оператором. &&Вираз, який складається з &&з заявами з обох боків, повертає помилкове (відмова) , якщо обидві сторони не повертають істинний (успіх). Так само, як звичайний і .

Якщо хтось запитає вас, "Дерек ходив у торговий центр і думав про метелика?" і ти знаєш, що Дерек не ходив до торгового центру, не потрібно займатись з'ясуванням, чи думав він про метелика.

Аналогічно, якщо команда зліва від &&fails (false), весь &&вираз негайно виходить з ладу (false). Заява праворуч &&ніколи не виконується.

Ось, [ 4 -eq 5 ]біжить. Він "провалюється" (повертаючи помилкове). Отже весь &&вираз провалюється. echo "Yeah, they're totally the same."ніколи не бігає. Все поводилось так, як і належить, але ця команда повідомляє про помилку (навіть незважаючи на те, що інакше еквівалентний ifумовно вище звіт про успіх).

Якби це останнє твердження в сценарії (і сценарій дійшов до нього, а не закінчувався в якийсь момент перед ним), весь сценарій повідомив би про помилку .

Окрім цього, є ще багато тестів. Наприклад, є тести з ||("або"). Однак, наведений вище приклад повинен бути достатнім, щоб пояснити, що таке тести, і дати можливість ефективно використовувати документацію, щоб визначити, чи є тест / команда певним тестом.

shvs. sh -e, Переглянуто

Оскільки в #!рядку (дивись також це питання ) у верхній частині /etc/rc.localмає sh -e, операційна система запускає сценарій , як якщо б він був викликаний за допомогою команди:

sh -e /etc/rc.local

На відміну від цього , ваші інші сценарії, такі як startup_script.sh, працювати без з -eпрапором:

sh /home/incero/startup_script.sh

Отже, вони продовжують працювати навіть тоді, коли команда в них повідомляє про помилку.

Це нормально і добре. rc.localслід викликати sh -eі більшість інших скриптів - включаючи більшість скриптів, якими керує - rc.localне повинен.

Просто не забудьте запам'ятати різницю:

  • Сценарії запускаються з sh -eпомилкою звітування про вихід із першого разу, коли команда, яку вони містять, виходить з ладу звітування.

    Схоже, що сценарій являв собою єдину довгу команду, що складається з усіх команд у скрипті, об'єднаних з &&операторами.

  • Сценарії, запущені з sh(без -e), продовжують виконуватись до тих пір, поки вони не прийдуть до команди, яка завершує (виходить із них), або до самого кінця сценарію. Успіх або невдача кожної команди по суті не має значення (якщо тільки наступна команда не перевіряє її). Сценарій виходить зі статусом виходу останнього запуску команди.

Допомагаючи своєму сценарію зрозуміти, що це не така невдача

Як можна утримати сценарій від того, що він не вважав його невдалим, коли цього не зробив?

Ви дивитеся на те, що відбувається безпосередньо перед тим, як це буде запущено.

  1. Якщо команда не вдалася, коли вона мала досягти успіху, з’ясуйте, чому, і виправте проблему.

  2. Якщо команда не вдалася, і це сталося правильно, тоді запобігайте поширенню цього стану відмови.

    • Один із способів утримати статус відмови від розповсюдження - це запустити іншу команду, яка є успішною. /bin/trueне має побічних ефектів і повідомляє про успіх (так само, як /bin/falseнічого, а також не дає результатів).

    • Інша полягає в тому, щоб переконатися, що сценарій закінчується exit 0.

      Це не обов'язково те саме, що exit 0бути в кінці сценарію. Наприклад, може бути if-блок, де сценарій закривається всередині.

Найкраще знати, що змушує ваш сценарій повідомляти про помилку, перш ніж змусити його повідомити про успіх. Якщо це дійсно не вдається певним чином (у сенсі не робити те, що ви хочете), ви не хочете, щоб він повідомляв про успіх.

Швидке виправлення

Якщо ви не можете досягти startup_script.shуспіху у звіті про вихід, ви можете змінити команду, rc.localяка її виконує, так що команда повідомляє про успіх, хоча startup_script.shй не зробила.

На даний момент у вас є:

sh /home/incero/startup_script.sh

Ця команда має однакові побічні ефекти (тобто побічний ефект запуску startup_script.sh), але завжди повідомляє про успіх:

sh /home/incero/startup_script.sh || /bin/true

Пам'ятайте, краще знати, чому startup_script.shповідомляє про помилку, і виправити це.

Як працює швидке виправлення

Це насправді приклад ||тесту, або тесту.

Припустимо, ви запитаєте, чи я вийняв сміття чи почистив сову. Якщо я вийняв сміття, я правдиво можу сказати "так", навіть якщо я не пам'ятаю, чи я чистив сову.

Команда зліва від ||запуску. Якщо це вдасться (правда), правий бік не повинен бігати. Тож якщо startup_script.shзвіт про успіх, trueкоманда ніколи не виконується.

Однак якщо startup_script.shповідомлення про збій [я не вийняв сміття] , то результат /bin/true [якщо я почистив сову] має значення.

/bin/trueзавжди повертає успіх (або справжній , як ми його іноді називаємо). Отже, вся команда вдається, і наступна команда в rc.localможе запуститися.

Додаткова примітка про успіх / невдачу, правда / хибність, нуль / нуль.

Сміливо ігноруйте це. Ви можете прочитати це, якщо ви програмуєте більш ніж однією мовою (тобто, не лише сценаріями оболонок).

Це велика плутанина для скриптових оболонок, які займаються мовами програмування, такими як C, і для програмістів на C, які займаються скриптом оболонок, виявити:

  • У сценарії оболонок:

    1. Повернене значення 0означає успіх та / або істину .
    2. Повернене значення чогось іншого, ніж 0означає відмову та / або хибність .
  • У програмуванні на С:

    1. Повернене значення 0означає хибне ..
    2. Повернене значення чогось іншого, ніж 0означає правду .
    3. Не існує простого правила, що означає успіх, а що означає невдачу. Іноді 0означає успіх, інший раз це означає невдачу, а інший раз означає, що сума двох доданих вами чисел дорівнює нулю. Повернені значення в загальному програмуванні використовуються для передачі різноманітної інформації різного роду.
    4. Чисельний статус виходу програми повинен вказувати на успіх чи невдачу відповідно до правил сценаріїв оболонок. Тобто, хоч це 0означає, що у вашій програмі C неправдиво, ви все одно змушуєте програму повертатися 0як її вихідний код, якщо ви хочете, щоб вона повідомила про її успіх (що оболонка потім інтерпретує як істинне ).

3
вау чудова відповідь! Там багато інформації, яку я не знав. Повернення 0 в кінці першого сценарію призводить до виконання решти сценарію (добре, я можу сказати, що chmod + x запустився). На жаль, схоже, що / usr / bin / wget у моєму сценарії не вдається отримати веб-сторінку, розміщену в script.sh. Я здогадуюсь, що мережа ще не готова до моменту виконання цього сценарію rc.local. Куди я можу поставити цю команду, щоб вона запускалася після запуску мережі та автоматично при запуску?
Програміст

Я зараз "зламав" це, запустивши sudo visudo, додавши наступний рядок: ім'я ALL = (ВСЕ) NOPASSWD: ВСІ, що виконують програму "програми запуску" (що для цього команда CLI?) Та додавши до цього startup_script, в той час як startupscript тепер запускає script.sh з командою sudo, заздалегідь запустивши як root (пароль більше не потребує). Без сумніву, це супер небезпечно і страшно, але це лише для користувальницького дискового дистрибутива pxe-boot.
Програміст

@ Stu2000 Якщо припустити incero(я вважаю, що це ім'я користувача) є адміністратором , тож дозволено запускати будь-яку команду як root(введенням власного пароля користувача), що не дуже небезпечно і страшно . Якщо ви хочете інших рішень, рекомендую розмістити нове запитання з цього приводу . Одна з проблем полягала в тому, що команди в rc.localкоманді не запускаються (і ви також перевіряли, що буде, якщо ви змусите їх продовжувати виконання). Тепер у вас є інша проблема: ви хочете підключити машину до мережі перед rc.localзапуском або запланувати запуск startup_script.shнаступного.
Елія Каган

1
З того часу я повернувся до цього питання, відредагував rc.local і виявив, що wget не працює. Додавання /usr/bin/sudo service networking restartдо сценарію запуску перед командою wget призвело до того, що wget працюватиме.
Програміст

3
@EliahKagan чудова вичерпна відповідь. Я навряд чи натрапив на кращу документаціюrc.local
souravc

1

Ви можете (у використаних для мене варіантах Unix) змінити поведінку за замовчуванням, змінивши:

#!/bin/sh -e

До:

#!/bin/sh

На початку сценарію. Прапор "-e" вказує sh вийти при першій помилці. Довга назва цього прапора - "errexit", тому початковий рядок еквівалентний:

#!/bin/sh --errexit

так, зняття -eробіт з розп’янської джессі.
Окі Ері Рінальді

-eТам через. Я б цього не робив, якби ти був. Але я не твої батьки.
Wyatt8740

-4

змінити перший рядок з: #!/bin/sh -e на !/bin/sh -e


3
Синтаксис з #!- правильний. (Принаймні, #!частина правильна. А решта зазвичай добре працює, для rc.local.) Дивіться цю статтю та це питання . У будь-якому разі, ласкаво просимо до Ask Ubuntu!
Елія Каган
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.