Discussion:
shell script forkt shell
(zu alt für eine Antwort)
Jan Novak
2020-03-24 07:20:20 UTC
Permalink
Moin,

ich habe ein shell script, welches bei einem update diese funktion aufruft:

myupdate(){
echo "forking update, please wait ... "
echo '#!/bin/bash

sleep 1
echo "
updatimg from : '$updateserver'
Version before update is : '$jnversion'"
/dev/null 2>&1
echo -n "Version after update is : "
cat /opt/dosanoid2.sh|grep "jnversion="|grep -v "cat"|cut -f2 -d"="
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}

Das funktioniert auch wunderbar. Nachdem das temporäre update script
geforkt wurde und beendet wird, ist kein Prompt zu sehen (obwohl er
natürlich da ist).
Wie könnte ich das umgehen, so dass der Prompt auch wieder sichtbar ist
(ohne ENTER drücken zu müssen).

Jan
Stefan Wiens
2020-03-24 14:07:25 UTC
Permalink
Post by Jan Novak
echo "
updatimg from : '$updateserver'
Version before update is : '$jnversion'"
/dev/null 2>&1
echo -n "Version after update is : "
cat /opt/dosanoid2.sh|grep "jnversion="|grep -v "cat"|cut -f2 -d"="
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Beim Schreiben nach /tmp sollte man etwas vorsichtiger sein.

Falls dosanoid-update.sh bereits existiert und schreibbar ist, behält es
Ownership und Permissions. Das ist anfällig für Race-Conditions. Wenn es
existiert und nicht schreibbar ist, hast du einen nicht behandelten
Fehler. Es könnte auch ein Symlink sein, dann wird eine beliebige Datei
überschrieben. Die umask sollte auch passend gesetzt sein.

Die temporäre Datei sollte man auch wieder wegräumen, natürlich nur,
wenn man sie sicher erzeugt hat. Da man nicht weiß, wann die
Hintergrundshell das Skript geöffnet hat, kann man es nicht einfach so
abräumen.

In GNU Coreutils gibt es mktemp(1); in debianutils tempfile(1). Mit
POSIX-Bordmitteln wird es, glaube ich, hakeliger. sh -C bzw. set -o
noclobber kämen in Betracht, aber da scheint nicht garantiert, dass mit
O_CREAT | O_EXCL geöffnet wird.
--
Stefan
Stefan Wiens
2020-03-24 14:45:59 UTC
Permalink
Post by Stefan Wiens
Post by Jan Novak
echo "
[...]
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Die temporäre Datei sollte man auch wieder wegräumen, natürlich nur,
wenn man sie sicher erzeugt hat. Da man nicht weiß, wann die
Hintergrundshell das Skript geöffnet hat, kann man es nicht einfach so
abräumen.
Das wäre die leichteste Übung. Wenn man der Hintergrundshell das Skript
per Redirection verabreicht, könnte man anschließend sofort löschen.
Oder halt via trap.
--
Stefan
Jan Novak
2020-03-25 06:20:12 UTC
Permalink
Post by Stefan Wiens
Post by Jan Novak
echo "
updatimg from : '$updateserver'
Version before update is : '$jnversion'"
/dev/null 2>&1
echo -n "Version after update is : "
cat /opt/dosanoid2.sh|grep "jnversion="|grep -v "cat"|cut -f2 -d"="
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Beim Schreiben nach /tmp sollte man etwas vorsichtiger sein.
Falls dosanoid-update.sh bereits existiert und schreibbar ist, behält es
Ownership und Permissions. Das ist anfällig für Race-Conditions. Wenn es
existiert und nicht schreibbar ist, hast du einen nicht behandelten
Fehler. Es könnte auch ein Symlink sein, dann wird eine beliebige Datei
überschrieben. Die umask sollte auch passend gesetzt sein.
Die temporäre Datei sollte man auch wieder wegräumen, natürlich nur,
wenn man sie sicher erzeugt hat. Da man nicht weiß, wann die
Hintergrundshell das Skript geöffnet hat, kann man es nicht einfach so
abräumen.
In GNU Coreutils gibt es mktemp(1); in debianutils tempfile(1). Mit
POSIX-Bordmitteln wird es, glaube ich, hakeliger. sh -C bzw. set -o
noclobber kämen in Betracht, aber da scheint nicht garantiert, dass mit
O_CREAT | O_EXCL geöffnet wird.
OK, danke für den Tip. Werde ich üb ernehmen.

