Bash lädt beim Speichern automatisch Updates in ein laufendes Skript (fügt sie ein): Warum? Irgendeine praktische Verwendung?

9

Ich habe ein Bash-Skript geschrieben und zufällig den Code aktualisiert (die Skriptdatei auf der Festplatte gespeichert), während das Skript auf eine Eingabe in einer whileSchleife wartete . Nachdem ich zum Terminal zurückgekehrt war und mit dem vorherigen Aufruf des Skripts fortgefahren war, gab bash einen Fehler bezüglich der Dateisyntax aus:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

Also habe ich Folgendes versucht:

1. Erstellen Sie ein Skript, nennen self-update.shwir es:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

Das Skript liest seinen Code, ändert das Wort "VOR" in "NACH" und schreibt sich dann mit dem neuen Code neu.

2. Führen Sie es aus:

chmod +x self-update.sh
./self-update.sh

3. Wunder ...

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

Nun, ich hätte nicht gedacht, dass bei demselben Aufruf NACH! sicher beim zweiten Lauf, aber nicht beim ersten.

Meine Frage ist also: Ist es beabsichtigt (beabsichtigt)? oder liegt es an der Art und Weise, wie bash das Skript ausführt? Zeile für Zeile oder Befehl für Befehl. Gibt es eine gute Verwendung eines solchen Verhaltens? Ein Beispiel dafür?


Bearbeiten: Ich habe versucht, die Datei neu zu formatieren, um alle Befehle in eine Zeile zu setzen. Es funktioniert jetzt nicht:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

Ausgabe:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

Wenn Sie die echoZeichenfolge in die nächste Zeile verschieben, trennen Sie sie vom cpAufruf von rewriting ( ):

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

Und jetzt funktioniert es wieder:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.
Aularon
quelle
1
Siehe auch
Martin von Wittich
1
Ich habe die Quelle durchgesehen, kann aber keine Erklärung dafür finden. Ich bin mir jedoch ziemlich sicher, dass ich einige selbstextrahierende Installationsprogramme gesehen habe, die vor einigen Jahren als Shell-Skripte implementiert wurden. Diese Skripte enthalten einige Zeilen ausführbarer Shell-Befehle und dann einen riesigen Datenblock, der von diesen Befehlen gelesen wird. Ich gehe also davon aus, dass dies so konzipiert ist, dass bash nicht das gesamte Skript in den Speicher lesen muss, was mit riesigen selbstextrahierenden Skripten nicht funktionieren würde.
Martin von Wittich
Hier ist ein Beispiel für ein SFX-Skript: ftp.games.skynet.be/pub/wolfenstein/…
Martin von Wittich
Nett! Ich erinnere mich, dass ich diese selbstinstallierenden Skripte gesehen habe. Der AMD Catalyst-Treiber (proprietär) wird immer noch so ausgeliefert. Er extrahiert sich selbst und wird dann installiert. Diese hängen von einem solchen Verhalten beim Lesen der Dateien in Blöcken ab. Danke für das Beispiel!
Aularon

Antworten:

12

Dies ist beabsichtigt. Bash liest Skripte in Stücken. Es liest also einen Teil des Skripts, führt alle möglichen Zeilen aus und liest dann den nächsten Block.

Sie stoßen also auf so etwas:

  • Bash liest die ersten 256 Bytes (Bytes 0-255) des Skripts.
  • Innerhalb dieser ersten 256 Bytes befindet sich ein Befehl, dessen Ausführung eine Weile dauert, und bash startet diesen Befehl und wartet darauf, dass er beendet wird.
  • Während der Befehl ausgeführt wird, wird das Skript aktualisiert und der geänderte Teil wird nach den bereits gelesenen 256 Bytes angezeigt.
  • Wenn die Befehls-Bash ausgeführt wurde, wird die Datei weiter gelesen, von ihrem Standort aus fortgesetzt und die Bytes 256-511 abgerufen.
  • Dieser Teil des Skripts hat sich geändert, aber bash weiß das nicht.

Noch problematischer wird dies, wenn Sie etwas vor Byte 256 bearbeiten. Nehmen wir an, Sie löschen ein paar Zeilen. Dann befinden sich die Daten im Skript, die sich bei Byte 256 befanden, jetzt an einer anderen Stelle, beispielsweise bei Byte 156 (100 Bytes früher). Wenn bash weiter liest, wird es aus diesem Grund das bekommen, was ursprünglich 356 war.

Dies ist nur ein Beispiel. Bash liest nicht unbedingt 256 Bytes gleichzeitig. Ich weiß nicht genau, wie viel es gleichzeitig liest, aber es spielt keine Rolle, das Verhalten ist immer noch dasselbe.

Patrick
quelle
Nein, es wird nach Blöcken gelesen, aber zurückgespult, um sicherzugehen, dass der nächste Befehl so gelesen wird, wie er ist, nachdem der vorherige Befehl zurückgegeben wurde.
Stéphane Chazelas
@StephaneChazelas Woher bekommst du das? Ich habe gerade eine Strace gemacht und es geht nicht einmal um statdie Datei, um zu sehen, ob sie geändert wurde. Keine lseekAnrufe.
Patrick
Siehe pastie.org/8662761 für die relevanten Teile der Strace-Ausgabe. Sehen Sie, wie die echo fooauf eine veränderte echo barwährend der sleep. Es hat sich schon seit Version 2 so verhalten, daher denke ich nicht, dass es sich um ein Versionsproblem handelt.
Stéphane Chazelas
Anscheinend liest es die Datei Zeile für Zeile, ich habe es mit der Datei versucht und das, was ich herausgefunden habe. Ich werde meine Frage bearbeiten, um dieses Verhalten hervorzuheben.
Aularon
@Patrick Wenn Sie Ihre Antwort aktualisieren können, um sie wiederzugeben, wird sie Zeile für Zeile gelesen, sodass ich Ihre Antwort akzeptieren kann. (Überprüfen Sie meine Bearbeitung der Frage zu diesem Thema).
Aularon