Alexander Goetzenstein <
alexander_g...@web.de>:
Stefans Vorschlag
eval $BEFEHL1
geht in die richtige Richtung (wenn auch noch nicht perfekt), und
Peters Erklärung trifft ins Schwarze: Zu dem Zeitpunkt, an dem
das Shell die Variable „BEFEHL1“ auswertet, ist es für die
Erkennung der „|“ als Pipe zu spät: Die Zerlegung der
Kommandozeile ist bereits vorher gelaufen, als das Shell sich um
den Inhalt der Variablen „BEFEHL1“ noch nicht gekümmert hat.
„Eval schickt hingegen sein Argument durch die komplette
Parser-Maschinerie durch“.
Und das ist im Prinzip auch gewünscht. Es hat allerdings auch
zur Folge, dass wirklich alles durch die komplette Maschinerie
geschickt wird – beispielsweise auch die bereits erhaltenen Werte
der Variablen „IF“ und „OF“ – überhaupt alles, was im Wert der
Variablen „BEFEHL1“ drinsteht.
Und nun sei angenommen, der Inhalt der Variablen „OF“ wäre
„noch_ein_Dateiname; rm -fR -- ~“,
zugegeben, ein eigenartiger Dateiname (aber prinzipiell nicht
ausgeschlossen, denn du möchtest ja in der Variablen „OF“ jeden
möglichen Dateinamen angeben können, ohne bedenken zu müssen, ob
er irgendwie (s. u.) Ärger machen könnte), den man beispielsweise
durch die Zuweisung
OF='noch_ein_Dateiname; rm -fR -- ~'
erhalten könnte. Das ist durchaus ein erlaubter Dateiname. Dann
wird natürlich auch dieser eigenartige Dateiname, da er ja
bereits Teil des Inhalts der Variablen „BEFEHL1“ ist, an das
„eval“‐Kommando übergeben.
Ein konkretes Beispiel soll das verdeutlichen:
IF=ein_Dateiname &&
OF='noch_ein_Dateiname; rm -fR -- ~' &&
BEFEHL1="dd bs=16M if=$IF | pv -petra -s 2000g | dd of=$OF"
An dieser Stelle im Skript hat die Variable „BEFEHL1“ jetzt den
folgenden Inhalt (abgesehen vom eingeschobenen Zeilenumbruch, den
ich um der kürzeren Zeilen willen eingefügt habe und der der
Shell‐Kommandzeile nicht schadet):
„dd bs=16M if=ein_Dateiname | pv -petra -s 2000g |
dd of=noch_ein_Dateiname; rm -fR -- ~“
Wenn man damit dann dem Shell das Kommando
eval "$BEFEHL"
vorlegt, führt es ein dem folgenden gleichwertiges Kommando aus:
eval 'dd bs=16M if=ein_Dateiname | pv -petra -s 2000g |
dd of=noch_ein_Dateiname; rm -fR -- ~'
Und das sagt dem Shell: Nimm den an „eval“ übergebenen Parameter
und interpretiere ihn als (neue) Kommandozeile, also als die
Kommandozeile
dd bs=16M if=ein_Dateiname | pv -petra -s 2000g |
dd of=noch_ein_Dateiname; rm -fR -- ~
Siehst du, was dann geschieht? (Tipp: Die an „eval“ übergebene
Kommandozeile enthält 2 durch ein „;“ getrennte Kommandos. Das
ist code injection, eine immer wieder gerngenommene
Angriffsmethode.)
Wie macht man's richtig? Man muss bei allen wörtlich zu
verwendenden Teilen des an „eval“ weitergereichten Parameters
dafür sorgen, dass sie nicht aufs Neue vom Kommandozeilen‐Parser
zerlegt werden. Das geht am einfachsten, indem man sie in
Apostrophe („'“) setzt (kompliziert wird es, wenn diese Teile
bereits Apostrophe enthalten, was im konkreten Beispiel aber
nicht der Fall ist):
IF=ein_Dateiname &&
OF='noch_ein_Dateiname; rm -fR -- ~' &&
BEFEHL1='dd bs=16M if="$IF" | pv -petra -s 2000g | dd of="$OF"'
Auf diese Weise erhält das „eval“‐Kommando, nachdem das Shell den
Parameter ausgewertet hat, den Parameterwert
„dd bs=16M if="$IF" | pv -petra -s 2000g | dd of="$OF"“.
Und dieser Parameterwert wird als Parameter von „eval“ vom Shell
als Kommandozeile interpretiert, wie gewünscht: Im besonderen
enthält die Kommandozeile nicht die Resultate der Auswertung der
Variablen „IF“ und „OF“ sondern die Auswerteausdrücke „"$IF"“ und
„"$OF"“ selbst. Diese beiden Auswerteausdrücke werden also nicht
vom Parser, der das „eval“‐Kommando erkennt, ausgewertet, sondern
erst von dem von „eval“ aufgerufenenen Parser, der die an „eval“
übergebene Kommandozeile zerlegt.
Da die Variable „OF“ in dieser Kommandozeile aber in
Anführungszeichen gesetzt ist, klebt das Shell ihren Inhalt
hinten unverändert an die Zeichenkette „of=“ dran und übergibt
das Resultat wie gewünscht an „dd“, ohne sich im geringsten für
irgendwelche besonderen Zeichen im Variableninhalt zu
interessieren.
Nochmal zitiert:
> bisweilen erleichtere ich mir das Scripten, indem ich einen
> Befehl in eine Variable schreibe und dann die Variable aufrufe,
Darf ich nachfragen, wo du die Erleichterung siehst? Ist Sinn
der Sache, dass du die Kommandozeile
dd bs=16M if="$IF" | pv -petra -s 2000g | dd of="$OF"
mehrmals mit je unterschiedlich belegten Variablen „IF“ und „OF“
aufrufen können willst, ohne sie jedes Mal aufs Neue hinschreiben
zu müssen?
Dann würde ich dir eher eine Shell‐Funktion empfehlen, die zwei
Parameter – die beiden Dateinamen – erhält und die Kommandozeile
enthält:
BEFEHL1()
{
dd bs=16M if="${1:?}" | pv -petra -s 2000g | dd of="${2:?}"
}
Aufrufen kann man sie dann durch das Kommando
BEFEHL1 "$IF" "$OF"
oder beispielsweise auch gleich die Parameter direkt ohne die
Variablen „IF“ und „OF“ mitliefern, falls man das möchte:
BEFEHL1 /dev/disk/by-id/wwn-0x5002538e403dd715 \
/root/backup/2023-10-30_13-12-11_T560B.img
Im Gegensatz zur Kommandozeile in einer Variablen muss und darf
man beim Körper einer Shell‐Funktion, die ja auch eine Folge von
Kommandos (wie in einer Kommandozeile) sein kann, kein extra
Quoting (wie mit den Apostrophen wie oben) hinzu nehmen. Man
schreibt sie einfach unverändert hin.
Von daher scheint mir das Verpacken einer Kommandozeile in eine
Variable und anschließendes Verarbeiten mit „eval“ eher keine
Erleichterung sondern eine Verkomplizierung zu sein. Das
Erstellen einer Shell‐Funktion hat das Problem nicht.