Dlaczego rc.local nie uruchamia wszystkich moich poleceń i co mogę z tym zrobić?

35

Mam następujący rc.localskrypt:

#!/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

Pierwszy wiersz startup_script.shfaktycznie pobiera script.shplik, /script.sho którym mowa w trzecim wierszu.

Niestety wygląda na to, że nie sprawia, że ​​skrypt jest wykonywalny ani nie jest uruchamiany. Uruchamiając rc.localplik ręcznie po uruchomieniu działa idealnie. Czy chmod nie może być uruchamiany przy starcie, czy coś?

Programista
źródło

Odpowiedzi:

70

Możesz przejść do A Quick Fix, ale niekoniecznie jest to najlepsza opcja. Dlatego polecam najpierw przeczytać to wszystko.

rc.local nie toleruje błędów.

rc.localnie zapewnia inteligentnego odzyskiwania po błędach. Jeśli dowolne polecenie nie powiedzie się, przestaje działać. Pierwszy wiersz #!/bin/sh -epowoduje wykonanie go w powłoce wywołanej z -eflagą. -eFlaga jest to, co sprawia, że skrypt (w tym przypadku rc.local) przestanie działać po raz pierwszy polecenia nie powiedzie się w nim.

Chcesz rc.localsię tak zachowywać. Jeśli polecenie nie powiedzie się, nie chcesz, aby kontynuowało działanie z innymi poleceniami uruchamiania, które mogły polegać na powodzeniu.

Więc jeśli jakieś polecenie zawiedzie, kolejne polecenia nie będą działać. Problem polega na tym, że /script.shsię nie uruchomił (nie, że się nie udało, patrz poniżej), więc najprawdopodobniej jakieś polecenie, zanim się nie powiedzie. Ale który?

Czy to było /bin/chmod +x /script.sh?

Nie.

chmoddziała dobrze w dowolnym momencie. Pod warunkiem, że system plików, który zawiera, /binzostał podłączony, możesz uruchomić /bin/chmod. I /binjest montowany przed rc.localuruchomieniami.

Po uruchomieniu jako root /bin/chmodrzadko kończy się niepowodzeniem. Nie powiedzie się, jeśli plik, na którym działa, jest tylko do odczytu i może zawieść, jeśli system plików, na którym działa, nie obsługuje uprawnień. Prawdopodobnie nie ma tutaj.

Nawiasem mówiąc, sh -ejest to jedyny powód, dla którego byłby to problem, gdyby się chmodnie powiódł. Po uruchomieniu pliku skryptu przez jawne wywołanie jego interpretera nie ma znaczenia, czy plik jest oznaczony jako wykonywalny. Tylko gdyby powiedział, /script.shże plik wykonywalny ma znaczenie. Ponieważ mówi sh /script.sh, że tak nie jest (chyba że /script.sh wywołuje się sam podczas działania, co może się nie powieść, ponieważ nie jest wykonywalny, ale jest mało prawdopodobne, że się wywołuje).

Co więc się nie udało?

sh /home/incero/startup_script.shnie powiodło się Prawie na pewno.

Wiemy, że działał, ponieważ został pobrany /script.sh.

(W przeciwnym razie ważne jest, aby upewnić się, że się uruchomił, na wypadek, gdyby jakoś /binnie było w ŚCIEŻCE - rc.localniekoniecznie ma to samo, PATHco masz po zalogowaniu. Jeśli /binnie byłeś na dobrej rc.localdrodze, to wymagałoby shuruchomienia jako /bin/sh. Ponieważ się uruchomił, /binjest w PATH, co oznacza, że ​​możesz uruchamiać inne polecenia, które się w nim znajdują /bin, bez pełnego określania ich nazw. Na przykład możesz uruchomić chmodzamiast tego /bin/chmod. Jednak zgodnie ze swoim stylem w rc.local, użyłem w pełni kwalifikowanych nazw dla wszystkich poleceń, z wyjątkiem shprzypadków, gdy sugeruję ich uruchomienie).

