Perché rc.local non esegue tutti i miei comandi e cosa posso fare al riguardo?


35

Ho il seguente rc.localscript:

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

La prima riga, in startup_script.shrealtà scarica il script.shfile a /script.shcui si fa riferimento nella terza riga.

Sfortunatamente, sembra che non stia eseguendo lo script o eseguendo lo script. L'esecuzione rc.localmanuale del file dopo aver avviato funziona perfettamente. Chmod non può essere eseguito all'avvio o qualcosa del genere?

Risposte:


70

Puoi saltare fino a Una correzione rapida ma questa non è necessariamente l'opzione migliore. Quindi raccomando di leggere tutto questo per primo.

rc.local non tollera errori.

rc.localnon fornisce un modo per recuperare in modo intelligente dagli errori. Se un comando non riesce, smette di funzionare. La prima riga, #!/bin/sh -efa sì che venga eseguita in una shell invocata con il -eflag. Il -eflag è ciò che fa rc.localfermare l'esecuzione di uno script (in questo caso ) la prima volta che un comando fallisce al suo interno.

Vuoi rc.localcomportarti così. Se un comando ha esito negativo, non si desidera che continui con qualsiasi altro comando di avvio potrebbe fare affidamento sul fatto che sia riuscito.

Quindi se un comando fallisce, i comandi successivi non verranno eseguiti. Il problema qui è che /script.shnon è stato eseguito (non che non è riuscito, vedi sotto), quindi molto probabilmente qualche comando prima che fallisse. Ma quale?

È stato /bin/chmod +x /script.sh?

No.

chmodfunziona bene in qualsiasi momento. A condizione che sia /binstato montato il filesystem che contiene , è possibile eseguire /bin/chmod. Ed /binè montato prima delle rc.localcorse.

Quando eseguito come root, /bin/chmodraramente fallisce. Non funzionerà se il file su cui opera è di sola lettura e potrebbe non funzionare se il file system su cui si trova non supporta le autorizzazioni. Nessuno dei due è probabilmente qui.

A proposito, sh -eè l' unica ragione per cui in realtà sarebbe un problema se chmodfallito. Quando si esegue un file di script invocando esplicitamente il suo interprete, non importa se il file è contrassegnato come eseguibile. Solo se dicesse /script.shil bit eseguibile del file è importante. Dal momento che dice sh /script.sh, non lo fa (a meno che ovviamente non si /script.sh chiami mentre è in esecuzione, il che potrebbe non riuscire a non essere eseguibile, ma è improbabile che si chiami da solo).

Quindi cosa è fallito?

sh /home/incero/startup_script.shfallito. Quasi certamente.

Sappiamo che è stato eseguito perché scaricato /script.sh.

(Altrimenti, sarebbe importante assicurarsi che funzionasse, nel caso in cui in qualche modo /binnon fosse nel PERCORSO - rc.localnon ha necessariamente la stessa PATHcosa che hai quando sei loggato. Se /binnon fosse nel rc.localpercorso, questo richiederebbe shdi essere eseguito come /bin/sh. Dato che è stato eseguito, /binè in PATH, il che significa che è possibile eseguire altri comandi che si trovano in /bin, senza qualificare completamente i loro nomi. Ad esempio, è possibile eseguire solo chmodanziché /bin/chmod. Tuttavia, in linea con il proprio stile in rc.local, ho usato nomi completi per tutti i comandi tranne sh, ogni volta che sto suggerendo di eseguirli.)

Possiamo essere abbastanza sicuri che /bin/chmod +x /script.shnon sia mai stato eseguito (o vedresti che è /script.shstato eseguito). E sappiamo che sh /script.shnon è stato eseguito neanche.

Ma è stato scaricato /script.sh. È riuscito! Come potrebbe fallire?

Due significati di successo

Ci sono due cose diverse che una persona potrebbe significare quando dice che un comando ha avuto successo:

  1. Ha fatto quello che volevi che facesse.
  2. Ha riferito che ci è riuscito.

E così è per il fallimento. Quando una persona dice che un comando non è riuscito, potrebbe significare:

  1. Non ha fatto quello che volevi che facesse.
  2. Ha riferito di aver fallito.

Uno script eseguito sh -e, ad esempio rc.local, interromperà l'esecuzione la prima volta che un comando segnala che non è riuscito . Non fa differenza quello che ha effettivamente fatto il comando.