Jan
Christoph 'Mehdorn' Weber
2020-07-11 15:44:21 UTC
Permalink
Hallo!
Post by Stefan Wiens
Post by Jan Novak
cat /opt/dosanoid2.sh|grep "jnversion="|grep -v "cat"|cut -f2 -d"="
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Beim Schreiben nach /tmp sollte man etwas vorsichtiger sein.
Braucht es das im konkreten Fall überhaupt? Meiner Meinung nach
sollte das auch direkt per Pipe gehen:

echo 'ls v*; echo foo' | bash &

Falls die Ausgaben vom Updater nicht interessieren, könnte man
die wegwerfen (und den ggf. umbauen, daß er z.B. stattdessen nach
syslog schreibt):

echo 'ls v*; echo foo' | bash >/dev/null 2>&1 </dev/null &

Dann läuft das trotzdem im Hintergrund, aber der Prompt ist
gleich wieder sichtbar.

Will man nur etwas (früh) im Hintergrund starten, weil es eine
Weile dauert und das Script solange noch etwas anderes tun soll,
kann man auch schlicht am Ende warten:

echo 'ls v*; sleep 5; echo foo' | bash &
do_other_things
wait


Aber das nur der Vollständigkeit halber, für dein Problem hast
du ja bereits eine andere Lösung.

