Warum führt rc.local nicht alle meine Befehle aus und was kann ich dagegen tun?


35

Ich habe das folgende rc.localSkript:

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

In der ersten Zeile wird startup_script.shdie script.shDatei heruntergeladen , /script.shauf die in der dritten Zeile verwiesen wird.

Leider scheint es, dass das Skript nicht ausführbar ist oder nicht ausgeführt wird. Das rc.localmanuelle Ausführen der Datei nach dem Start funktioniert einwandfrei. Kann chmod nicht beim Start ausgeführt werden oder so?

Antworten:


70

Sie können bis hinunter zu A Quick Fix springen, dies ist jedoch nicht unbedingt die beste Option. Deshalb empfehle ich, dies alles zuerst zu lesen.

rc.local toleriert keine Fehler.

rc.localbietet keine Möglichkeit, Fehler intelligent zu beheben. Wenn ein Befehl fehlschlägt, wird er nicht mehr ausgeführt. Die erste Zeile #!/bin/sh -ebewirkt, dass sie in einer Shell ausgeführt wird, die mit dem -eFlag aufgerufen wird . Das -eFlag bewirkt, dass ein Skript (in diesem Fall rc.local) beim ersten Fehlschlagen eines Befehls nicht mehr ausgeführt wird.

Du willst rc.localdich so verhalten. Wenn ein Befehl fehlschlägt, möchten Sie nicht, dass er mit allen anderen Startbefehlen fortgesetzt wird, die möglicherweise darauf beruhen, dass der Befehl erfolgreich ausgeführt wurde.

Wenn also ein Befehl fehlschlägt, werden die nachfolgenden Befehle nicht ausgeführt. Das Problem hier ist, dass /script.shes nicht ausgeführt wurde (nicht, dass es fehlgeschlagen ist, siehe unten), also höchstwahrscheinlich ein Befehl, bevor es fehlgeschlagen ist. Aber welcher?

War es /bin/chmod +x /script.sh?

Nein.

chmodläuft jederzeit einwandfrei. Vorausgesetzt, das Dateisystem, das es enthält, /binwurde bereitgestellt, können Sie ausführen /bin/chmod. Und /binwird vor rc.localLäufen montiert .

Wenn es als root ausgeführt wird, /bin/chmodschlägt es selten fehl. Es schlägt fehl, wenn die Datei, auf der es ausgeführt wird, schreibgeschützt ist, und schlägt möglicherweise fehl, wenn das Dateisystem, auf dem es ausgeführt wird, keine Berechtigungen unterstützt. Beides ist hier nicht wahrscheinlich.

Übrigens sh -eist das der einzige Grund, warum es ein Problem wäre, wenn es chmodfehlschlägt. Wenn Sie eine Skriptdatei ausführen, indem Sie ihren Interpreter explizit aufrufen, spielt es keine Rolle, ob die Datei als ausführbar markiert ist. Nur wenn es heißt, /script.shwürde das ausführbare Bit der Datei von Bedeutung sein. Da heißt sh /script.shes nicht, es sei denn, es /script.sh ruft sich selbst auf, während es ausgeführt wird, was daran scheitern könnte, dass es nicht ausführbar ist, aber es ist unwahrscheinlich, dass es sich selbst aufruft.

Was ist also gescheitert?

sh /home/incero/startup_script.shgescheitert. Fast sicher.

Wir wissen, dass es lief, weil es heruntergeladen wurde /script.sh.

(Sonst wäre es wichtig, sicherzustellen, dass es lief, irgendwie in Fall /binwar nicht in PATH - rc.localnicht unbedingt das gleiche PATH. Wie Sie haben , wenn Sie angemeldet sind Wenn /binwar nicht rc.local‚s Weg, diese würde erfordern , shwie ausgeführt werden /bin/sh. da es lief, /binin ist PATH, was bedeutet , dass Sie andere Befehle ausführen können , die sich befinden /bin, ohne ihre Namen voll qualifizierenden. Sie können beispielsweise nur ausführen können , chmodstatt /bin/chmod. Doch mit Stil im Einklang in rc.local, habe ich voll qualifizierte Namen für alle Befehle mit Ausnahme verwendet sh, wann immer ich vorschlage Sie sie ausführen.)

Wir können uns ziemlich sicher sein, dass es /bin/chmod +x /script.shnie gelaufen ist (oder Sie würden sehen, dass /script.shdas ausgeführt wurde). Und wir wissen, dass sh /script.shes auch nicht gelaufen ist.

