Wenn Sie ein Skript bearbeiten, sind in der Regel alle ausgeführten Verwendungen des Skripts fehleranfällig.
Soweit ich weiß, liest bash (auch andere Shells?) Das Skript inkrementell. Wenn Sie also die Skriptdatei extern geändert haben, werden die falschen Informationen gelesen. Gibt es eine Möglichkeit, dies zu verhindern?
Beispiel:
sleep 20
echo test
Wenn Sie dieses Skript ausführen, liest bash die erste Zeile (etwa 10 Byte) und geht in den Ruhezustand. Wenn das Skript fortgesetzt wird, kann es ab dem 10. Byte unterschiedliche Inhalte im Skript geben. Ich bin möglicherweise mitten in einer Zeile im neuen Skript. Dadurch wird das laufende Skript unterbrochen.
\n
würde das Weglassen des letzten den Trick tun? Vielleicht reicht eine Unterschale()
? Ich bin nicht sehr erfahren damit, bitte helfen Sie!sleep 20 ;\n echo test ;\n sleep 20
und ich mit der Bearbeitung beginne, kann es sich schlecht verhalten . Bash könnte beispielsweise die ersten 10 Bytes des Skripts lesen, densleep
Befehl verstehen und in den Ruhezustand wechseln. Nach dem Fortsetzen befinden sich ab 10 Byte unterschiedliche Inhalte in der Datei.Antworten:
Yes-Shells
bash
lesen die Datei insbesondere zeilenweise, sodass sie genauso funktioniert, wie wenn Sie sie interaktiv verwenden.Sie werden feststellen, dass die Datei, wenn sie nicht suchbar ist (wie eine Pipe),
bash
sogar jeweils ein Byte liest, um sicherzugehen, dass das\n
Zeichen nicht überschritten wird . Wenn die Datei suchbar ist, wird sie optimiert, indem ganze Blöcke gleichzeitig gelesen werden. Suchen Sie jedoch nach dem\n
.Das heißt, Sie können Dinge tun wie:
Oder schreiben Sie Skripte, die sich selbst aktualisieren. Was Sie nicht könnten, wenn es Ihnen diese Garantie nicht geben würde.
Nun ist es selten, dass Sie solche Dinge tun möchten, und wie Sie herausgefunden haben, wird diese Funktion häufiger im Weg stehen, als es sinnvoll ist.
Um dies zu vermeiden, können Sie versuchen, sicherzustellen, dass Sie die Datei nicht direkt ändern (z. B. eine Kopie ändern und die Kopie an ihren Platz verschieben (wie dies zum Beispiel
sed -i
oderperl -pi
einige Editoren tun)).Oder Sie könnten Ihr Skript schreiben wie:
(Beachten Sie, dass es wichtig ist, dass Sie
exit
sich in der gleichen Zeile wie befinden}
. Sie können es jedoch auch in die geschweiften Klammern unmittelbar vor der schließenden setzen.)oder:
Die Shell muss das Skript erst lesen,
exit
bevor sie etwas unternimmt. Dadurch wird sichergestellt, dass die Shell nicht erneut aus dem Skript liest.Das bedeutet, dass das gesamte Skript im Speicher abgelegt wird.
Dies kann sich auch auf das Parsen des Skripts auswirken.
Zum Beispiel in
bash
:Würde das in UTF-8 codierte U + 00E9 ausgeben. Wenn Sie es jedoch ändern in:
Das
\ue9
wird in dem Zeichensatz erweitert, der zu dem Zeitpunkt gültig war, als der Befehl analysiert wurde. In diesem Fall ist dies der Fall, bevor derexport
Befehl ausgeführt wird.Beachten Sie auch, dass bei Verwendung des Befehls
source
aka.
bei einigen Shells die gleichen Probleme bei den Quelldateien auftreten.Dies ist jedoch nicht der Fall,
bash
wenn dersource
Befehl die Datei vollständig liest, bevor er sie interpretiert. Wennbash
Sie speziell dafür schreiben , können Sie tatsächlich davon Gebrauch machen, indem Sie am Anfang des Skripts Folgendes hinzufügen:(Darauf würde ich mich jedoch nicht verlassen, da Sie sich vorstellen können, dass zukünftige Versionen von
bash
dieses Verhalten ändern könnten, das derzeit als Einschränkung angesehen werden kann.) und deralready_sourced
Trick ist ein bisschen spröde, da davon ausgegangen wird, dass sich die Variable nicht in der Umgebung befindet, ganz zu schweigen davon, dass er den Inhalt der Variablen BASH_SOURCE beeinflusst.)quelle
}; exec true
. Auf diese Weise sind keine Zeilenumbrüche am Ende der Datei erforderlich, was für einige Editoren (wie z. B. Emacs) hilfreich ist. Alle Tests, die ich mir}; exec true
}; exit
? Sie verlieren auch den Exit-Status.. script
) verwendet wird.Sie müssen lediglich die Datei löschen (dh kopieren, löschen, die Kopie wieder in den ursprünglichen Namen umbenennen). Tatsächlich können viele Editoren so konfiguriert werden, dass sie dies für Sie tun. Wenn Sie eine Datei bearbeiten und einen geänderten Puffer darin speichern, wird die alte Datei nicht überschrieben, sondern umbenannt, eine neue erstellt und der neue Inhalt in die neue Datei eingefügt. Daher sollte jedes laufende Skript problemlos fortgesetzt werden können.
Wenn Sie ein einfaches Versionskontrollsystem wie RCS verwenden, das für vim und emacs verfügbar ist, haben Sie den doppelten Vorteil, dass Sie einen Verlauf Ihrer Änderungen haben. Das Checkout-System sollte standardmäßig die aktuelle Datei entfernen und sie mit den richtigen Modi neu erstellen. (Achten Sie natürlich darauf, solche Dateien nicht zu verlinken).
quelle
Einfachste Lösung:
Auf diese Weise liest bash den gesamten
{}
Block, bevor es ausgeführt wird, und dieexit
Direktive stellt sicher, dass außerhalb des Codeblocks nichts gelesen wird.Wenn Sie das Skript nicht "ausführen", sondern als "Quelle" verwenden möchten, benötigen Sie eine andere Lösung. Das sollte dann funktionieren:
Oder wenn Sie den Exit-Code direkt steuern möchten:
Voilà! Dieses Skript kann sicher bearbeitet, als Quelle verwendet und ausgeführt werden. Sie müssen immer noch sicherstellen, dass Sie es nicht in diesen Millisekunden ändern, wenn es zum ersten Mal gelesen wird.
quelle
Konzeptioneller Beweiß. Hier ist ein Skript, das sich selbst ändert:
Wir sehen die geänderte Version drucken
Dies liegt daran, dass beim Laden von Bashs ein Datei-Handle für das Skript geöffnet bleibt, sodass Änderungen an der Datei sofort angezeigt werden.
Wenn Sie die speicherinterne Kopie nicht aktualisieren möchten, heben Sie die Verknüpfung der Originaldatei auf und ersetzen Sie sie.
Eine Möglichkeit, dies zu tun, ist die Verwendung von sed -i.
konzeptioneller Beweiß
Wenn Sie zum Ändern des Skripts einen Editor verwenden, ist möglicherweise nur das Aktivieren der Funktion "Sicherungskopie aufbewahren" erforderlich, damit der Editor die geänderte Version in eine neue Datei schreibt, anstatt die vorhandene zu überschreiben.
quelle
bash
öffnet die Datei nicht mitmmap()
. Es ist nur vorsichtig, nach Bedarf eine Zeile nach der anderen zu lesen, genau wie wenn die Befehle von einem Endgerät abgerufen werden, wenn es interaktiv ist.Das Einschließen Ihres Skripts in einen Block
{}
ist wahrscheinlich die beste Option, erfordert jedoch das Ändern Ihrer Skripts.wäre die zweitbeste Option (vorausgesetzt, tmpfs ) der Nachteil ist, dass sie $ 0 kaputt macht, wenn Ihre Skripte dies verwenden.
Die Verwendung von so etwas
F=test.sh; tail -n $(cat "$F" | wc -l) "$F" | bash
ist weniger ideal, da die gesamte Datei im Speicher bleiben muss und $ 0 kaputt geht.Das Berühren der Originaldatei sollte vermieden werden, damit die letzte Änderung, Lesesperren und feste Links nicht gestört werden. Auf diese Weise können Sie einen Editor geöffnet lassen, während die Datei ausgeführt wird, und rsync prüft nicht unnötigerweise, ob die Datei wie erwartet auf Sicherungen und feste Verknüpfungen überprüft wird.
Das Ersetzen der Datei beim Bearbeiten würde funktionieren, ist jedoch weniger robust, da sie für andere Skripte / Benutzer / nicht erzwingbar ist, oder man könnte es vergessen. Und wieder würde es harte Verbindungen brechen.
quelle
tac test.sh | tac | bash