Christoph
--
Kennt jemand eine Internetseite oder notfalls auch eine guenstige
CD-Rom auf der ich sphaerische Hintergrundmusik im Wave-Format fuer
Webseiten finde? --
Wie jetzt? Kugel- oder wellenfoermig? (K. Hellmann, Freddy Leitner)
Stefan Reuther
2020-03-24 17:22:51 UTC
Permalink
Post by Jan Novak
myupdate(){
...
Post by Jan Novak
echo "
...
Post by Jan Novak
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Das funktioniert auch wunderbar. Nachdem das temporäre update script
geforkt wurde und beendet wird, ist kein Prompt zu sehen (obwohl er
natürlich da ist).
Wie könnte ich das umgehen, so dass der Prompt auch wieder sichtbar ist
(ohne ENTER drücken zu müssen).
Was willst du damit eigentlich erreichen?

"Im Hintergrund ausführen" und "Konsolenausgabe" beißt sich halt. Wenn
das Skript aus dem Hintergrund eine Konsolenausgabe macht, überschreibt
es den Prompt oder was auch immer sonst gerade im Vordergrund läuft.

Also führ das Ding entweder im Vordergrund aus (dann kommt der Prompt
ganz normal nach dem Skript wieder, aber eben erst dann), streich die
Ausgaben (z.B. stattdessen in ein Logfile schreiben), oder hol dir den
Prompt eben mit Enter, Ctrl-C, Ctrl-L wieder.


Stefan
Helmut Waitzmann
2020-03-24 17:42:17 UTC
Permalink
Jan Novak <***@gmail.com>:

Vorbemerkung:  Wenn du ein Shell‐Skript postest, formatiere es
bitte so, dass die Zeilen kurz genug sind, damit sie nicht vom
Newsreader umbrochen werden.  Zeilen mit maximal 66 Zeichen Länge
sind dafür ein guter Anhaltspunkt. 

Hier habe ich jetzt raten müssen, wie es eigentlich auszusehen
hat, und habe es mal umformatiert: 
Post by Jan Novak
ich habe ein shell script, welches bei einem update diese
myupdate(){
echo "forking update, please wait ... "
echo '#!/bin/bash
sleep 1
echo "
updatimg from : '$updateserver'
Version before update is : '$jnversion'"
.sh /opt >/dev/null 2>&1
echo -n "Version after update is : "
cat /opt/dosanoid2.sh | grep "jnversion=" |
grep -v "cat" | cut -f2 -d"="
echo "Ready (press ENTER)"
' >/tmp/dosanoid-update.sh
sh /tmp/dosanoid-update.sh &
exit
}
Das funktioniert auch wunderbar. Nachdem das temporäre update
script geforkt wurde und beendet wird, ist kein Prompt zu sehen
(obwohl er natürlich da ist).
Ja.  Er ist nicht dort, wo man ihn erwartet:  Wenn dein
Shell‐Skript die Funktion «myupdate» abgearbeitet hat, beendet es
sich (vermutlich – du hast nicht genug Kontext gepostet).  Dann
liefert dir auch der interaktive Shell seinen Prompt. 

Zu diesem Zeitpunkt läuft jedoch vermutlich noch der von der
Funktion «myupdate» angestoßene Update‐Prozess, der das
Shell‐Skript «/tmp/dosanoid-update.sh» abarbeitet, als verwaister
Prozess weiter.  Je nachdem, wie nun das Terminal, in dem dein
interaktiver Shell läuft, eingestellt ist (beispielsweise mit dem
Programm «stty»), kann der verwaiste Prozess Ausgaben aufs
Terminal machen (bei «stty -tostop»), oder es wird ihm verwehrt
(bei «stty tostop»). 

Vermutlich ist dein Terminal so eingestellt, dass er es darf. 
Also wird er es tun.  Aber erinnere dich:  Zu diesem Zeitpunkt hat
der interaktive Shell seinen Prompt längst ausgegeben.  Deshalb
erscheinen die Ausgaben des verwaisten Update‐Prozesses nach dem
Prompt des interaktiven Shells.  Schau nach.  Stimmt's? 
Post by Jan Novak
Wie könnte ich das umgehen, so dass der Prompt auch wieder
sichtbar ist (ohne ENTER drücken zu müssen).
Gar nicht.  Du hast dem interaktiven Shell vorgeschwindelt, dein
Job sei bereits zu Ende, obwohl da noch ein verwaister Prozess,
von dem der Shell natürlich nichts wissen kann, lebt.  Dann musst
du auch damit leben, dass er dir das glaubt und entsprechend
handelt.  Und deswegen wird er dir auch keinen weiteren Prompt
ausgeben, ehe du ein weiteres Kommando (oder eine leere Zeile)
eingetippt hast. 

Wenn du das nicht willst, lass den Update‐Prozess nicht
verwaisen:  Lass dein Shell‐Skript erst dann zu Ende kommen, wenn
der Update‐Prozess fertig ist. 

Oder lass die Ausgaben des verwaisten Update‐Prozesses nicht aufs
Terminal kommen, sondern schicke sie dir per E‐Mail. 

Oder lass den Update‐Prozess als eigenen Job laufen.  Das geht
dann aber nicht aus demselben Shell‐Skript heraus, sondern
erfordert, dass du aus dem interaktiven Shell zwei Jobs startest. 

Wenn du das Shell‐Skript nicht ändern darfst (es also so nehmen
musst, wie es ist), sind die eben genannten Möglichkeiten für dich
so nicht machbar. 

Es gibt dann aber noch einen weiteren Trick, wie man erreichen
kann, dass der interaktive Shell wartet, bis auch der verwaiste
Unterprozess zu Ende gekommen ist.  Starte dein Shell‐Skript mit
einer Umleitung der Terminal‐Ausgaben in ein Pipe zu «cat»:

dein_Shell-Skript 2>&1 | cat -u

Das Problem dabei:  Der Exit‐Status des Shell‐Skripts geht
verloren.  Mit dem «bash» als interaktivem Shell gibt es aber
Abhilfe, um dennoch an ihn ranzukommen:

(
set_exitstatus() { return ${1+"$1"} ; } &&
dein_Shell-Skript 2>&1 | cat -u
set -- "${PIPESTATUS[@]" &&
set_exitstatus "$1" &&
set_exitstatus "$2"
)


Zwei Bemerkungen noch: 


Das «echo»‐Kommando schreibt dem Shell‐Skript
«/tmp/dosanoid-update.sh» in die erste Zeile «#!/bin/bash».  Es
ruft es jedoch dann schließlich mit einem «sh» statt einem «bash»
auf. 

Das Shell‐Skript macht es sich damit, die Inhalte der
Shell‐Variablen «updateserver» und «jnversion» in das
Update‐Skript zu schreiben, etwas zu einfach:  Je nachdem, wer
bestimmt, welchen Inhalt die Shell‐Variablen erhalten, führt das
dazu, dass das Shell‐Skript eine Schwachstelle hat, die zur
Privilegien‐Ausweitung («root» werden) ausgenutzt werden kann. 
--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.
Jan Novak
2020-03-25 06:30:09 UTC
Permalink
Vorbemerkung:  Wenn du ein Shell‐Skript postest, formatiere es bitte so,
dass die Zeilen kurz genug sind, damit sie nicht vom Newsreader
umbrochen werden.  Zeilen mit maximal 66 Zeichen Länge sind dafür ein
guter Anhaltspunkt.
OK :-)
Oder lass den Update‐Prozess als eigenen Job laufen.  Das geht dann aber
nicht aus demselben Shell‐Skript heraus, sondern erfordert, dass du aus
dem interaktiven Shell zwei Jobs startest.
Es geht ja genau darum, das das eigentliche primäre script sich selbst
updatet... Daher war die Idee, ein anderes script zu starten, welches
dieses Update macht.
Es gibt dann aber noch einen weiteren Trick, wie man erreichen kann,
dass der interaktive Shell wartet, bis auch der verwaiste Unterprozess
zu Ende gekommen ist.  Starte dein Shell‐Skript mit einer Umleitung der
 dein_Shell-Skript 2>&1 | cat -u