Aber es wurde heruntergeladen /script.sh. Es ist gelungen! Wie konnte es scheitern?

Zwei Bedeutungen des Erfolgs

Es gibt zwei verschiedene Dinge, die eine Person bedeuten könnte, wenn sie sagt, dass ein Befehl erfolgreich war:

  1. Es hat getan, was Sie wollten.
  2. Es wurde berichtet, dass es gelungen ist.

Und so ist es zum Scheitern verurteilt. Wenn eine Person sagt, ein Befehl sei fehlgeschlagen, könnte dies bedeuten:

  1. Es hat nicht getan, was Sie wollten.
  2. Es hat berichtet, dass es fehlgeschlagen ist.

Ein Skript, das mit ausgeführt sh -ewird rc.local, wird angehalten, wenn ein Befehl zum ersten Mal meldet, dass es fehlgeschlagen ist . Es macht keinen Unterschied, was der Befehl tatsächlich tat.

Wenn Sie nicht beabsichtigen startup_script.sh, einen Fehler zu melden, wenn er das tut, was Sie wollen, ist dies ein Fehler in startup_script.sh.

  • Einige Fehler verhindern, dass ein Skript das tut, was Sie möchten. Sie beeinflussen, was Programmierer ihre Nebenwirkungen nennen .
  • Und einige Fehler verhindern, dass ein Skript korrekt meldet, ob es erfolgreich war oder nicht. Sie beeinflussen, was Programmierer als Rückgabewert bezeichnen (was in diesem Fall ein Exit-Status ist ).

Es ist sehr wahrscheinlich, dass startup_script.shes alles getan hat, außer dass gemeldet wurde, dass es fehlgeschlagen ist.

Wie Erfolg oder Misserfolg gemeldet wird

Ein Skript ist eine Liste von null oder mehr Befehlen. Jeder Befehl hat einen Beendigungsstatus. Angenommen, es gibt keine Fehler bei der tatsächlichen Ausführung des Skripts (wenn der Interpreter beispielsweise die nächste Zeile des Skripts während der Ausführung nicht lesen konnte), lautet der Beendigungsstatus eines Skripts wie folgt:

  • 0 (Erfolg) wenn das Skript leer war (dh keine Befehle hatte).
  • NWenn das Skript aufgrund des Befehls beendet wurde , finden Sie hier einen Exit-Code.exit NN
  • Der Exit-Code des letzten Befehls, der ansonsten im Skript ausgeführt wurde.

Wenn eine ausführbare Datei ausgeführt wird, meldet sie ihren eigenen Exit-Code - nicht nur für Skripte. (Und technisch gesehen sind Exit-Codes aus Skripten die Exit-Codes, die von den Shells zurückgegeben werden, die sie ausführen.)

Wenn beispielsweise ein C-Programm mit exit(0);oder return 0;in seiner main()Funktion endet , wird der Code 0dem Betriebssystem übergeben, das ihn für den aufrufenden Prozess bereitstellt (bei dem es sich beispielsweise um die Shell handeln kann, von der aus das Programm ausgeführt wurde).

0Bedeutet, dass das Programm erfolgreich war. Jede andere Zahl bedeutet, dass es fehlgeschlagen ist. (Auf diese Weise können sich unterschiedliche Zahlen manchmal auf unterschiedliche Gründe beziehen, warum das Programm fehlgeschlagen ist.)

Befehle, die fehlschlagen sollen

Manchmal führen Sie ein Programm mit der Absicht aus, dass es fehlschlägt. In diesen Situationen können Sie den Fehler als Erfolg ansehen, auch wenn es kein Fehler ist, den das Programm als Fehler meldet. Sie können beispielsweise rmeine Datei verwenden, von der Sie vermuten, dass sie noch nicht vorhanden ist, um sicherzustellen, dass sie gelöscht wird.

So etwas passiert wahrscheinlich in startup_script.sh, kurz bevor es aufhört zu laufen. Der letzte Befehl, der im Skript ausgeführt wird, meldet wahrscheinlich einen Fehler (auch wenn sein "Fehler" völlig in Ordnung oder sogar notwendig ist), wodurch das Skript einen Fehler meldet.

Tests scheitern