Możemy być prawie pewni, że /bin/chmod +x /script.shnigdy nie biegł (lub zobaczysz, że /script.shzostał wykonany). I wiemy, że też sh /script.shnie był prowadzony.

Ale zostało pobrane /script.sh. Udało się! Jak to może zawieść?

Dwa znaczenia sukcesu

Istnieją dwie różne rzeczy, które osoba może mieć na myśli, gdy mówi, że polecenie się powiodło:

  1. Zrobił to, co chciałeś.
  2. Poinformowano, że się udało.

I tak jest z porażką. Gdy osoba mówi, że polecenie nie powiodło się, może to oznaczać:

  1. Nie zrobił tego, co chciałeś.
  2. Zgłoszono, że się nie udało.

Uruchomienie skryptu sh -e, na przykład rc.local, przestanie działać po raz pierwszy, gdy polecenie zgłosi, że nie powiodło się . Nie ma znaczenia, co faktycznie zrobiło polecenie.

Jeśli nie zamierzasz startup_script.shzgłaszać awarii, gdy robi to, co chcesz, jest to błąd startup_script.sh.

  • Niektóre błędy uniemożliwiają skryptowi robienie tego, co chcesz. Wpływają na to, co programiści nazywają jego skutkami ubocznymi .
  • Niektóre błędy uniemożliwiają skryptowi prawidłowe zgłaszanie, czy się udało. Wpływają na to, co programiści nazywają wartością zwracaną (w tym przypadku jest to status wyjścia ).

Najprawdopodobniej startup_script.shzrobił wszystko, co powinien, oprócz tego, że zawiódł.

Jak zgłasza się sukces lub porażkę

Skrypt to lista zer lub więcej poleceń. Każde polecenie ma status wyjścia. Zakładając, że nie ma błędów w uruchamianiu skryptu (na przykład, jeśli interpreter nie mógł odczytać następnego wiersza skryptu podczas uruchamiania), kod wyjścia skryptu to:

  • 0 (sukces), jeśli skrypt był pusty (tzn. nie miał poleceń).
  • N, jeśli skrypt zakończył się w wyniku polecenia , gdzie jest kod zakończenia.exit NN
  • Kod wyjścia ostatniego polecenia wykonanego w skrypcie, w przeciwnym razie.

Gdy plik wykonywalny jest uruchamiany, zgłasza swój własny kod wyjścia - nie dotyczą one tylko skryptów. (I technicznie kody wyjścia ze skryptów to kody wyjścia zwracane przez powłoki, które je uruchamiają).

Na przykład, jeśli program C kończy się na exit(0);lub return 0;w swojej main()funkcji, kod 0jest przekazywany do systemu operacyjnego, który zapewnia go procesowi wywołującemu (którym może być na przykład powłoka, z której program został uruchomiony).

0oznacza, że ​​program się powiódł. Każda inna liczba oznacza, że ​​się nie udało. (W ten sposób różne liczby mogą czasami odnosić się do różnych przyczyn niepowodzenia programu).

Polecenia przeznaczone do niepowodzenia

Czasami uruchamiasz program z zamiarem niepowodzenia. W takich sytuacjach możesz uznać jego niepowodzenie za sukces, mimo że program nie zgłasza błędu. Na przykład możesz użyć rmpliku, który podejrzewasz, że już nie istnieje, aby się upewnić, że został usunięty.

Prawdopodobnie dzieje się coś takiego startup_script.sh, zanim przestanie działać. Ostatnie polecenie, aby uruchomić w skrypcie jest prawdopodobnie zgłaszania awarii (choć jej „awarii” może być całkowicie w porządku, a nawet konieczne), co sprawia, że niepowodzenie raportu skrypt.

Testy oznaczają niepowodzenie

Jednym szczególnym rodzajem polecenia jest test , przez który mam na myśli wykonanie polecenia zamiast jego skutków ubocznych. Oznacza to, że test jest uruchamiany, aby można było zbadać (i wykonać) jego status wyjścia.

