Pourquoi rc.local n’exécute-t-il pas toutes mes commandes et que puis-je faire à ce sujet?

35

J'ai le rc.localscript suivant :

#!/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 première ligne startup_script.shtélécharge en fait le script.shfichier /script.shauquel il est fait référence dans la troisième ligne.

Malheureusement, il semble que cela ne rende pas le script exécutable ou n'exécute pas le script. L'exécution rc.localmanuelle du fichier après le démarrage fonctionne parfaitement. Chmod ne peut pas être exécuté au démarrage ou quelque chose?

Programmeur
la source

Réponses:

70

Vous pouvez aller jusqu'à A Quick Fix, mais ce n'est pas nécessairement la meilleure option. Je recommande donc de lire tout cela en premier.

rc.local ne tolère pas les erreurs.

rc.localne fournit pas un moyen de récupérer intelligemment des erreurs. Si une commande échoue, elle cesse de s'exécuter. La première ligne, #!/bin/sh -eprovoque son exécution dans un shell appelé avec l' -eindicateur. L' -eindicateur est ce qui fait qu'un script (dans ce cas, rc.local) cesse de s'exécuter la première fois qu'une commande échoue.

Vous voulez rc.localvous comporter comme ça. Si une commande échoue, vous ne voulez pas qu'elle continue avec les autres commandes de démarrage sur lesquelles elle pourrait s'appuyer.

Donc, si une commande échoue, les commandes suivantes ne seront pas exécutées. Le problème ici est qu’il /script.shn’a pas été exécuté (ce n’est pas son échec, voir ci-dessous), donc probablement une commande avant son échec. Mais lequel?

Était-ce /bin/chmod +x /script.sh?

Non.

chmodfonctionne bien à tout moment. /binVous pouvez exécuter le système de fichiers qui contient a été monté /bin/chmod. Et /binest monté avant les rc.localcourses.

Lorsqu'il est exécuté en tant que root, il /bin/chmodéchoue rarement. Il échouera si le fichier sur lequel il opère est en lecture seule et risque d' échouer si le système de fichiers sur lequel il est actif ne dispose pas d'autorisations prises en charge. Ni est probable ici.

À propos, sh -eest la seule raison pour laquelle ce serait un problème en cas d' chmodéchec. Lorsque vous exécutez un fichier de script en appelant explicitement son interpréteur, le fichier est considéré comme exécutable. Ce n'est que si cela est dit /script.shque le bit exécutable du fichier importera. Puisqu'elle dit sh /script.sh, elle ne le fait pas (à moins bien sûr de /script.sh s'appeler pendant son exécution, ce qui pourrait échouer si elle n'est pas exécutable, mais il est peu probable qu'elle s'appelle elle-même).

Alors qu'est-ce qui a échoué?

sh /home/incero/startup_script.shéchoué. Presque certainement.

Nous savons qu'il a fonctionné, car il a été téléchargé /script.sh.

