Die Ausgabe eines Shell-Skripts wird bei der Übergabe als Argument an ein Skript falsch aufgeteilt.

Die Ausgabe eines Shell-Skripts wird bei der Übergabe als Argument an ein Skript falsch aufgeteilt.

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.shund Einfügen als Eingabe sh test.shfunktioniert 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.shstattdessen 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- $varoder Befehlsersetzung $(cmd)nicht in Anführungszeichen setzen, wird das Ergebnis den folgenden Transformationen unterzogen:

  1. 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).
  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.shSchreiben 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.shtest.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 IFSnur ein Zeilenumbruch enthalten ist. Stellen Sie sicher, dass args.shgenau 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 \tdurch 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.sheine Liste von Zeichenfolgen als Eingabe akzeptieren. Wenn die Zeichenfolgen keine Zeilenumbrüche enthalten, rufen Sie dies auf args.sh | test.shund 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 "$@"

verwandte Informationen