A meno che tu non intenda startup_script.shsegnalare un errore quando fa quello che vuoi, questo è un bug startup_script.sh.

  • Alcuni bug impediscono a uno script di fare ciò che vuoi che faccia. Influiscono su ciò che i programmatori chiamano i suoi effetti collaterali .
  • E alcuni bug impediscono a uno script di riportare correttamente se è riuscito o meno. Influiscono su ciò che i programmatori chiamano il suo valore di ritorno (che in questo caso è uno stato di uscita ).

È molto probabile che abbia startup_script.shfatto tutto il possibile, tranne per il fatto che ha fallito.

Come viene segnalato successo o fallimento

Uno script è un elenco di zero o più comandi. Ogni comando ha uno stato di uscita. Supponendo che non vi siano errori nell'esecuzione effettiva dello script (ad esempio, se l'interprete non è in grado di leggere la riga successiva dello script durante l'esecuzione), lo stato di uscita di uno script è:

  • 0 (esito positivo) se lo script era vuoto (ovvero non aveva comandi).
  • N, se lo script è terminato a seguito del comando , dove è presente un codice di uscita.exit NN
  • In caso contrario, il codice di uscita dell'ultimo comando eseguito nello script.

Quando viene eseguito un eseguibile, segnala il proprio codice di uscita - non sono solo per gli script. (E tecnicamente, i codici di uscita dagli script sono i codici di uscita restituiti dalle shell che li eseguono.)

Ad esempio, se un programma C termina con exit(0);, o return 0;nella sua main()funzione, il codice 0viene dato al sistema operativo, che lo fornisce al processo di chiamata (che può, ad esempio, essere la shell da cui è stato eseguito il programma).

0significa che il programma è riuscito. Ogni altro numero indica che è fallito. (In questo modo, numeri diversi possono a volte fare riferimento a motivi diversi per cui il programma ha avuto esito negativo.)

Comandi destinati a fallire

A volte, si esegue un programma con l'intenzione che fallirà. In queste situazioni, potresti considerare il suo fallimento come un successo, anche se non è un bug che il programma riporti l'errore. Ad esempio, potresti utilizzare rmun file che sospetti già non esista, solo per assicurarti che sia eliminato.

Qualcosa del genere sta probabilmente accadendo startup_script.sh, appena prima che smetta di funzionare. L' ultimo comando da eseguire nello script sta probabilmente segnalando un errore (anche se il suo "fallimento" potrebbe essere del tutto soddisfacente o addirittura necessario), il che rende l'errore di riportare lo script.

Test destinati a fallire

Un tipo speciale di comando è un test , con il quale intendo un comando eseguito per il suo valore di ritorno piuttosto che per i suoi effetti collaterali. Cioè, un test è un comando che viene eseguito in modo che il suo stato di uscita possa essere esaminato (e seguito).

Ad esempio, supponiamo di dimenticare se 4 è uguale a 5. Fortunatamente, conosco lo scripting della shell:

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

Qui, il test [ -eq 5 ]fallisce perché dopo tutto risulta 4 ≠ 5. Ciò non significa che il test non sia stato eseguito correttamente; lo ha fatto. Il suo compito era controllare se 4 = 5, quindi segnalare il successo in tal caso e il fallimento in caso contrario.

Vedete, nello scripting della shell, il successo può anche significare vero e il fallimento può anche significare falso .

Anche se l' echoaffermazione non viene mai eseguita, il ifblocco nel suo insieme restituisce successo.

Tuttavia, suppongo di averlo scritto più breve:

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

Questa è una scorciatoia comune. &&è un booleano e un operatore. &&Un'espressione, che consiste di &&dichiarazioni su entrambi i lati, restituisce false (guasto) a meno che entrambi i lati restituiscono true (successo). Proprio come un normale e .

Se qualcuno ti chiede "Derek è andato al centro commerciale e ha pensato a una farfalla?" e sai che Derek non è andato al centro commerciale, non devi preoccuparti di capire se pensasse a una farfalla.

Allo stesso modo, se il comando a sinistra di &&non riesce (falso), l'intera &&espressione fallisce immediatamente (falso). L'affermazione sul lato destro di &&non viene mai eseguita.

Ecco, [ 4 -eq 5 ]corre. "Fallisce" (restituendo false). Quindi l'intera &&espressione fallisce. echo "Yeah, they're totally the same."non corre mai. Tutto si è comportato come dovrebbe essere, ma questo comando segnala un errore (anche se il ifcondizionale altrimenti equivalente sopra riportato indica il successo).