( Dans le cas contraire, il serait important de vous assurer qu'il couriez, en cas d'une certaine manière /binn'a pas été dans le PATH - rc.localn'a pas nécessairement la même PATH. Que vous avez lorsque vous êtes connecté Si /binn'étaient pas dans rc.localle chemin de », ce il faudrait shêtre exécuté comme /bin/sh. Puisqu'il couriez, /binest en PATH, ce qui signifie que vous pouvez exécuter d' autres commandes qui sont situées dans /bin, sans qualifier pleinement leurs noms. par exemple, vous pouvez exécuter simplement chmodplutôt que /bin/chmod. Cependant, conformément à votre style dans rc.local, je l' ai utilisé les noms qualifiés pour toutes les commandes , à l' exception sh, chaque fois que je vous suggère de les exécuter.)

Nous pouvons être pratiquement sûrs de /bin/chmod +x /script.shne jamais courir (ou vous verriez que cela a /script.shété exécuté). Et nous savons que ce sh /script.shn’était pas le cas non plus.

Mais c'est téléchargé /script.sh. Ça a réussi! Comment pourrait-il échouer?

Deux sens du succès

Il y a deux choses différentes qu'une personne peut vouloir dire quand elle dit qu'une commande a réussi:

  1. Il a fait ce que tu voulais qu'il fasse.
  2. Il a rapporté qu'il a réussi.

Et c'est donc pour l'échec. Lorsqu'une personne dit qu'une commande a échoué, cela peut vouloir dire:

  1. Il n'a pas fait ce que tu voulais qu'il fasse.
  2. Il a rapporté que cela a échoué.

Un script exécuté avec sh -e, comme rc.local, arrêtera de courir la première fois qu'une commande rapporte son échec . Ce que la commande a réellement fait ne change rien.

Sauf si vous avez l'intention startup_script.shde signaler un échec lorsqu'il fait ce que vous voulez, il s'agit d'un bogue startup_script.sh.

  • Certains bogues empêchent un script de faire ce que vous voulez. Ils affectent ce que les programmeurs appellent ses effets secondaires .
  • Et certains bogues empêchent un script de signaler correctement s'il a réussi ou non. Ils affectent ce que les programmeurs appellent sa valeur de retour (qui dans ce cas est un état de sortie ).

Il est fort probable que startup_script.shtout a été fait, sauf que cela a échoué.

Comment le succès ou l'échec est signalé

Un script est une liste de zéro ou plusieurs commandes. Chaque commande a un statut de sortie. En supposant qu’il n’existe pas d’échec lors de l’exécution réelle du script (par exemple, si l’interprète ne peut pas lire la ligne suivante du script lorsqu’il est exécuté), le statut de sortie d’un script est le suivant:

  • 0 (succès) si le script était vide (c.-à-d. sans commandes).
  • N, si le script se termine à la suite de la commande , où se trouve un code de sortie.exit NN
  • Le code de sortie de la dernière commande exécutée dans le script, sinon.

Lorsqu'un exécutable s'exécute, il indique son propre code de sortie - il ne s'agit pas uniquement de scripts. (Et techniquement, les codes de sortie des scripts sont les codes de sortie renvoyés par les shells qui les exécutent.)

Par exemple, si un programme C se termine par exit(0);ou return 0;dans sa main()fonction, le code 0est donné au système d'exploitation, qui le fournit au processus appelant (qui peut être, par exemple, le shell à partir duquel le programme a été exécuté).

0signifie que le programme a réussi. Chaque autre numéro signifie que cela a échoué. (Ainsi, différents numéros peuvent parfois indiquer différentes raisons pour lesquelles le programme a échoué.)

Commandes vouées à l'échec

Parfois, vous exécutez un programme avec l’intention d’échouer. Dans ces situations, son échec peut être considéré comme un succès, même si le programme ne signale pas un échec. Par exemple, vous pouvez utiliser rmun fichier dont vous pensez qu'il n'existe pas déjà, simplement pour vous assurer qu'il est supprimé.

Quelque chose comme ça se passe probablement startup_script.shjuste avant que ça arrête de courir. La dernière commande à exécuter dans le script est probablement le signalement d'un échec (même si son "échec" peut être totalement correct, voire nécessaire), ce qui entraîne l'échec du rapport du script.

Tests voués à l'échec

Un type particulier de commande est un test , j'entends par là une commande exécutée pour sa valeur de retour plutôt que pour ses effets secondaires. C'est-à-dire qu'un test est une commande qui est exécutée afin que son statut de sortie puisse être examiné (et utilisé).

Par exemple, supposons que j'oublie si 4 est égal à 5. Heureusement, je connais les scripts shell:

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

Ici, le test [ -eq 5 ]échoue car il s'avère que 4 5 après tout. Cela ne signifie pas que le test n'a pas fonctionné correctement. ça faisait. Son travail consistait à vérifier si 4 = 5, puis signaler la réussite si oui, et l'échec si non.

Vous voyez, dans les scripts shell, succès peut également signifier vrai et échec, faux .

Bien que l' echoinstruction ne soit jamais exécutée, le ifbloc dans son ensemble renvoie le succès.

Cependant, supposé que je l’ai écrit plus court:

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

C'est un raccourci courant. &&est un booléen et un opérateur. Une &&expression, qui consiste en des &&déclarations de chaque côté, renvoie false (échec) à moins que les deux côtés ne renvoient true (succès). Juste comme une normale et .

Si quelqu'un vous demande, "Est-ce que Derek est allé au centre commercial et a pensé à un papillon?" et vous savez que Derek n'est pas allé au centre commercial, vous n'avez pas à vous soucier de savoir s'il pensait à un papillon.

De même, si la commande à gauche de &&échoue (false), toute l' &&expression échoue immédiatement (false). La déclaration à droite de &&n'est jamais exécutée.

Ici, [ 4 -eq 5 ]court. Cela "échoue" (retourne faux). Donc, toute l' &&expression échoue. echo "Yeah, they're totally the same."ne court jamais. Tout a fonctionné comme il se doit, mais cette commande signale un échec (même si la ifcondition conditionnelle équivalente ci-dessus indique un succès).

S'il s'agissait de la dernière instruction d'un script (et que le script s'y trouvait, plutôt que de se terminer un peu avant), le script entier signalerait un échec .