Interessant ...
Das Problem dabei:  Der Exit‐Status des Shell‐Skripts geht verloren.
Das wäre nicht dramatisch.
Mit dem «bash» als interaktivem Shell gibt es aber Abhilfe, um dennoch
 (
   set_exitstatus() { return ${1+"$1"} ; } &&
   dein_Shell-Skript 2>&1 | cat -u
   set_exitstatus "$1" &&
   set_exitstatus "$2"
 )
Nehme ich mal mit, vielleicht kann werde ich den exitstatus doch noch
mal brauchen.
Das «echo»‐Kommando schreibt dem Shell‐Skript «/tmp/dosanoid-update.sh»
in die erste Zeile «#!/bin/bash».  Es ruft es jedoch dann schließlich
mit einem «sh» statt einem «bash» auf.
Ohh, ja ... danke, hatte ich übersehen.
Das Shell‐Skript macht es sich damit, die Inhalte der Shell‐Variablen
«updateserver» und «jnversion» in das Update‐Skript zu schreiben, etwas
zu einfach:  Je nachdem, wer bestimmt, welchen Inhalt die
Shell‐Variablen erhalten, führt das dazu, dass das Shell‐Skript eine
Schwachstelle hat, die zur Privilegien‐Ausweitung («root» werden)
ausgenutzt werden kann.
Die Variablen (Inhalte) kommen aus dem primären Script. Dieses muss als
root ausgeführt werden, weil es diese Berechtigungen für den eigentliche
Zweck benötigt (Daten vom zfs lesen und verschieben).

Jan
Stefan Reuther
2020-03-25 17:57:37 UTC
Permalink
Post by Jan Novak
Oder lass den Update‐Prozess als eigenen Job laufen.  Das geht dann
aber nicht aus demselben Shell‐Skript heraus, sondern erfordert, dass
du aus dem interaktiven Shell zwei Jobs startest.
Es geht ja genau darum, das das eigentliche primäre script sich selbst
updatet... Daher war die Idee, ein anderes script zu starten, welches
dieses Update macht.
Dafür must du aber nicht solche Handstände machen. Mach lieber andere
Handstände :)

- neues Skript nach /pfad/zu/mein/skript.neu schreiben
- `mv /pfad/zu/mein/skript.neu /pfad/zu/mein/skript`

