web-dev-qa-db-fra.com

Pourquoi rc.local ne lance-t-il pas toutes mes commandes et que puis-je faire à ce sujet?

J'ai le script rc.local 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.sh, télécharge en fait le fichier script.sh dans /script.sh auquel 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 manuelle du fichier rc.local après le démarrage fonctionne parfaitement. Chmod ne peut pas être exécuté au démarrage ou quelque chose?

35
Programster

Vous pouvez sauter jusqu'à une solution rapide , 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.local ne 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 -e, provoque son exécution dans un shell appelé avec l'indicateur -e. L’indicateur -e permet à un script (dans ce cas, rc.local) de s’exécuter lors de la première échec d’une commande.

Vous voulez que rc.local se comporte comme ceci. Si une commande échoue, vous ne voulez pas continuer avec toutes les autres commandes de démarrage susceptibles de s’appuyer sur son succès.

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

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

Non.

chmod fonctionne correctement à tout moment. Si le système de fichiers qui contient /bin a été monté, vous pouvez exécuter /bin/chmod. Et /bin est monté avant que rc.local ne soit exécuté.

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

Au fait, sh -e est la seulement des raisons pour lesquelles il y aurait un problème si chmod échouait. Lorsque vous exécutez un fichier de script en appelant explicitement son interpréteur, le fichier est considéré comme exécutable. Le bit exécutable du fichier importera seulement si /script.sh est indiqué. Puisqu'il indique sh /script.sh, ce n'est pas le cas (à moins que bien sûr /script.sh s'appelle pendant son exécution, ce qui pourrait échouer s'il n'est pas exécutable, mais il est peu probable qu'il s'appelle lui-même).

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

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

Nous savons que cela a fonctionné, car il a téléchargé /script.sh.

(Sinon, il serait important de s'assurer qu'il fonctionne correctement, au cas où /bin n'était pas dans PATH --rc.local n'a pas nécessairement le même PATH que lorsque vous êtes connecté. in. Si /bin ne figure pas dans le chemin de rc.local, sh doit être exécuté en tant que /bin/sh. Puisqu'il a été exécuté, /bin est dans PATH, ce qui signifie que vous pouvez exécuter d'autres commandes situées dans /bin sans qualifier leurs noms. Par exemple, vous pouvez exécuter uniquement chmod plutôt que /bin/chmod. Cependant, conformément à votre style dans rc.local, j’ai utilisé des noms complets pour toutes les commandes sauf sh, chaque fois que je vous suggère de les exécuter.)

Nous pouvons être assez certains que /bin/chmod +x /script.sh n'a jamais été exécuté (ou vous verriez que /script.sh a été exécuté). Et nous savons que sh /script.sh n'a pas été exécuté non plus.

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

Deux sens du succès

Une personne peut vouloir dire différentes choses 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, cessera de s'exécuter la première fois qu'une commande signale son échec . Ce que la commande a réellement fait ne change rien.

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

  • Certains bogues empêchent un script de faire ce que vous voulez. Ils affectent ce que les programmeurs appellent 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 valeur de retour (ce qui dans ce cas est un état de sortie ).

Il est fort probable que startup_script.sh a fait tout ce qu'il devrait, sauf que a signalé l'échec.

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 (success) si le script était vide (c'est-à-dire sans commande).
  • N, si le script s'est terminé à la suite de la commande exit N, où N est un code de sortie.
  • 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 fonction main(), le code 0 est attribué au système d'exploitation, qui lui fournit un processus appelant (qui peut être, par exemple, le Shell à partir duquel le programme a été exécuté. ).

0 signifie 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, vous pouvez penser que son échec est un succès, même si le programme ne signale pas un échec. Par exemple, vous pouvez utiliser rm sur un fichier dont vous pensez qu'il n'existe pas déjà, simplement pour vous assurer qu'il est supprimé.

Quelque chose comme cela se passe probablement dans startup_script.sh, juste avant que le logiciel cesse de fonctionner. La dernière commande à exécuter dans le script signale probablement un échec (même si son "échec" peut être totalement correct, voire nécessaire), ce qui entraîne l'échec du rapport de script.

Tests voués à l'échec

Un type spécial de commande est un test , ce qui signifie une commande exécutée pour sa valeur de retour plutôt que 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 le succès si c'est le cas, et l'échec si ce n'est pas le cas.

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

Même si l'instruction echo ne s'exécute jamais, le bloc if 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 opérateur booléen et . Une expression &&, qui consiste en && avec des instructions de chaque côté, renvoie false (échec) à moins que les deux côtés ne renvoient true (succès). Tout 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). L'instruction à droite de && n'est jamais exécutée.

Ici, [ 4 -eq 5 ] s'exécute. Cela "échoue" (retourne faux). Donc, l'expression && entière échoue. echo "Yeah, they're totally the same." ne s'exécute jamais. Tout se comporte comme il se doit, mais cette commande signale un échec (même si la condition if équivalente, par ailleurs, indique le 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 moment antérieur), 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.

sh contre sh -e, revisité

Depuis la ligne #! (voir aussi cette question ) en haut de /etc/rc.local a sh -e, le système d'exploitation exécute le script comme s'il avait été appelé à l'aide de la commande:

sh -e /etc/rc.local

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

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.local devrait être appelé avec sh -e et la plupart des autres scripts, y compris la plupart des scripts exécutés par rc.local-- ne le devraient pas.

Assurez-vous juste de vous rappeler la différence:

  • Les scripts sont exécutés avec sh -e en échec de rapport de sortie la première fois qu’une commande qu’ils contiennent ferme en échec de rapport.

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

  • Les scripts exécutés avec sh (sans -e) continuent à s'exécuter jusqu'à arriver à une commande qui les termine (se termine) ou jusqu'à 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/true n'a pas d'effets secondaires et enregistre un succès (tout comme /bin/false ne fait rien et échoue également).

    • Une autre consiste à vérifier que le script est terminé par exit 0.

      Ce n'est pas nécessairement la même chose que exit 0 étant à la fin du script. Par exemple, il peut y avoir un bloc if- 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. Si cela échoue vraiment (en ce sens qu'il ne fait pas ce que vous voulez qu'il fasse), vous ne voulez pas vraiment qu'il fasse état d'une réussite.

Une solution rapide

Si vous ne parvenez pas à startup_script.sh quitter la création de rapports avec succès, vous pouvez modifier la commande dans rc.local qui l'exécute, de sorte que la commande enregistre un succès même si startup_script.sh ne l'a pas été.

Actuellement vous avez:

sh /home/incero/startup_script.sh

Cette commande a les mêmes effets secondaires (c'est-à-dire l'effet secondaire de l'exécution de 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 startup_script.sh signale un échec et de le réparer.

Comment fonctionne le Quick Fix

Il s’agit en fait d’un exemple de test ||, d’un test ou .

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 || est exécutée. Si cela réussit (vrai), alors le membre de droite n'a pas à courir. Ainsi, si startup_script.sh signale un succès, la commande true ne s'exécute jamais.

Cependant, si startup_script.sh signale un échec [Je n’ai pas sorti la corbeille] , le résultat de /bin/true [si j’ai brossé le hibou] est important.

/bin/true renvoie toujours le succès (ou true , comme on l'appelle parfois). Par conséquent, la commande entière réussit et la commande suivante dans rc.local peut ê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'est-à-dire, pas seulement les scripts Shell).

Il est très confus pour les auteurs de scripts 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 de 0 signifie success et/ou true .
    2. Une valeur de retour autre que 0 signifie échec et/ou faux .
  • Programmation en C:

    1. Une valeur de retour de 0 signifie false ..
    2. Une valeur de retour autre que 0 signifie true .
    3. Il n'y a pas de règle simple pour ce qui signifie succès et ce qui signifie échec. Parfois, 0 signifie succès, parfois cela signifie échec, parfois 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.
    4. L'état de sortie numérique d'un programme doit indiquer un succès ou un échec conformément aux règles applicables aux scripts Shell. C'est-à-dire que même si 0 signifie false dans votre programme C, vous faites quand même que votre programme retourne 0 comme code de sortie si vous voulez qu'il le signale comme réussi (ce que le shell interprète alors comme ) true ).
70
Eliah Kagan

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
1
Bryce