Załóżmy na przykład, że zapomniałem, czy 4 jest równe 5. Na szczęście znam skrypty powłoki:

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

Tutaj test [ -eq 5 ]kończy się niepowodzeniem, ponieważ okazuje się, że w końcu 4 after 5. To nie znaczy, że test nie działał poprawnie; to zrobiło. Jego zadaniem było sprawdzenie, czy 4 = 5, a następnie zgłoszenie sukcesu, jeśli tak, i niepowodzenia, jeśli nie.

Widzisz, w skryptach powłoki sukces może również oznaczać prawda , a niepowodzenie może oznaczać fałsz .

Mimo że echoinstrukcja nigdy się nie uruchamia, ifblok jako całość zwraca sukces.

Jednak przypuszczam, że napisałem to krócej:

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

Jest to powszechny skrót. &&jest wartością logiczną i operatorem. &&Wyraz, który składa się &&ze sprawozdania z każdej strony, zwraca false (niepowodzenie), chyba że obie strony powrót prawdziwej (sukces). Tak jak normalnie i .

Jeśli ktoś cię zapyta: „czy Derek poszedł do centrum handlowego i pomyślał o motyle?” i wiesz, że Derek nie poszedł do centrum handlowego, nie musisz się zastanawiać, czy pomyślał o motyle.

Podobnie, jeśli polecenie po lewej stronie &&nie powiedzie się (fałsz), całe &&wyrażenie natychmiast zawiedzie (fałsz). Instrukcja po prawej stronie &&nigdy nie jest uruchamiana.

Tutaj [ 4 -eq 5 ]biegnie. „Kończy się niepowodzeniem” (zwraca false). Całe &&wyrażenie zawodzi. echo "Yeah, they're totally the same."nigdy nie działa. Wszystko zachowało się tak, jak powinno, ale to polecenie zgłasza awarię (mimo że ifpowyższy równoważny warunek powyżej informuje o sukcesie).

Gdyby to była ostatnia instrukcja w skrypcie (a skrypt do niej dotarł, zamiast kończyć w pewnym momencie przed nią), cały skrypt zgłosiłby błąd .

Poza tym jest wiele testów. Na przykład są testy z ||(„lub”). Jednak powyższy przykład powinien wystarczyć do wyjaśnienia, jakie są testy, i umożliwić efektywne korzystanie z dokumentacji w celu ustalenia, czy dane polecenie / polecenie jest testem.

shkontra sh -eponownie odwiedzone

Od tej #!linii (patrz też to pytanie ) w górnej części /etc/rc.localma sh -e, system operacyjny uruchamia skrypt tak jakby wywoływana za pomocą polecenia:

sh -e /etc/rc.local

W przeciwieństwie do tego, twoje inne skrypty, takie jak startup_script.sh, uruchomić bez tej -eflagi:

sh /home/incero/startup_script.sh

W związku z tym nadal działają, nawet jeśli polecenie w nich zgłasza awarię.

To jest normalne i dobre. rc.localpowinny być wywoływane przy użyciu sh -ewiększości skryptów - w tym większości skryptów uruchamianych przez - rc.localnie powinny.

Pamiętaj tylko o różnicy:

  • Skrypty są uruchamiane z sh -ebłędem zgłaszania wyjścia, gdy polecenie, które zawierają, kończy działanie zgłaszania błędu.

    To tak, jakby skrypt był pojedynczym długim poleceniem składającym się ze wszystkich poleceń w skrypcie połączonych z &&operatorami.

  • Skrypty działają z sh(bez -e), dopóki nie dotrą do polecenia, które je kończy (wychodzi z nich) lub do samego końca skryptu. Powodzenie lub niepowodzenie każdego polecenia jest w zasadzie nieistotne (chyba że następne polecenie to sprawdzi). Skrypt kończy działanie ze statusem zakończenia ostatniego uruchomienia polecenia.