Das macht eine neue Datei mit einem neuen Inode und legt die dann unter
dem korrekten Dateinamen ab. Die Shell hat derweil den alten Inode
geöffnet, und der bleibt auch leben, wenn der Dateiname gelöscht wird,
solange er noch gebraucht wird.

Du darfst nur nicht einfach mit `echo whatever > /pfad/zu/mein/skript`
in die existierende Datei mit dem existierenden Inode schreiben, das
geht in der Tat schief.

Unter Windows geht das nicht, da muss man Handstände mit im Hintergrund
gestarteten Update-Prozessen machen.


Stefan
Jan Novak
2020-03-25 18:54:12 UTC
Permalink
Post by Stefan Reuther
Post by Jan Novak
Oder lass den Update‐Prozess als eigenen Job laufen.  Das geht dann
aber nicht aus demselben Shell‐Skript heraus, sondern erfordert, dass
du aus dem interaktiven Shell zwei Jobs startest.
Es geht ja genau darum, das das eigentliche primäre script sich selbst
updatet... Daher war die Idee, ein anderes script zu starten, welches
dieses Update macht.
Dafür must du aber nicht solche Handstände machen. Mach lieber andere
Handstände :)
- neues Skript nach /pfad/zu/mein/skript.neu schreiben
- `mv /pfad/zu/mein/skript.neu /pfad/zu/mein/skript`
Das macht eine neue Datei mit einem neuen Inode und legt die dann unter
dem korrekten Dateinamen ab. Die Shell hat derweil den alten Inode
geöffnet, und der bleibt auch leben, wenn der Dateiname gelöscht wird,
solange er noch gebraucht wird.
hmmm... vielleicht ist das die Lösung. Probiere ich mal aus.
Danke.

Jan
Stefan Kanthak
2020-03-25 19:13:05 UTC
Permalink
Post by Jan Novak
Mach lieber andere Handstände :)
- neues Skript nach /pfad/zu/mein/skript.neu schreiben
- `mv /pfad/zu/mein/skript.neu /pfad/zu/mein/skript`
Das macht eine neue Datei mit einem neuen Inode und legt die dann unter
dem korrekten Dateinamen ab. Die Shell hat derweil den alten Inode
geöffnet, und der bleibt auch leben, wenn der Dateiname gelöscht wird,
solange er noch gebraucht wird.
hmmm... vielleicht ist das die Lösung. Probiere ich mal aus.
Noch deutlicher wird dieses STANDARD-Verhalten so demonstriert:

- loesche das gerade laufende Skript:
del /pfad/zum/skript
- schreibe das neue Skript mit dem gleichen Dateinamen:
cat <<\E\O\F >>/pfad/zum/skript
...
EOF

Stefan
--
<https://www.duden.de/rechtschreibung/Kanthaken>
Stefan Reuther
2020-03-26 17:36:11 UTC
Permalink
Post by Stefan Kanthak
Post by Jan Novak
Post by Stefan Reuther
- neues Skript nach /pfad/zu/mein/skript.neu schreiben
- `mv /pfad/zu/mein/skript.neu /pfad/zu/mein/skript`
Das macht eine neue Datei mit einem neuen Inode und legt die dann unter
dem korrekten Dateinamen ab. Die Shell hat derweil den alten Inode
geöffnet, und der bleibt auch leben, wenn der Dateiname gelöscht wird,
solange er noch gebraucht wird.
hmmm... vielleicht ist das die Lösung. Probiere ich mal aus.
del /pfad/zum/skript
Das gibt je nach Umgebung '-bash: del: command not found' oder
'Parameterformat nicht ordnungsgemäß - "pfad".'.

:-)
Post by Stefan Kanthak
cat <<\E\O\F >>/pfad/zum/skript
...
EOF
Das riskiert halt, am Ende ohne Skript dazustehen. Manchmal will man das
(z.B. wenn das neue Skript nur ein 'unzip' weg ist), manchmal nicht.


