Ersetzen eines Shell-Skripts während der Ausführung

Ersetzen eines Shell-Skripts während der Ausführung

Ich habe eine Platine, auf der ein „Patch“-Skript ausgeführt wird. Das Patch-Skript läuft immer im Hintergrund und ist ein Shell-Skript, das den folgenden Pseudocode ausführt:

while true; do
    # checks if a patch tar file exists and if yes then do patching
    sleep 10
done

Dieses Skript befindet sich unter /opt/patch.sh und wurde durch das SystemV-Init-Skript gestartet.

Das Problem ist, dass das Skript, wenn es das Tar findet, es extrahiert und darin ein Shell-Skript namenspatch.shwas spezifisch für den Inhalt des Teers ist.

Wenn das Skript bei/opt/patch.shWenn das Tar gefunden wird, geschieht Folgendes:

tar -xf /opt/update.tar -C /mnt/update
mv /mnt/update/patch.sh /opt/patch.sh
exec /opt/patch.sh

Es ersetzt sich selbst durch das andere Skript und führt es vom selben Ort aus aus. Können dabei Probleme auftreten?

Antwort1

Wenn die Datei ersetzt wird, indem sie an Ort und Stelle überschrieben wird (Inode bleibt gleich), würden alle Prozesse, die sie geöffnet haben, die neuen Daten sehen, wenn sie aus der Datei lesen. Wenn sie ersetzt wird, indem die Verknüpfung der alten Datei aufgehoben und eine neue mit demselben Namen erstellt wird, ändert sich die Inode-Nummer und alle Prozesse, die die Datei geöffnet halten, hätten immer noch diealtDatei.

mvkönnte beides tun, je nachdem, ob die Verschiebung zwischen Dateisystemen erfolgt oder nicht... Um sicherzustellen, dass Sie eine völlig neue Datei erhalten, heben Sie zuerst die Verknüpfung mit dem Original auf oder benennen Sie es um. Etwa so:

mv /opt/patch.sh /opt/patch.sh.old     # or rm
mv /mnt/update/patch.sh /opt/patch.sh

Auf diese Weise hätte die laufende Shell auch nach dem Verschieben noch einen Dateihandle zu den alten Daten.


Das heißt, soweit ich es getestet habe, liest Bash die gesamte Schleife, bevor sie etwas davon ausführt, sodass Änderungen an der zugrunde liegenden Datei das laufende Skript nicht beeinflussen würden, solange die Ausführung innerhalb der Schleife bleibt. (Es muss die gesamte Schleife lesen, bevor es ausgeführt wird, da es am Ende Umleitungen geben kann, die die gesamte Schleife betreffen.)

Nach dem Verlassen der Schleife bewegt Bash den Lesezeiger zurück an die Position direkt nach dem Ende der Schleife und setzt dann das Lesen der Eingabedatei fort.

Alle im Skript definierten Funktionen werden ebenfalls in den Speicher geladen. Wenn Sie also die Hauptlogik des Skripts in eine Funktion packen und diese nur am Ende aufrufen, ist das Skript relativ sicher gegen Änderungen an der Datei:

#!/bin/sh
main() {
    do_stuff
    exit
}
main

Es ist jedenfalls nicht allzu schwer zu testen, was passiert, wenn ein Skript überschrieben wird:

$ cat > old.sh <<'EOF'
#!/bin/bash
for i in 1 2 3 4 ; do
        # rm old.sh
        cat new.sh > old.sh 
        sleep 1
        echo $i
done
echo will this be reached?
EOF
$ cat > new.sh <<'EOF'
#!/bin/bash
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOF
$ bash old.sh

Wenn rm old.shauskommentiert wird, wird das Skript direkt geändert. Ohne den Kommentar wird eine neue Datei erstellt. (Dieses Beispiel beruht teilweise darauf, new.shdass größer als ist old.sh, denn wenn es kürzer wäre, läge die Leseposition der Shell nach der Schleife hinter dem Ende des neuen Skripts.)

Antwort2

Ich hatte dieses Problem schon einmal und kann bestätigen, dass dies ein Problem sein kann. In meinem Fall führt ein Regressionsskript zuerst einen Git-Pull aus und wird möglicherweise aktualisiert, nachdem es ausgeführt wird, was zu Problemen führt.

Das Problem tritt normalerweise auf, wenn die Shell zurückgeht und prüft, ob noch weitere Zeilen zu interpretieren sind. Dies kann zu Fehlern führen, selbst wenn sich der gewünschte Code in einer Schleife befindet. Um dies zu vermeiden, verwenden Sie die Struktur indieser Beitrag.

Antwort3

Ein selbstausführendes, selbstmodifizierendes Skript? Das ist keine gute Idee.

Eine bessere Lösung wäre, einen Stub-Daemon mit minimaler Funktionalität zu erstellen (d. h. verantwortlich für die Installation neuer Versionen des Slave-Skripts und dessen periodisches Aufrufen). So etwas wie ... (nicht getestet)

while true; do
  # check if a patch tar file exists and if yes then do patching
  if [ -f "$PATCH" ]; then
      ( cd /usr/local/mydaemon \
      && tar -xzf "$PATCH" \
      && rm -f "$PATCH" ) \
      || exit -1
  fi
  $SCRIPT
  sleep 10
done

verwandte Informationen