Il y a beaucoup de tests en plus de cela. Par exemple, il existe des tests avec ||("ou"). Cependant, l'exemple ci-dessus devrait suffire à expliquer ce que sont les tests et à vous permettre d'utiliser efficacement la documentation pour déterminer si une instruction ou une commande particulière est un test.

shvs sh -e, revisité

Depuis la #!ligne (voir aussi cette question ) en haut de /etc/rc.locala sh -e, le système d'exploitation exécute le script comme s'il était appelé avec la commande:

sh -e /etc/rc.local

En revanche, vos autres scripts, tels que startup_script.sh, s'exécutent sans l' -eindicateur:

sh /home/incero/startup_script.sh

Par conséquent, ils continuent à fonctionner même lorsqu'une commande en eux signale un échec.

C'est normal et bon. rc.localdoit être appelé avec sh -eet la plupart des autres scripts, y compris la plupart des scripts exécutés par rc.local--should.

Assurez-vous simplement de vous souvenir de la différence:

  • Les scripts s'exécutent avec un sh -eéchec de rapport de sortie la première fois qu'une commande qu'ils contiennent ferme les échecs de rapport.

    C'est comme si le script était une longue commande composée de toutes les commandes du script jointes à des &&opérateurs.

  • Les scripts s'exécutent avec sh(sans -e) continuer à s'exécuter jusqu'à ce qu'ils aboutissent à une commande qui les termine (se termine) ou à la toute fin du script. Le succès ou l'échec de chaque commande est essentiellement sans importance (à moins que la commande suivante ne le vérifie). Le script se ferme avec le statut de sortie de la dernière commande exécutée.

Aider votre script à comprendre que ce n'est pas un tel échec après tout

Comment pouvez-vous empêcher votre script de penser qu'il a échoué?

Vous regardez ce qui se passe juste avant la fin de l'exécution.

  1. Si une commande échoue alors qu'elle aurait dû réussir, déterminez pourquoi et corrigez le problème.

  2. Si une commande échoue et que c'est la bonne chose à faire, empêchez la propagation de cet état d'échec.

    • Une façon de garder un état d'échec de se propager consiste à exécuter une autre commande qui réussit. /bin/truen'a aucun effet secondaire et signale le succès (tout comme /bin/falserien et échoue également).

    • Une autre consiste à s'assurer que le script est terminé par exit 0.

      Ce n'est pas nécessairement la même chose exit 0qu'être à la fin du script. Par exemple, il peut y avoir un ifbloc où le script se termine à l'intérieur.