Stefan
Stefan Kanthak
2020-03-26 18:14:01 UTC
Permalink
Post by Stefan Reuther
Post by Stefan Kanthak
Post by Jan Novak
Post by Stefan Reuther
- neues Skript nach /pfad/zu/mein/skript.neu schreiben
- `mv /pfad/zu/mein/skript.neu /pfad/zu/mein/skript`
Das macht eine neue Datei mit einem neuen Inode und legt die dann unter
dem korrekten Dateinamen ab. Die Shell hat derweil den alten Inode
geöffnet, und der bleibt auch leben, wenn der Dateiname gelöscht wird,
solange er noch gebraucht wird.
hmmm... vielleicht ist das die Lösung. Probiere ich mal aus.
del /pfad/zum/skript
Das gibt je nach Umgebung '-bash: del: command not found' oder
'Parameterformat nicht ordnungsgemäß - "pfad".'.
:-)
ARGH!
"del" ist latuernich falsch, das richtige Kommando ist "rm".
Post by Stefan Reuther
Post by Stefan Kanthak
cat <<\E\O\F >>/pfad/zum/skript
...
EOF
Das riskiert halt, am Ende ohne Skript dazustehen. Manchmal will man das
(z.B. wenn das neue Skript nur ein 'unzip' weg ist), manchmal nicht.
Mir ging's um die Demonstration des Verhaltens: der Verzeichnis-Eintrag
verschwindet sofort, eine gleichnamgie Datei kann neu geschrieben werden,
waehrend die vorhandene, noch geoeffnete Datei weiterhin gelesen werden kann.

Stefan
--
<https://www.duden.de/rechtschreibung/Kanthaken>
Ulli Horlacher
2020-03-27 12:39:26 UTC
Permalink
Post by Stefan Reuther
Post by Stefan Kanthak
del /pfad/zum/skript
Das gibt je nach Umgebung '-bash: del: command not found'
https://fex.belwue.de/fstools/del.html

:-)
--
Ullrich Horlacher Server und Virtualisierung
Rechenzentrum TIK
Universitaet Stuttgart E-Mail: ***@tik.uni-stuttgart.de
Allmandring 30a Tel: ++49-711-68565868
70569 Stuttgart (Germany) WWW: http://www.tik.uni-stuttgart.de/
Helmut Waitzmann
2020-03-25 22:04:04 UTC
Permalink
Post by Jan Novak
Post by Helmut Waitzmann
Es gibt dann aber noch einen weiteren Trick, wie man erreichen
kann, dass der interaktive Shell wartet, bis auch der verwaiste
Unterprozess zu Ende gekommen ist.  Starte dein Shell‐Skript
 dein_Shell-Skript 2>&1 | cat -u
Interessant ...
Der Witz daran ist folgender:  Solange irgendein Prozess –
beispielsweise auch der verwaiste Unterprozess – noch an der
Schreibseite des FIFOs angeschlossen ist, wartet das an der
Leseseite lauschende «cat», ob da noch etwas zum Lesen kommt. 
Weil der verwaiste Prozess seinen Schreibanschluss am FIFO aber
nicht schließt, ehe er sich beendet hat, wartet das «cat», bis er
sich beendet hat. 
Post by Jan Novak
Post by Helmut Waitzmann
Das Problem dabei:  Der Exit‐Status des Shell‐Skripts geht verloren.
Das wäre nicht dramatisch.
Hm.  Die reine Lehre sagt:  Es ist (nahezu) immer dramatisch, denn
irgendwann wird jemand auf die Idee kommen, das Skript woanders
(nichtinteraktiv) wiederzuverwenden.  Und dann wird es niemanden
geben, der vor dem Bildschirm sitzt und nach Fehlermeldungen
Ausschau hält.  Dann wird der (nichtinteraktive) Aufrufer darauf
vertrauen, dass auch das Skript – wie jedes vernünftig
geschriebene Programm – am Exit‐Status anzeigt, wenn es
scheitert. 
Post by Jan Novak
Post by Helmut Waitzmann
Oder lass den Update‐Prozess als eigenen Job laufen.  Das geht
dann aber nicht aus demselben Shell‐Skript heraus, sondern
erfordert, dass du aus dem interaktiven Shell zwei Jobs
startest.
Es geht ja genau darum, das das eigentliche primäre script sich
selbst updatet... Daher war die Idee, ein anderes script zu
starten, welches dieses Update macht.
Dann ist der Rat natürlich hinfällig. 


