スクリプトに引数として渡されると、シェルスクリプトの出力が誤って分割されます。

スクリプトに引数として渡されると、シェルスクリプトの出力が誤って分割されます。

次の2つのシェルスクリプトがあるとします。

#!/bin/sh
#This script is named: args.sh

echo 1 "\"Two words\"" 3

、そして:

#!/bin/sh
#This script is named: test.sh

echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3

スクリプトを呼び出すとき:

sh test.sh $(sh args.sh)

、私は受け取った:

Argument 1: 1
Argument 2: "Two
Argument 3: words"

私が得ると予想される場合:

Argument 1: 1
Argument 2: Two words
Argument 3: 3

出力をコピーしてsh args.sh入力として貼り付けるのはうまくいくsh test.shので、実際にシェルが行うことではないと思います。を呼び出すことで、目的の/予想される出力を得ることができますsh args.sh | xargs sh test.sh

しかし、最初のスクリプト(args.sh)の出力をxargsにパイピングせずにこれを行うための同等の方法があるかどうか疑問に思います。スクリプトを元の順序で呼び出したい。パラメータスクリプトは、パラメータを2番目のスクリプトとして出力します。また、この呼び出しが期待どおりに機能しない理由の説明を探しています。

答え1

問題の一部は、args.shから返された文字列が直接コマンドのように解析されず、$' \t\n'$ IFS()の値によってのみ解析されることです。以下を使用してコマンドトレースをオンにします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"
$

Singleで始まる行に注意してください+。 4つの引数があり、そのうちの'"Two'2'words"'つは別々の引数として解析されます。あなたが望むのは$ IFSを変更することです。

$ 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
$

これはすべての出力に適用されるわけではありません。最善の方法は、args.shの出力を変更して、出力をスペース以外のカンマやコロンなどの文字で区切ることです。

$ 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
$

答え2

変数置換$varまたはコマンド置換を$(cmd)引用符で囲まない場合、結果は次のように変換されます。

  1. 結果の文字列を単語に分割します。スペース(スペース、タブ、および改行の順序)で分割が発生します。これは設定で構成できますIFS12)。
  2. 生成された各単語はグローバルパターンとして扱われ、一部のファイルと一致する場合、その単語はファイル名のリストに置き換えられます。

結果は文字列ではなく文字列のリストです。また、"引用符文字はここに含まれず、文字列拡張の一部ではなく、シェルソース構文の一部です。

シェルプログラミングの一般的な規則は、変数とコマンド置換を削除する理由を知らない限り、常に二重引用符を付けることです。そのため、test.shecho "Argument 1: $1"引数を渡すと問題が発生します。からにtest.sh単語リストを渡す必要がありますが、選択した方法にはコマンド置換が含まれ、単純な文字列の手段のみを提供します。args.shtest.sh

IFS渡されたパラメータに改行が含まれておらず、呼び出しプロシージャのわずかな変更が許可されている場合は、改行のみを含めるように設定できます。args.sh1行に1つのファイル名を出力し、複雑な引用符がないことを確認してください。

IFS='
'
test.sh $(args.sh)
unset IFS

引数に任意の文字を含めることができる場合(おそらく引数として渡すことができないnullバイトを除く)、いくつかのエンコードを実行する必要があります。もちろん、すべてのエンコーディングが可能です。これは、パラメータを直接渡すのと同じではありません。これは不可能です。たとえば、(シェルがサポートしていない場合はargs.sh実際のタブに置き換えます):\t

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

そして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

test.sh文字列のリストを入力として受け入れるように変更することをお勧めします。文字列に改行文字が含まれていないargs.sh | test.sh場合args.sh説明する):

while IFS= read -r line; do
  # process "$line"
done

参照がまったく必要ない別の方法は、最初のスクリプトから2番目のスクリプトを呼び出すことです。


args.sh "$@"

関連情報