Pomaganie skryptowi w zrozumieniu, że to wcale nie taka porażka

Jak możesz nie myśleć, że skrypt się nie powiedzie, jeśli nie?

Patrzysz na to, co dzieje się tuż przed uruchomieniem.

  1. Jeśli polecenie nie powiodło się, gdy powinno się powieść, dowiedz się, dlaczego i napraw problem.

  2. Jeśli polecenie nie powiedzie się, a tak właśnie powinno być, należy uniemożliwić propagację tego statusu niepowodzenia.

    • Jednym ze sposobów na uniknięcie propagacji statusu błędu jest uruchomienie innej komendy, która się powiedzie. /bin/truenie ma żadnych skutków ubocznych i informuje o sukcesie (tak jak /bin/falsenic nie robi, a także zawodzi).

    • Innym jest upewnienie się, że skrypt zostanie zakończony przez exit 0.

      To niekoniecznie to samo, exit 0co na końcu skryptu. Na przykład może istnieć ifblokada, w której skrypt kończy działanie.

Najlepiej wiedzieć, co powoduje, że skrypt zgłasza awarię, zanim zgłosi sukces. Jeśli naprawdę w jakiś sposób zawodzi (w sensie nie robienia tego, co chcesz), tak naprawdę nie chcesz, aby raportował sukces.

Szybka poprawka

Jeśli nie możesz startup_script.shpomyślnie zakończyć raportowania wyjścia, możesz zmienić polecenie, rc.localktóre je uruchamia, tak aby polecenie raportowało sukces, nawet jeśli startup_script.shnie.

Obecnie masz:

sh /home/incero/startup_script.sh

To polecenie ma takie same skutki uboczne (tj. Efekt uboczny uruchamiania startup_script.sh), ale zawsze zgłasza powodzenie:

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

Pamiętaj, że lepiej wiedzieć, dlaczego startup_script.shzgłasza awarię i naprawić.

Jak działa szybka poprawka

To jest właściwie przykład ||testu, lub testu.

Przypuśćmy, że zapytasz, czy wyjąłem śmieci lub wyszczotkowałem sowę. Gdybym wyniósł śmieci, mogę zgodnie z prawdą powiedzieć „tak”, nawet jeśli nie pamiętam, czy wyszczotkowałem sowę.

Polecenie po lewej stronie ||uruchomień. Jeśli się powiedzie (prawda), to prawa strona nie musi biec. Jeśli więc startup_script.shraport się powiedzie, truepolecenie nigdy się nie uruchomi.

Jeśli jednak startup_script.shzgłosi błąd [nie wyjąłem śmieci] , wtedy wynik /bin/true [jeśli wyszczotkowałem sowę] ma znaczenie.

/bin/truezawsze zwraca sukces (lub prawdziwy , jak to czasem nazywamy). W rezultacie cała komenda się powiedzie i rc.localmoże zostać uruchomiona kolejna komenda w .

Dodatkowa uwaga na temat powodzenia / niepowodzenia, prawda / fałsz, zero / zero.

Możesz to zignorować. Możesz to przeczytać, jeśli programujesz w więcej niż jednym języku (tj. Nie tylko skryptowanie powłoki).

Jest to duże zamieszanie dla skrypterów powłoki podejmujących języki programowania, takich jak C, a dla programistów C, którzy podejmują się skryptów powłoki, aby odkryć:

  • W skryptach powłoki:

    1. Zwracana wartość 0oznacza sukces i / lub wartość true .
    2. Zwracana wartość czegoś innego niż 0oznacza niepowodzenie i / lub fałsz .
  • W programowaniu C:

    1. Zwracana wartość 0oznacza false .
    2. Zwracana wartość czegoś innego niż 0oznacza true .
    3. Nie ma prostej zasady, co oznacza sukces, a co porażkę. Czasem 0oznacza sukces, innym razem porażkę, innym razem sumę dwóch liczb, które właśnie dodałeś, wynosi zero. Wartości zwrotne w programowaniu ogólnym są używane do sygnalizowania szerokiej gamy różnych rodzajów informacji.
    4. Numeryczny status wyjścia programu powinien wskazywać powodzenie lub niepowodzenie zgodnie z regułami skryptowania powłoki. Oznacza to, że nawet jeśli 0oznacza to fałsz w programie C, nadal powoduje, że Twój program zwraca 0kod wyjścia, jeśli chcesz, aby zgłosił powodzenie (co powłoka interpretuje jako prawda ).