Jetzt geht mir ein Licht auf!  Dir geht es mit dem Update im
Hintergrund nicht darum, dass du nicht darauf warten willst, bis
das Update fertig ist, sondern du wolltest mit dem
Extra‐Update‐Skript vermeiden, dass das Skript während des Updates
seinem Shell den Skripttext zerstört?  Das könnte in der Tat
passieren, wird aber nicht dadurch vermieden, dass man ein zweites
Skript hernimmt: 

Hast du dir überlegt, was geschieht, wenn du das primäre Skript
startest, während noch im Hintergrund das Update vom letzten Start
läuft? 

Hast du dir überlegt, was geschieht, wenn das primäre Skript in
sehr kurzen Abständen hintereinander gestartet wird, so, dass der
erste Start die Datei «dosanoid-update.sh» noch nicht fertig
geschrieben hat, während der zweite damit beginnt? 

Man muss verhindern, dass das Skript gestartet wird, während seine
Datei gerade vom letzten Start her noch ein Update erfährt. 

Man muss verhindern, dass (auf derselben Skript‐Datei) zwei
Updates laufen. 

Man muss es hinbekommen, dass das Update nicht einfach die
bestehende Skript‐Datei ändert.  Und das gelingt auch: 

Es dürfte die neue Fassung nicht einfach auf die alte schreiben,
sondern müsste sie in eine Datei schreiben, die einen Namen hat,
den auch ein weiteres angestoßenes Update nicht verwendet:  Jedes
Update bräuchte seinen eigenen Dateinamen.  Eine Möglichkeit dazu
wäre, im Dateinamen die Prozessnummer des Updaters unterzubringen,
denn jede Prozessnummer kommt im System zum selben Zeitpunkt nur
einmal vor. 

Diese Datei müsste sich im selben Dateisystem (beispielsweise: im
selben Verzeichnis) wie die Skript‐Datei befinden, damit sie nach
erfolgter Erstellung am Ende des Updaters mit dem
«rename»‐Systemaufruf (siehe die Handbuch‐Seite «rename(2)») den
Platz der Skript‐Datei einnehmen kann.  (Ich meine, man kann
annehmen, dass das Programm «mv» diesen Systemaufruf benutzt.)

Ich würde entweder das Shell‐Skript warten lassen, bis das von ihm
angestoßene Update fertig ist, oder alternativ das Update im
Hintergrund laufen lassen, und mir seine Ausgabe per E‐Mail
schicken lassen. 
Post by Jan Novak
Post by Helmut Waitzmann
Das Shell‐Skript macht es sich damit, die Inhalte der
Shell‐Variablen «updateserver» und «jnversion» in das
Update‐Skript zu schreiben, etwas zu einfach:  Je nachdem, wer
bestimmt, welchen Inhalt die Shell‐Variablen erhalten, führt
das dazu, dass das Shell‐Skript eine Schwachstelle hat, die zur
Privilegien‐Ausweitung («root» werden) ausgenutzt werden kann.
Die Variablen (Inhalte) kommen aus dem primären Script. Dieses
muss als root ausgeführt werden, weil es diese Berechtigungen für
den eigentliche Zweck benötigt (Daten vom zfs lesen und
verschieben).
Wenn das also selber keine Schwachstelle hat, mag das gehen. 
Man kann es aber auch gleich richtig machen, dann kann auch aus
Versehen (statt aus Angriffslust) kein Ärger auftreten: 

In diesem Fall hier ist es am einfachsten, dem Update‐Skript die
Werte dieser beiden Variablen als Aufrufparameter mitzugeben. 
Dann hat es die Werte der Aufrufparameter als positional
parameters «"${1?}"» und «"${2?}"» zur Verfügung, und man muss den
Variableninhalt nicht zuvor ins Update‐Skript hineinschreiben. 