Se quella fosse l'ultima affermazione in uno script (e lo script è arrivato ad esso, piuttosto che terminare ad un certo punto prima di esso), l'intero script segnalerebbe l' errore .

Ci sono molti test oltre a questo. Ad esempio, ci sono test con ||("o"). Tuttavia, l'esempio sopra dovrebbe essere sufficiente per spiegare quali sono i test e consentire all'utente di utilizzare efficacemente la documentazione per determinare se un determinato comando / istruzione è un test.

shvs. sh -e, rivisitato

Poiché la #!riga (vedi anche questa domanda ) nella parte superiore di /etc/rc.localhas sh -e, il sistema operativo esegue lo script come se fosse stato invocato con il comando:

sh -e /etc/rc.local

Al contrario, gli altri script, come ad esempio startup_script.sh, eseguire senza la -ebandiera:

sh /home/incero/startup_script.sh

Di conseguenza continuano a funzionare anche quando un comando in essi riporta errori.

Questo è normale e buono. rc.localdovrebbe essere invocato con sh -ee la maggior parte degli altri script - inclusa la maggior parte degli script eseguiti da rc.local- non dovrebbe.

Assicurati di ricordare la differenza:

  • Gli script vengono eseguiti con sh -eerrore di segnalazione delle uscite la prima volta che un comando in esse contenuto esce da errori di segnalazione.

    È come se lo script fosse un singolo comando lungo costituito da tutti i comandi nello script uniti con gli &&operatori.

  • Gli script eseguiti con sh(senza -e) continuano l'esecuzione fino a quando non arrivano a un comando che li termina (esce da) o fino alla fine dello script. L'esito positivo o negativo di ogni comando è essenzialmente irrilevante (a meno che il comando successivo non lo controlli). Lo script esce con lo stato di uscita dell'ultima esecuzione del comando.

Aiutare il tuo script a capire che dopo tutto non è un tale fallimento

Come puoi evitare che la tua sceneggiatura pensi che sia fallita quando non lo ha fatto?

Guarda cosa succede poco prima che finisca di funzionare.

  1. Se un comando ha avuto esito negativo quando avrebbe dovuto avere esito positivo, capire perché e risolvere il problema.

  2. Se un comando non ha funzionato e questa è stata la cosa giusta da fare, impedire la propagazione dello stato di errore.

    • Un modo per impedire la propagazione di uno stato di errore consiste nell'eseguire un altro comando che abbia esito positivo. /bin/truenon ha effetti collaterali e segnala il successo (proprio come /bin/falsenon fa nulla e fallisce anche).

    • Un altro è assicurarsi che lo script sia terminato da exit 0.

      Non è necessariamente la stessa cosa di exit 0essere alla fine della sceneggiatura. Ad esempio, potrebbe esserci un ifblocco in cui lo script esce all'interno.

È meglio sapere cosa fa sì che il tuo script riporti errori, prima di farlo registrare. Se in qualche modo sta fallendo (nel senso di non fare quello che vuoi che faccia), non vuoi davvero riportare il successo.

Una soluzione rapida

Se non è possibile eseguire startup_script.shcorrettamente la segnalazione di uscita, è possibile modificare il comando rc.localche lo esegue, quindi tale comando segnala il successo anche se startup_script.shnon lo ha fatto.

Attualmente hai:

sh /home/incero/startup_script.sh