Eliah Kagan
źródło
3
wow świetna odpowiedź! Jest tam wiele informacji, których nie znałem. Zwrócenie 0 na końcu pierwszego skryptu powoduje wykonanie pozostałej części skryptu (no cóż, mogę powiedzieć, że uruchomiono chmod + x). Niestety wygląda na to, że / usr / bin / wget w moim skrypcie nie może pobrać strony internetowej do umieszczenia w script.sh. Zgaduję, że sieć nie jest gotowa do czasu uruchomienia tego skryptu rc.local. Gdzie mogę umieścić to polecenie, aby działało po uruchomieniu sieci i automatycznie podczas uruchamiania?
Programster
Na razie „zhackowałem” go, uruchamiając sudo visudo, dodając następujący wiersz: nazwa użytkownika ALL = (ALL) NOPASSWD: WSZYSTKIE uruchomione programy „startowe” (jakie jest w tym celu polecenie CLI?) I dodając do tego skrypt startowy, podczas gdy startupscript uruchamia teraz skrypt.sh z poleceniem sudo, aby działał jako root (nie potrzebuje już hasła). Bez wątpienia jest to bardzo niepewne i straszne, ale jest to tylko niestandardowy dysk dystrybucyjny pxe-boot na żywo.
Programster
@ Stu2000 Zakładając incero(zakładam, że to jest nazwa użytkownika) i tak jest administratorem , a zatem można uruchamiać dowolne polecenia jako root(wprowadzając własne hasło użytkownika), co nie jest zbyt niebezpieczne i okropne . Jeśli chcesz innych rozwiązań, polecam zamieścić nowe pytanie na ten temat . Jednym z problemów było to, że polecenia rc.localsię nie uruchomiły (a także sprawdziłeś, co by się stało, gdybyś je uruchomił). Teraz masz inny problem: chcesz podłączyć komputer do sieci przed rc.localuruchomieniem lub zaplanować uruchomienie startup_script.shpóźniej.
Eliah Kagan
1
Od tego czasu wróciłem do tego wydania, zredagowałem plik rc.local i stwierdziłem, że wget nie działa. Dodanie /usr/bin/sudo service networking restartdo skryptu uruchamiania przed uruchomieniem komendy wget spowodowało, że wget zaczął działać.
Programster
3
@EliahKagan wspaniała wyczerpująca odpowiedź. Lepiej nie natknąłem się na lepszą dokumentacjęrc.local
souravc
1

Możesz (w wariantach Unixa, których używam) zastąpić domyślne zachowanie, zmieniając:

#!/bin/sh -e

Do:

#!/bin/sh

Na początku skryptu. Flaga „-e” instruuje sh, aby wyjść z pierwszego błędu. Długa nazwa tej flagi to „errexit”, więc oryginalny wiersz jest równoważny z:

#!/bin/sh --errexit
Bryce
źródło
tak, usunięcie -edziała na raspbian jessie.
Oki Erie Rinaldi,
-eJest tam bez powodu. Nie zrobiłbym tego, gdybym był tobą. Ale nie jestem twoimi rodzicami.
Wyatt8740
-4

zmień pierwszą linię z: #!/bin/sh -e na !/bin/sh -e

elJenso
źródło
3
Składnia z #!jest poprawna. (Przynajmniej #!część jest poprawna. A reszta zwykle działa dobrze, na rc.local.) Zobacz ten artykuł i to pytanie . Tak czy inaczej, zapraszamy do Ask Ubuntu!
Eliah Kagan