Das hat nebenbei den schönen Effekt, dass das Update‐Skript jedes
Mal gleich aussieht und deshalb eine Neuerstellung bei jedem
Update nicht nötig ist.  Man kann sich also den Aufwand mit einer
Datei im «/tmp/»‐Verzeichnis sparen. 

Exkurs:

Natürlich kann es auch Fälle geben, wo man nicht darum herum
kommt, einen Variableninhalt in ein Shell‐Skript
hineinzuschreiben. 

Wenn sowohl das zu schreibende Shell‐Skript ein Bash‐Skript werden
soll als auch der Hineinschreiber das in den Bash eingebaute
«printf»‐Kommando zum Hineinschreiben verwendet, ist dessen
Formatierungsanweisung «%q» wie geschaffen dafür. 

Beispiel für ein Bash‐Kommando, das zwei «printf»‐Anweisungen in
ein Bash‐Skript schreibt: 

{
printf ' %q' printf '%s\n' "So gibt's keinen Aerger." &&
echo &&
printf ' %s' printf '%s\n' "So knallt's." &&
echo
} > Skript.bash

Lass das Kommando mal laufen und schau dir dann das «Skript.bash»
an.  Das erste «printf»‐Kommando darin ist korrekt (wenn auch
vielleicht mit etwas eigenwilligem Quoting), das zweite defekt. 

Man kriegt das auch ohne Bash mit POSIX‐Shell‐Mitteln hin.  Dann
muss man aber etwas dem «%q» Entsprechendes selber schreiben. 

Ende des Exkurses. 


Und wenn ich schon beim Beckmessern bin:  Schick das Kommando
«echo» in den Ruhestand und nimm statt dessen «printf».  «echo»
kennt je nach Fassung (oder Konfiguration) die Option «-n» oder
Backslash‐Escape‐Sequenzen – oder lässt es bleiben.  «printf»
beendet diese Misere:  «-n» braucht man da nicht, und
Backslash‐Escape‐Sequenzen gibt es mit der Formatierungsanweisung
«%b».  (Der einzige Fall, wo ich «echo» noch verwende, ist, wenn
ich nur eine leere Zeile ausgeben will.  Dann schreibe ich gerne
auch einfach mal «echo» statt «printf '\n'».) 

Hier kommt jetzt eine Fassung von «myupdate», die in dein primäres
Shell‐Skript rein kommt.  Sie darf nicht im Hintergrund sondern
muss im selben Prozess laufen.  Weil das Shell‐Skript sich nicht
vor dem Updater beendet, wird auch kein Text nach dem Shell‐Prompt
aufs Terminal geschrieben.  (Wenn du doch an einer Fassung
interessiert bist, die im Hintergrund läuft, hake noch mal nach.) 

«"$0"» ist der Dateiname des Shell‐Skripts, «"$$"» die
Prozessnummer des Shells, der das Skript abarbeitet.

myupdate()
{
printf '%s\n' \
'updating from : '"$updateserver" \
'Version before update is : '"$jnversion" &&
scp \
root@"$updateserver":\
/store/freigaben/data/scripte/dosanoid.sh \
"$0.$$" >/dev/null 2>&1 &&
chmod -- a+rx "$0.$$" &&
mv -f -- "$0.$$" "$0" &&
printf '%s' \
'Version after update is : ' &&
cat /opt/dosanoid2.sh | grep -e 'jnversion=' |
grep -v -e 'cat' | cut -f2 -d'='
}
myupdate

Warum funktioniert das?  Weil das Ersetzen des Shell‐Skripts durch
eine neue Fassung («mv -f -- "$0.$$" "$0"») die bisherige Fassung
des Shell‐Skripts weder überschreibt noch löscht:  Die Datei des
bisherigen Shell‐Skripts hat danach zwar keinen Namen mehr, bleibt
aber so lange im Dateisystem vorhanden, wie der sie lesende Shell
sie noch geöffnet hat.  Erst wenn der sie schließt – sei es
ausdrücklich (weil er sie nicht mehr braucht), oder, weil er sich
beendet – wird sie aus dem Dateisystem entfernt.  (Solche
namenlosen Dateien werden gerne auch temporäre Dateien genannt.) 
--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.
Loading...