Почему 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 не запускаться при запуске или что-то в этом роде?

Programster
источник

Ответы:

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запуском.

При запуске от имени root /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«s путь, это потребовалось 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-5. Это не значит, что тест не был выполнен правильно; это сделал. Его работа состояла в том, чтобы проверить, если 4 = 5, затем сообщить об успехе, если так, и об ошибке, если нет.

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

Хотя echoоператор никогда не выполняется, ifблок в целом возвращает успех.

Тем не менее, предположил, что я написал это короче:

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

Это обычная стенография. &&является логическим и оператором. &&Выражение, которое состоит из &&с заявлениями по обе стороны, возвращает ложное (отказ) , если обе стороны не возвращают истинный (успех). Так же , как нормальный и .

Если кто-то спросит вас: «Дерек пошел в торговый центр и подумал о бабочке?» и вы знаете, что Дерек не ходил в торговый центр, вам не нужно беспокоиться, если он подумает о бабочке.

Точно так же, если команда слева от &&сбоев (ложь), все &&выражение немедленно терпит неудачу (ложь). Утверждение на правой стороне &&никогда не запускается.

Здесь [ 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сообщает об ошибке, и исправить это.

Как работает Quick Fix

На самом деле это пример ||теста, или теста.

Предположим, вы спросили, вынул ли я мусор или почистил сову. Если я вытащил мусор, я могу честно сказать «да», даже если я не помню, чистил ли я сову или нет.

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

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

/bin/trueвсегда возвращает успех (или истина , как мы иногда называем это). Следовательно, вся команда выполнена успешно, и rc.localможет быть выполнена следующая команда .

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

Не стесняйтесь игнорировать это. Возможно, вы захотите прочитать это, если вы программируете на более чем одном языке (то есть, не только в сценариях оболочки).

Для создателей сценариев оболочки, использующих такие языки программирования, как C, и для программистов на C, занимающихся сценариями оболочки, возникает большая путаница:

  • В сценариях оболочки:

    1. Возвращаемое значение 0означает успех и / или истину .
    2. Возвращаемое значение чего-то другого, кроме 0означает сбой и / или false .
  • В программировании на С:

    1. Возвращаемое значение 0означает ложь .
    2. Ответное значение чего - то другого , чем 0средства истинных .
    3. Не существует простого правила, что означает успех, а что - неудача. Иногда 0означает успех, иногда означает неудачу, иногда это означает, что сумма двух чисел, которые вы только что добавили, равна нулю. Возвращаемые значения в общем программировании используются, чтобы сигнализировать широкий спектр различных видов информации.
    4. Числовой статус завершения программы должен указывать на успех или неудачу в соответствии с правилами для сценариев оболочки. То есть, даже если 0в вашей C-программе это означает «false», вы все равно заставите свою программу возвращаться в 0качестве кода завершения, если вы хотите, чтобы она сообщала об успешном выполнении (которое оболочка затем интерпретирует как true ).
Элия ​​Каган
источник
3
вау отличный ответ! Там много информации, которую я не знал. Возвращение 0 в конце первого скрипта приводит к выполнению остальной части скрипта (я могу сказать, что chmod + x запустился). К сожалению, похоже, что / usr / bin / wget в моем скрипте не может получить веб-страницу для вставки в script.sh. Я предполагаю, что к моменту выполнения этого скрипта rc.local сеть не готова. Куда я могу поместить эту команду, чтобы она запускалась при запуске сети и автоматически при запуске?
Программист
Я «взломал» его на данный момент, запустив sudo visudo, добавив следующую строку: username ALL = (ALL) NOPASSWD: ALL запускает программу «автозапуска приложений» (для чего нужна команда 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 заработал.
Programster
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

elJenso
источник
3
Синтаксис с #!является правильным. (По крайней мере, #!часть правильная. А остальное, как правило, хорошо подходит для rc.local.) См. Эту статью и этот вопрос . В любом случае, добро пожаловать в Ask Ubuntu!
Элия ​​Каган