Eine spezielle Art von Befehl ist ein Test , mit dem ich einen Befehl meine, der eher auf seinen Rückgabewert als auf seine Nebenwirkungen hin ausgeführt wird. Das heißt, ein Test ist ein Befehl, der ausgeführt wird, damit sein Beendigungsstatus überprüft werden kann (und auf den reagiert werden kann).

Angenommen, ich vergesse, wenn 4 gleich 5 ist. Glücklicherweise kenne ich mich mit Shell-Skripten aus:

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

Hier [ -eq 5 ]schlägt der Test fehl, weil er immerhin 4 ≠ 5 ergibt. Das bedeutet nicht, dass der Test nicht korrekt durchgeführt wurde. es tat. Es war Aufgabe zu überprüfen, ob 4 = 5, dann Erfolg zu melden, und Fehler, wenn nicht.

Sie sehen, in Shell-Skripten kann Erfolg auch wahr und Misserfolg auch falsch bedeuten .

Obwohl die echoAnweisung niemals ausgeführt wird, gibt der ifgesamte Block Erfolg zurück.

Angenommen, ich hätte es kürzer geschrieben:

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

Dies ist eine übliche Abkürzung. &&ist ein boolean und Operator. Ein &&Ausdruck, der aus &&Anweisungen auf beiden Seiten besteht, gibt false (Fehler) zurück, es sei denn, beide Seiten geben true (Erfolg) zurück. Genau wie ein normaler und .

Wenn dich jemand fragt: "Ist Derek ins Einkaufszentrum gegangen und hat an einen Schmetterling gedacht?" und du weißt, Derek ist nicht ins Einkaufszentrum gegangen, du musst dir keine Gedanken machen, ob er an einen Schmetterling denkt.

Wenn der Befehl links von &&fehlschlägt (false), &&schlägt der gesamte Ausdruck sofort fehl (false). Die Anweisung auf der rechten Seite von &&wird niemals ausgeführt.

Hier [ 4 -eq 5 ]läuft. Es "schlägt fehl" (Rückgabe falsch). Der ganze &&Ausdruck versagt also. echo "Yeah, they're totally the same."läuft nie. Alles hat sich so verhalten, wie es sein sollte, aber dieser Befehl meldet einen Fehler (obwohl die ansonsten äquivalente ifBedingung oben einen Erfolg meldet).

Wenn dies die letzte Anweisung in einem Skript wäre (und das Skript darauf zugreifen würde, anstatt irgendwann davor zu enden), würde das gesamte Skript einen Fehler melden .

Daneben gibt es viele Tests. Beispielsweise gibt es Tests mit ||("oder"). Das obige Beispiel sollte jedoch ausreichen, um zu erklären, was Tests sind, und es Ihnen zu ermöglichen, mithilfe der Dokumentation effektiv festzustellen, ob eine bestimmte Anweisung / ein bestimmter Befehl ein Test ist.

shvs. sh -eRevisited

Da die #!Zeile (siehe auch diese Frage ) oben /etc/rc.localhat sh -e, führt das Betriebssystem das Skript so aus, als ob es mit dem Befehl aufgerufen worden wäre:

sh -e /etc/rc.local

Im Gegensatz dazu Ihre andere Skripten, wie startup_script.shlaufen , ohne die -eFlagge:

sh /home/incero/startup_script.sh

Folglich werden sie auch dann ausgeführt, wenn ein Befehl einen Fehler meldet.

Das ist normal und gut. rc.localsollte mit aufgerufen werden sh -eund die meisten anderen Skripte - einschließlich der meisten von - rc.localsollten nicht ausgeführt werden.

Denken Sie daran, den Unterschied zu beachten:

  • Skripts werden mit einem sh -eFehler beim Beenden der Berichterstellung ausgeführt, wenn ein Befehl, den sie enthalten, zum ersten Mal einen Fehler beim Beenden der Berichterstellung verursacht.

    Es ist, als ob das Skript ein einzelner langer Befehl wäre, der aus allen Befehlen im Skript besteht, die mit &&Operatoren verknüpft sind.

  • Skripte, die mit sh(ohne -e) ausgeführt werden, werden solange ausgeführt, bis sie zu einem Befehl gelangen, der sie beendet (beendet), oder bis zum Ende des Skripts. Der Erfolg oder Misserfolg jedes Befehls ist im Wesentlichen irrelevant (es sei denn, der nächste Befehl überprüft ihn). Das Skript wird mit dem Beendigungsstatus der letzten Befehlsausführung beendet.

