Im Anschluss an: unerwartetes Verhalten bei der Ersetzung von Shell-Befehlen
Ich habe einen Befehl, der eine riesige Liste von Argumenten aufnehmen kann, von denen einige legitimerweise Leerzeichen enthalten können (und wahrscheinlich andere Dinge).
Ich habe ein Skript geschrieben, das diese Argumente mit Anführungszeichen für mich generieren kann, aber ich muss die Ausgabe kopieren und einfügen, z
./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
Ich habe versucht, dies durch einfaches Tun zu rationalisieren
./othercommand $(./somecommand)
und stieß auf das unerwartete Verhalten, das oben in Frage gestellt wurde. Die Frage ist: Kann die Befehlssubstitution zuverlässig verwendet werden, um die Argumente zu generieren, othercommand
wenn einige Argumente zitiert werden müssen und dies nicht geändert werden kann?
shell
arguments
command-substitution
user1207217
quelle
quelle
eval
verwendet werden, wird jedoch im Allgemeinen nicht empfohlen.xargs
ist auch etwas zu beachtensomecommand
einer regelmäßigen Shell-Analyse:
) ... vorausgesetzt, dass das Zeichen zuverlässig nicht in der Ausgabe enthalten ist.Antworten:
Wenn die Ausgabe für die Shell korrekt in Anführungszeichen gesetzt ist und Sie der Ausgabe vertrauen , können Sie sie ausführen
eval
.Angenommen, Sie haben eine Shell, die Arrays unterstützt, ist es am besten, eine zu verwenden, um die erhaltenen Argumente zu speichern.
Wenn
./gen_args.sh
eine Ausgabe wie erzeugt wird'foo bar' '*' asdf
, können wireval "args=( $(./gen_args.sh) )"
ein Arrayargs
mit den Ergebnissen auffüllen. Das wäre die drei Elementefoo bar
,*
,asdf
.Wir können
"${args[@]}"
wie gewohnt die Array-Elemente einzeln erweitern:(Beachten Sie die Anführungszeichen.
"${array[@]}"
Erweitert sich auf alle Elemente als eindeutige Argumente, die nicht geändert wurden. Ohne Anführungszeichen unterliegen die Array-Elemente einer Wortaufteilung . Siehe z. B. die Seite Arrays in BashGuide .)Jedoch ,
eval
wird gerne weiter Shell Ersetzungen ausgeführt werden , so$HOME
in der Ausgabe zu Ihrem Home - Verzeichnis erweitern würde, und ein Befehl Substitution würde tatsächlich einen Befehl in der Shell - Lauf laufeneval
. Eine Ausgabe von"$(date >&2)"
würde ein einzelnes leeres Array-Element erstellen und das aktuelle Datum auf stdout drucken. Dies ist ein Problem, wenngen_args.sh
die Daten von einer nicht vertrauenswürdigen Quelle wie einem anderen Host über das Netzwerk abgerufen werden und Dateinamen von anderen Benutzern erstellt werden. Die Ausgabe kann beliebige Befehle enthalten. (Wenn esget_args.sh
selbst böswillig wäre, müsste es nichts ausgeben, es könnte einfach die böswilligen Befehle direkt ausführen.)Eine Alternative zu Shell-Zitaten, die ohne Auswertung nur schwer zu analysieren sind, besteht darin, in der Ausgabe Ihres Skripts ein anderes Zeichen als Trennzeichen zu verwenden. Sie müssten eine auswählen, die in den tatsächlichen Argumenten nicht benötigt wird.
Lassen Sie uns wählen
#
und die Skriptausgabe habenfoo bar#*#asdf
. Jetzt können wir die Erweiterung des Befehls ohne Anführungszeichen verwenden , um die Ausgabe des Befehls auf die Argumente aufzuteilen.Sie müssen
IFS
später zurücksetzen, wenn Sie von der Wortaufteilung an anderer Stelle im Skript abhängig sind (unset IFS
sollte funktionieren, um es zum Standard zu machen), und auch verwenden,set +f
wenn Sie später Globbing verwenden möchten.Wenn Sie Bash oder eine andere Shell mit Arrays nicht verwenden, können Sie die Positionsparameter dafür verwenden. Ersetzen Sie
args=( $(...) )
durchset -- $(./gen_args.sh)
und verwenden Sie"$@"
stattdessen"${args[@]}"
. (Auch hier benötigen Sie Anführungszeichen"$@"
, da sonst die Positionsparameter einer Wortaufteilung unterliegen.)quelle
${args[@]}
- das hat bei mir sonst nicht funktioniert"${array[@]}"
wie mit"$@"
. Beide müssen in Anführungszeichen gesetzt werden, da sonst die Wortaufteilung die Array-Elemente in Teile zerlegt.Das Problem ist , dass , sobald Ihr
somecommand
Skript die Optionen gibt fürothercommand
die Optionen sind wirklich nur Text und auf Gedeih und Verderb des Standard - Parsing Shell (beeinflußt durch , was auch immer$IFS
passiert zu sein , und was Shell - Optionen sind in der Tat, Sie , die in dem allgemeinen Fall würde nicht Kontrolle haben über).Anstelle der Verwendung von
somecommand
zu Ausgabe der Optionen, wäre es einfacher, sicherer und robuster , es zu benutzen zu nennenothercommand
. Dassomecommand
Skript würde dann sein , Wrapper - Skript um ,othercommand
anstatt irgendeine Art von Helfer - Skript , dass Sie in besonderer Weise als Teil der Kommandozeile aufrufen müssten sich daran zu erinnernotherscript
. Wrapper-Skripte sind eine sehr verbreitete Methode, um ein Tool bereitzustellen, das nur ein ähnliches Tool mit anderen Optionen aufruft (überprüfen Sie einfach,file
welche Befehle/usr/bin
tatsächlich Shell-Skript-Wrapper sind).In
bash
,ksh
oderzsh
könnten Sie leicht ein Wrapper - Skript , das ein Array verwendet , um die einzelnen Optionen für halten ,othercommand
etwa so:Rufen Sie dann auf
othercommand
(noch im Wrapper-Skript):Die Erweiterung von
"${options[@]}"
würde sicherstellen, dass jedes Element desoptions
Arrays einzeln in Anführungszeichen gesetzt undothercommand
als separate Argumente dargestellt wird.Der Benutzer des Wrappers würde sich der Tatsache nicht bewusst sein, dass er tatsächlich aufruft
othercommand
, was nicht wahr wäre , wenn das Skript stattdessen nur die Befehlszeilenoptionen fürothercommand
als Ausgabe generieren würde .In
/bin/sh
,$@
um die Optionen zu halten:(
set
Der Befehl zum Einstellen der Positionsparameter verwendet wird$1
,$2
,$3
usw. Diese sind , was die Anordnung macht$@
in einem Standard - POSIX - Shell. Die erste--
zu signalisieren ist ,set
dass es keine Möglichkeiten gegeben, nur Argumente. Das--
ist wirklich nur dann benötigt , wenn die Der erste Wert beginnt mit-
).Beachten Sie, dass es sich um doppelte Anführungszeichen handelt
$@
und${options[@]}
dass die Elemente nicht einzeln in Wörter aufgeteilt werden (und der Dateiname nicht markiert ist).quelle
set --
?Wenn die
somecommand
Ausgabe in einer zuverlässig guten Shell-Syntax vorliegt, können Sie Folgendes verwendeneval
:Sie müssen jedoch sicherstellen, dass die Ausgabe ein gültiges Anführungszeichen und dergleichen enthält, da Sie sonst möglicherweise auch Befehle außerhalb des Skripts ausführen:
Beachten Sie, dass dies
echo rm bar baz test.sh
(aufgrund von;
) nicht an das Skript übergeben wurde und als separater Befehl ausgeführt wurde. Ich habe die|
Umgebung hinzugefügt, um dies$var
hervorzuheben.Im Allgemeinen ist es
somecommand
nicht möglich, die Ausgabe zuverlässig zum Erstellen einer Befehlszeichenfolge zu verwenden, es sei denn, Sie können der Ausgabe von vollständig vertrauen .quelle