Questo comando ha gli stessi effetti collaterali (ovvero l'effetto collaterale della corsa startup_script.sh), ma riporta sempre il successo:

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

Ricorda, è meglio sapere perché startup_script.shsegnala l'errore e risolverlo.

Come funziona la correzione rapida

Questo è in realtà un esempio di ||test, test o .

Supponiamo che tu mi chieda se ho portato fuori la spazzatura o spazzolato il gufo. Se avessi portato fuori la spazzatura, potrei davvero dire "sì", anche se non ricordo se ho spazzolato il gufo.

Il comando a sinistra delle ||corse. Se riesce (vero), allora il lato destro non deve correre. Quindi, se startup_script.shsegnala un esito positivo, il truecomando non viene mai eseguito.

Tuttavia, se viene startup_script.shsegnalato un errore [Non ho eliminato il cestino] , il risultato di /bin/true [se ho spazzolato il gufo] è importante.

/bin/truerestituisce sempre successo (o vero , come a volte lo chiamiamo). Di conseguenza, l'intero comando ha esito positivo e rc.localpuò essere eseguito il comando successivo in .

Una nota aggiuntiva su successo / fallimento, vero / falso, zero / diverso da zero.

Sentiti libero di ignorare questo. Potresti voler leggere questo se programmi in più di una lingua, però (cioè non solo script di shell).

È un punto di grande confusione per gli script di shell che adottano linguaggi di programmazione come C, e per i programmatori C che utilizzano script di shell, per scoprire:

  • Nella shell scripting:

    1. Un valore di ritorno 0significa successo e / o vero .
    2. Un valore di ritorno di qualcosa di diverso da 0significa fallimento e / o falso .
  • In programmazione C:

    1. Un valore restituito 0significa false ..
    2. Un valore di ritorno di qualcosa di diverso da 0significa vero .
    3. Non esiste una regola semplice per cosa significhi successo e cosa significhi fallimento. A volte 0significa successo, altre volte significa fallimento, altre volte significa che la somma dei due numeri appena aggiunti è zero. I valori di ritorno nella programmazione generale vengono utilizzati per segnalare un'ampia varietà di diversi tipi di informazioni.
    4. Lo stato di uscita numerico di un programma dovrebbe indicare l'esito positivo o negativo in conformità con le regole per gli script di shell. Cioè, anche se 0significa falso nel tuo programma C, fai comunque in modo che il tuo programma ritorni 0come suo codice di uscita se vuoi che riporti che è riuscito (che la shell poi interpreta come vero ).

3
wow ottima risposta! Ci sono molte informazioni lì che non sapevo. Restituire 0 alla fine del primo script provoca l'esecuzione del resto dello script (bene posso dire che il chmod + x è stato eseguito). Sfortunatamente sembra che / usr / bin / wget nel mio script non riesca a recuperare una pagina web da inserire in script.sh. Immagino che la rete non sia pronta quando viene eseguito questo script rc.local. Dove posso inserire questo comando in modo che venga eseguito all'avvio della rete e automaticamente all'avvio?
Programmatore

L'ho 'hackerato' per ora eseguendo sudo visudo, aggiungendo la seguente riga: username ALL = (ALL) NOPASSWD: TUTTI eseguendo il programma 'startup application' (qual è il comando CLI per questo?), E aggiungendo startup_script a questo, mentre startupscript ora esegue script.sh con il comando sudo in anticipo per essere eseguito come root (non è più necessaria la password). Senza dubbio questo è super insicuro e terribile, ma questo è solo per un disco di distribuzione live personalizzato con avvio pxe.
Programmatore

@ Stu2000 Supponendo incero(suppongo che sia il nome utente) è comunque un amministratore , e quindi può eseguire qualsiasi comando come root(inserendo la propria password utente), che non è super insicuro e terribile . Se vuoi altre soluzioni, ti consiglio di pubblicare una nuova domanda a riguardo . Un problema era il motivo per cui i comandi in rc.localnon venivano eseguiti (e hai anche controllato per vedere cosa sarebbe successo se li avessi fatti continuare a funzionare). Ora hai un problema diverso: vuoi connettere la macchina alla rete prima delle rc.localesecuzioni, o pianificare la startup_script.shsuccessiva esecuzione .
Eliah Kagan,

1
Da allora sono tornato su questo problema, ho modificato rc.local e ho scoperto che wget non "funzionava". L'aggiunta /usr/bin/sudo service networking restartallo script di avvio prima del comando wget ha comportato che wget funzionasse.
Programster

3
@EliahKagan meravigliosa risposta esaustiva. Difficilmente ho trovato una documentazione migliore surc.local
souravc, il

1

È possibile (nelle varianti Unix che utilizzo) sovrascrivere il comportamento predefinito modificando:

#!/bin/sh -e

A:

#!/bin/sh

All'inizio della sceneggiatura. Il flag "-e" indica a sh di uscire al primo errore. Il nome lungo di quella bandiera è "errexit", quindi la linea originale equivale a:

#!/bin/sh --errexit

sì, rimuovendo le -eopere su raspbian jessie.
Oki Erie Rinaldi,

L' -eè lì per un motivo. Non lo farei se fossi in te. Ma non sono i tuoi genitori.
Wyatt8740,

-4

cambia la prima riga da: #!/bin/sh -e a !/bin/sh -e


3
La sintassi con #!è quella corretta. (Almeno, la #!parte è corretta. E il resto di solito funziona bene per, per rc.local.) Vedi questo articolo e questa domanda . Comunque, benvenuti a Ask Ubuntu!
Eliah Kagan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.