Helfen Sie Ihrem Skript zu verstehen, dass es doch kein solcher Fehler ist

Wie können Sie verhindern, dass Ihr Skript als fehlgeschlagen eingestuft wird, wenn dies nicht der Fall ist?

Sie sehen, was passiert, bevor es fertig ist.

  1. Wenn ein Befehl fehlgeschlagen ist, obwohl er erfolgreich sein sollte, finden Sie heraus, warum und beheben Sie das Problem.

  2. Wenn ein Befehl fehlgeschlagen ist und dies richtig war, verhindern Sie, dass sich dieser Fehlerstatus ausbreitet.

    • Eine Möglichkeit, die Weitergabe eines Fehlerstatus zu verhindern, besteht darin, einen anderen erfolgreichen Befehl auszuführen. /bin/truehat keine Nebenwirkungen und meldet Erfolg (genau wie /bin/falsenichts und auch scheitert).

    • Zum anderen muss sichergestellt werden, dass das Skript durch beendet wird exit 0.

      Das ist nicht unbedingt dasselbe wie exit 0am Ende des Skripts. Beispielsweise könnte es einen if-block geben, in dem das Skript beendet wird.

Es ist am besten zu wissen, was Ihr Skript dazu veranlasst, einen Fehler zu melden, bevor der Erfolg gemeldet wird. Wenn es auf irgendeine Weise wirklich versagt (in dem Sinne, dass Sie nicht das tun, was Sie wollen), möchten Sie nicht, dass es wirklich über den Erfolg berichtet.

Eine schnelle Lösung

Wenn Sie nicht machen können startup_script.shAusgang Bericht Erfolg, können Sie den Befehl in ändern , rc.localdass es läuft, so dass der Befehl Berichte Erfolg , auch wenn dies startup_script.shnicht tat.

Derzeit haben Sie:

sh /home/incero/startup_script.sh

Dieser Befehl hat die gleichen Nebenwirkungen (dh die Nebenwirkung des Laufens startup_script.sh), aber immer meldet Erfolg:

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

Denken Sie daran, es ist besser , zu wissen , warum startup_script.shmeldet eine Störung, und beheben.

So funktioniert die Schnellkorrektur

Dies ist eigentlich ein Beispiel für einen ||Test, einen oder einen Test.

Angenommen, Sie fragen, ob ich den Müll herausgenommen oder die Eule gebürstet habe. Wenn ich den Müll nahm, kann ich ehrlich sagen „Ja“, auch wenn ich mich nicht erinnern, ob ich die Eule gebürstet.

Der Befehl links von wird ausgeführt ||. Wenn es erfolgreich ist (true), muss die rechte Seite nicht ausgeführt werden. Wenn also ein startup_script.shErfolg gemeldet wird, wird der trueBefehl nie ausgeführt.

Wenn jedoch ein startup_script.shFehler gemeldet wird [ich habe den Müll nicht herausgenommen] , ist das Ergebnis von /bin/true [wenn ich die Eule gebürstet habe] von Bedeutung .

/bin/trueGibt immer Erfolg zurück (oder wahr , wie wir es manchmal nennen). Folglich ist der gesamte Befehl erfolgreich, und der nächste Befehl rc.localkann ausgeführt werden.

Eine zusätzliche Anmerkung zu Erfolg / Misserfolg, wahr / falsch, null / ungleich null.

Fühlen Sie sich frei, dies zu ignorieren. Möglicherweise möchten Sie dies lesen, wenn Sie in mehr als einer Sprache programmieren (dh nicht nur mit Shell-Skripten).

Es ist ein Punkt großer Verwirrung für Shell-Skripter, die sich mit Programmiersprachen wie C befassen, und für C-Programmierer, die sich mit Shell-Skripten befassen, zu entdecken:

  • In Shell Scripting:

    1. Ein Rückgabewert von 0bedeutet Erfolg und / oder wahr .
    2. Ein Rückgabewert von etwas anderem als 0bedeutet Fehler und / oder Falsch .
  • In der C-Programmierung:

    1. Ein Rückgabewert von 0bedeutet false .
    2. Ein Rückgabewert von etwas anderem als 0Mittel wahr .
    3. Es gibt keine einfache Regel, was Erfolg und was Misserfolg bedeutet. Manchmal 0bedeutet Erfolg, ein anderes Mal bedeutet es Misserfolg, ein anderes Mal bedeutet es, dass die Summe der beiden soeben hinzugefügten Zahlen Null ist. Rückgabewerte in der allgemeinen Programmierung werden verwendet, um eine Vielzahl verschiedener Arten von Informationen zu signalisieren.
    4. Der numerische Exit-Status eines Programms sollte gemäß den Regeln für Shell-Skripte Erfolg oder Misserfolg anzeigen. Das heißt, obwohl 0dies in Ihrem C-Programm false bedeutet, geben Sie Ihr Programm trotzdem 0als Exit-Code zurück, wenn Sie möchten, dass es erfolgreich war (was die Shell dann als true interpretiert ).

