Nehmen wir an, ich habe die folgenden beiden Shell-Skripte:
#!/bin/sh
#This script is named: args.sh
echo 1 "\"Two words\"" 3
, Und:
#!/bin/sh
#This script is named: test.sh
echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3
Wenn ich die Skripte wie folgt aufrufe:
sh test.sh $(sh args.sh)
, Ich erhalte:
Argument 1: 1
Argument 2: "Two
Argument 3: words"
Stattdessen erwartete ich Folgendes:
Argument 1: 1
Argument 2: Two words
Argument 3: 3
Das Kopieren der Ausgabe sh args.sh
und Einfügen als Eingabe sh test.sh
funktioniert einwandfrei. Ich gehe also davon aus, dass dies nicht das ist, was die Shell tut. Ich kann die gewünschte/erwartete Ausgabe sh args.sh | xargs sh test.sh
stattdessen durch Aufrufen erzielen.
Ich frage mich jedoch, ob es eine gleichwertige Möglichkeit gibt, dies zu tun, ohne die Ausgabe des ersten Skripts (args.sh) an xargs weiterzuleiten. Ich möchte, dass die Skripts in der ursprünglichen Reihenfolge aufgerufen werden; das Argumentskript soll die Parameter an das zweite ausgeben. Ich suche auch nach einer Erklärung, warum dieser Aufruf nicht wie erwartet funktioniert?
Antwort1
Ein Teil des Problems besteht darin, dass der von args.sh zurückgegebene String nicht wie ein direkter Befehl analysiert wird, sondern nur anhand des Werts von $IFS ( $' \t\n'
). Versuchen Sie, die Befehlsverfolgung mit einzuschalten set -x
:
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh 1 '"Two' 'words"' 3
Argument 1: 1
Argument 2: "Two
Argument 3: words"
$
Beachten Sie die Zeile, die mit einem einzelnen beginnt +
: Es gibt vier Argumente, '"Two'
und 'words"'
die beiden werden als separate Argumente analysiert. Sie sollten $IFS ändern.
$ set -x
$ IFS='"'
+ IFS='"'
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh '1 ' 'Two words' ' 3'
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$
Dies funktioniert nicht bei jeder Ausgabe. Am besten ändern Sie die Ausgabe von args.sh so, dass die Ausgabe durch etwas anderes als ein Leerzeichen getrennt wird, beispielsweise durch ein Komma oder einen Doppelpunkt:
$ cat /tmp/args.sh
#!/bin/sh
#This script is named: args.sh
echo "1,Two words,3"
$ IFS=,
$ sh /tmp/test.sh $(sh /tmp/args.sh)
+ sh /tmp/args.sh
+ sh /tmp/test.sh 1 Two words 3
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$
Antwort2
Wenn Sie eine Variablen- $var
oder Befehlsersetzung $(cmd)
nicht in Anführungszeichen setzen, wird das Ergebnis den folgenden Transformationen unterzogen:
- Teilen Sie die resultierende Zeichenfolge in Wörter auf. Die Aufteilung erfolgt an Leerzeichen (Sequenzen aus Leerzeichen, Tabulatoren und Zeilenumbrüchen); dies kann durch die Einstellung konfiguriert werden
IFS
(siehe1,2). - Jedes der resultierenden Wörter wird als Glob-Muster behandelt und wenn es mit einigen Dateien übereinstimmt, wird das Wort durch die Liste der Dateinamen ersetzt.
Beachten Sie, dass das Ergebnis kein String, sondern eine Liste von Strings ist. Beachten Sie außerdem, dass Anführungszeichen wie "
hier nicht vorkommen. Sie sind Teil der Shell-Quellsyntax und nicht der String-Erweiterung.
Eine allgemeine Regel der Shell-Programmierung ist, Variablen- und Befehlsersetzungen immer in Anführungszeichen zu setzen, es sei denn, Sie wissen, warum Sie sie weglassen müssen. test.sh
Schreiben Sie also in echo "Argument 1: $1"
. Um Argumente an zu übergeben , haben Sie ein Problem: Sie müssen eine Liste von Wörtern von an test.sh
übergeben , aber Ihre gewählte Methode beinhaltet eine Befehlsersetzung und diese bietet nur die Möglichkeit für eine einfache Zeichenfolge.args.sh
test.sh
Wenn Sie garantieren können, dass die zu übergebenden Argumente keine Zeilenumbrüche enthalten und es akzeptabel ist, den Aufrufprozess geringfügig zu ändern, können Sie festlegen, dass IFS
nur ein Zeilenumbruch enthalten ist. Stellen Sie sicher, dass args.sh
genau ein Dateiname pro Zeile ohne Anführungszeichen ausgegeben wird.
IFS='
'
test.sh $(args.sh)
unset IFS
Wenn die Argumente beliebige Zeichen enthalten können (vermutlich mit Ausnahme von Nullbytes, die Sie nicht als Argumente übergeben können), müssen Sie eine Kodierung vornehmen. Jede Kodierung ist möglich; natürlich ist dies nicht dasselbe wie die direkte Übergabe der Argumente: das ist nicht möglich. Beispiel: in args.sh
(ersetzen Sie es \t
durch ein echtes Tabulatorzeichen, wenn Ihre Shell dies nicht unterstützt):
for x; do
printf '%s_\n' "$x" |
sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done
und in test.sh
:
for arg; do
decoded=$(printf '%s\n' "$arg" |
sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
decoded=${decoded%_qn}
# process "$decoded"
done
Möglicherweise möchten Sie test.sh
eine Liste von Zeichenfolgen als Eingabe akzeptieren. Wenn die Zeichenfolgen keine Zeilenumbrüche enthalten, rufen Sie dies auf args.sh | test.sh
und verwenden Sie es in args.sh
(Erläuterung):
while IFS= read -r line; do
# process "$line"
done
Eine andere Methode, die das Setzen von Anführungszeichen gänzlich überflüssig macht, besteht darin, das zweite Skript vom ersten aus aufzurufen.
…
args.sh "$@"