Il est préférable de savoir ce qui provoque l'échec de votre script avant de lui signaler le succès. S'il échoue vraiment (en ce sens qu'il ne fait pas ce que vous voulez), vous ne voulez pas vraiment qu'il fasse état d'un succès.

Une solution rapide

Si vous ne pouvez pas assurer le startup_script.shsuccès du rapport de sortie, vous pouvez modifier la commande dans rc.locallaquelle il s'exécute, de sorte que la commande signale le succès, même si ce startup_script.shn'est pas le cas.

Actuellement vous avez:

sh /home/incero/startup_script.sh

Cette commande a les mêmes effets secondaires (c.-à-d. L'effet secondaire de l'exécution startup_script.sh), mais indique toujours le succès:

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

N'oubliez pas qu'il est préférable de savoir pourquoi un startup_script.shéchec est signalé et de le réparer.

Comment fonctionne le Quick Fix

Ceci est en fait un exemple de ||test, un ou un test.

Supposons que vous me demandiez si j'ai sorti la poubelle ou si j'ai brossé le hibou. Si je sortais les poubelles, je pourrais sincèrement dire «oui», même si je ne me souvenais pas si j'avais brossé le hibou.

La commande à gauche de ||run. Si cela réussit (vrai), alors le membre de droite n'a pas à courir. Donc, si le résultat est startup_script.shpositif, la truecommande ne sera jamais exécutée.

Cependant, si un startup_script.shéchec est signalé [je n'ai pas sorti la corbeille] , le résultat de /bin/true [si je brosse le hibou] est important.

/bin/truerenvoie toujours le succès (ou vrai , comme nous l'appelons parfois). Par conséquent, la commande entière réussit et la commande suivante rc.localpeut être exécutée.

Une note supplémentaire sur le succès / l'échec, vrai / faux, zéro / différent de zéro.

N'hésitez pas à l'ignorer. Vous voudrez peut-être lire ceci si vous programmez dans plus d'une langue (c.-à-d., Pas seulement les scripts shell).

Il est très confus pour les scripteurs shell utilisant des langages de programmation tels que C, et pour les programmeurs C utilisant les scripts shell de découvrir:

  • Dans les scripts shell:

    1. Une valeur de retour 0signifie succès et / ou vrai .
    2. Une valeur de retour autre que 0signifiant échec et / ou faux .
  • Programmation en C:

    1. Une valeur de retour des 0moyens faux ..
    2. Une valeur de retour de quelque chose d'autre que 0signifie true .
    3. Il n'y a pas de règle simple pour ce qui signifie succès et ce qui signifie échec. Parfois, cela 0signifie succès, parfois cela signifie échec, d'autres fois cela signifie que la somme des deux nombres que vous venez d'ajouter est égale à zéro. Les valeurs de retour dans la programmation générale sont utilisées pour signaler une grande variété d'informations de types différents.
    4. Le statut de sortie numérique d'un programme doit indiquer un succès ou un échec conformément aux règles applicables aux scripts shell. En d’autres termes , même si 0signifie faux dans votre programme C, vous faites 0quand même que votre programme retourne comme code de sortie si vous voulez qu’il signale qu’il a réussi (ce que le shell interprète alors comme vrai ).
Eliah Kagan
la source
3
wow super réponse! Il y a beaucoup d'informations là-dedans que je ne connaissais pas. Si vous renvoyez 0 à la fin du premier script, le reste du script est exécuté (je peux dire que chmod + x a été exécuté). Malheureusement, il semble que le script / usr / bin / wget de mon script ne récupère pas une page Web à insérer dans script.sh. Je suppose que le réseau n’est pas prêt au moment où ce script rc.local s’exécute. Où puis-je placer cette commande pour qu'elle s'exécute au démarrage du réseau et automatiquement au démarrage?
Programmeur
Je l'ai 'piraté' pour l'instant en exécutant sudo visudo, en ajoutant la ligne suivante: nom d'utilisateur ALL = (ALL) NOPASSWD: programme ALL 'en cours d'exécution' (quelle est la commande CLI pour cela?), Et en ajoutant startup_script à cela, Alors que startupscript exécute maintenant script.sh avec la commande sudo au préalable pour s'exécuter en tant que root (le mot de passe n'est plus nécessaire). Nul doute que c’est très dangereux et terrible, mais c’est juste pour un disque de distro live pxe-boot personnalisé.
Programmeur
@ Stu2000 En supposant incero(je suppose que c'est le nom d'utilisateur) de toute façon , c'est un administrateur , et il est donc autorisé à exécuter n'importe quelle commande en root(en entrant son propre mot de passe utilisateur), ce qui n'est pas super dangereux et non dangereux . Si vous souhaitez d’autres solutions, je vous recommande de poser une nouvelle question à ce sujet . L'un des problèmes était la raison pour laquelle les commandes rc.localne s'exécutaient pas (et vous avez également vérifié ce qui se passerait si vous les faisiez continuer à s'exécuter). Vous avez maintenant un problème différent: vous voulez que la machine soit connectée au réseau avant l' rc.localexécution, ou planifier l'exécution startup_script.shultérieure.
Eliah Kagan
1
Depuis, je suis revenu sur ce problème, j'ai modifié le fichier rc.local et constaté que wget ne fonctionnait pas. L'ajout /usr/bin/sudo service networking restartau script de démarrage avant la commande wget a entraîné le fonctionnement de wget.
Programmeur
3
@EliahKagan merveilleuse réponse exhaustive. J'ai à peine trouvé une meilleure documentation surrc.local
souravc
1

Vous pouvez (dans les variantes Unix que j'utilise) remplacer le comportement par défaut en modifiant:

#!/bin/sh -e

À:

#!/bin/sh

Au début du script. Le drapeau "-e" indique à sh de sortir à la première erreur. Le nom long de ce drapeau est "errexit", la ligne d'origine est donc équivalente à:

#!/bin/sh --errexit
Bryce
la source
oui, enlever des -eœuvres sur Jessie raspbian.
Oki Erie Rinaldi
Le -eest là pour une raison. Je ne ferais pas ça si j'étais toi. Mais je ne suis pas tes parents.
Wyatt8740
-4

changer la première ligne de: #!/bin/sh -e à !/bin/sh -e

ElJenso
la source
3
La syntaxe avec #!est la bonne. (Au moins, la #!partie est correcte. Et le reste fonctionne généralement bien pour, pour rc.local.) Voir cet article et cette question . Quoi qu'il en soit, bienvenue à Ask Ubuntu!
Eliah Kagan