3
Wow tolle Antwort! Es gibt viele Informationen, die ich nicht kannte. Wenn Sie am Ende des ersten Skripts 0 zurückgeben, wird der Rest des Skripts ausgeführt (ich kann also sagen, dass chmod + x ausgeführt wurde). Leider sieht es so aus, als ob das / usr / bin / wget in meinem Skript keine Webseite zum Einfügen in script.sh abrufen kann. Ich vermute, dass das Netzwerk zum Zeitpunkt der Ausführung dieses rc.local-Skripts noch nicht bereit ist. Wo kann ich diesen Befehl ablegen, damit er ausgeführt wird, wenn das Netzwerk gestartet wurde und automatisch beim Start?
Programster

Ich habe es vorerst gehackt, indem ich sudo visudo ausgeführt habe und die folgende Zeile hinzugefügt habe: Benutzername ALL = (ALL) NOPASSWD: ALL, das das Programm 'startup applications' ausführt (was ist der CLI-Befehl dafür?), Und dazu startup_script hinzugefügt habe, Während startupscript jetzt script.sh mit dem Befehl sudo ausführt, um es als root auszuführen (benötigt kein Passwort mehr). Kein Zweifel, das ist super unsicher und schrecklich, aber das ist nur für eine benutzerdefinierte Pxe-Boot-Live-Distribution.
Programster

@ Stu2000 Vorausgesetzt, incero(ich nehme an, das ist der Benutzername) ist sowieso ein Administrator und darf daher jeden Befehl ausführen, der root(durch Eingabe seines eigenen Benutzerpassworts) nicht besonders unsicher und schrecklich ist . Wenn Sie andere Lösungen wünschen, empfehle ich, eine neue Frage zu diesem Thema zu stellen . Ein Problem war, warum die Befehle in rc.localnicht ausgeführt wurden (und Sie haben auch überprüft, was passieren würde, wenn Sie sie weiterhin ausführen lassen). Jetzt haben Sie ein anderes Problem: Sie möchten, dass die Maschine vor rc.localdem Ausführen mit dem Netzwerk verbunden wird , oder Sie möchten die Ausführung startup_script.shspäter planen .
Eliah Kagan

1
Ich bin seitdem auf diese Ausgabe zurückgekommen, habe die rc.local bearbeitet und festgestellt, dass wget nicht "funktioniert". Das Hinzufügen /usr/bin/sudo service networking restartzum Startskript vor dem Befehl wget führte dazu, dass wget dann funktionierte.
Programster

3
@EliahKagan wunderbare erschöpfende Antwort. Ich bin amrc.local
souravc 26.02.14

1

Sie können (in von mir verwendeten Unix-Varianten) das Standardverhalten überschreiben, indem Sie Folgendes ändern:

#!/bin/sh -e

Zu:

#!/bin/sh

Zu Beginn des Skripts. Das Flag "-e" weist sh an, beim ersten Fehler zu beenden. Der lange Name dieser Flagge lautet "errexit", daher entspricht die ursprüngliche Zeile:

#!/bin/sh --errexit

ja, entfernen -earbeitet an raspbian jessie.
Oki Erie Rinaldi

Das -egibt es nicht ohne Grund. Ich würde das nicht tun, wenn ich du wäre. Aber ich bin nicht deine Eltern.
Wyatt8740

-4

Ändern Sie die erste Zeile von: #!/bin/sh -e nach !/bin/sh -e


3
Die Syntax mit #!ist die richtige. (Zumindest ist das #!Teil korrekt. Und der Rest funktioniert normalerweise gut, z rc.local. B.) Siehe diesen Artikel und diese Frage . Wie auch immer, willkommen bei Ask Ubuntu!
Eliah Kagan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.