コマンド置換によって他のコマンドのパラメータを生成する方法

コマンド置換によって他のコマンドのパラメータを生成する方法

以下から: シェルコマンド置換の予期しない動作

多数の引数を受け入れるコマンドがあります。その中には合法的に空白(およびおそらく他のもの)を含めることができます。

引用符を使用してこれらのパラメータを生成するスクリプトを作成しましたが、出力をコピーして貼り付ける必要があります。

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

私はこれを簡単に単純化しようとしました。

./othercommand $(./somecommand)

上記の予期しない動作が発生しました。問題は、othercommand一部のパラメーターに引用が必要で、変更できない場合にパラメーターを生成するためにコマンド置換を確実に使用できることです。

答え1

私は引用符を使用してこれらのパラメータを生成するスクリプトを作成しました。

シェルの出力が正しく引用されたら、そしてあなたはその結果を信頼しますその後、実行できますeval

配列をサポートするシェルがあると仮定すると、シェルを使用して取得した引数を格納するのが最善です。

./gen_args.sh同様の出力が生成されたら、'foo bar' '*' asdf実行してeval "args=( $(./gen_args.sh) )"呼び出しの結果で配列を埋めることができます。 、、、のargs3つの要素です。foo bar*asdf

"${args[@]}"通常どおり、配列要素を個別に拡張できます。

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(引用符に注意してください。"${array[@]}"変更されていない一意の引数ですべての要素に展開されます。引用符がない場合、配列要素はトークン化の影響を受けます。BashGuideの配列ページ.)

しかし、evalすべてのシェル置換がうまく実行されるため、出力は$HOMEホームディレクトリに展開され、コマンド置換は実際に実行されているシェルでコマンドを実行しますeval。の出力は"$(date >&2)"空の配列要素を生成し、標準出力に現在の日付を印刷します。これはgen_args.sh、信頼できないソース(ネットワーク上の他のホスト、他のユーザーが作成したファイル名)からデータを取得した場合に問題になります。出力には任意のコマンドを含めることができます。get_args.sh悪意のある場合は、何も出力せずに悪意のある命令を直接実行するだけです。)


シェル引用の代替案は、スクリプト出力で別の文字を区切り文字として使用することです。シェルの引用は評価なしで解析するのが難しいからです。実際のパラメータでは、必要ないものを選択する必要があります。

選択#してスクリプトを出力してみましょうfoo bar#*#asdf。今私達は利用できます引用しないコマンド拡張は、コマンドの出力をパラメータに分割します。

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the array
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

IFSスクリプトの他の場所で単語の区切りを使用する場合(デフォルト値に設定する必要がある場合unset IFS)、後でこれを設定する必要がset +fあります。後でワイルドカードを使用する場合は、ワイルドカードも使用できます。

Bashや配列を含む他のシェルを使用しない場合は、位置引数を使用できます。代わりにthenargs=( $(...) )に置き換えて使用してください。 (ここでも引用符が必要です。そうしないと、位置パラメータは単語の分離の影響を受けます。)set -- $(./gen_args.sh)"$@""${args[@]}""$@"

答え2

問題は、somecommandスクリプトがoptionsを出力すると、そのothercommandオプションは実際にはテキストにすぎず、シェルの標準構文解析($IFS現在進行中の操作と適用されるシェルオプションの影響を受けます)。いいえ制御可能)。

somecommand使用する代わりに出力オプションを使用すると、より簡単で安全で強力になります。呼ぶ othercommand。スクリプトsomecommandは次のとおりです。ラッパースクリプトラッパースクリプトは、他のオプションセットで他の同様のツールを呼び出すためのツールを提供する非常に一般的な方法ですothercommand。内部コマンドは実際にはシェルスクリプトラッパーです)。otherscriptfile/usr/bin

bashまたは、配列を使用して、次のように個々のオプションをksh保持zshするラッパースクリプトを簡単に作成できますothercommand

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

それからothercommand(まだラッパースクリプトにあります)、次を呼び出します。

othercommand "${options[@]}"

拡張により、"${options[@]}"配列内の各要素が個別に参照され、別々の引数として表示されますoptionsothercommand

ラッパーユーザーは実際に呼び出していることを忘れてしまいますothercommandいいえスクリプトが単にコマンドラインオプションを出力として生成する場合はothercommandTrueです。

でオプションを保存する/bin/shには、次の手順を$@実行します。

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

(位置引数などをset設定するために使用されるコマンドです。これは標準のPOSIXシェルの配列の内容です。最初の文字はオプションがなく、引数のみがあることを示します)$1$2$3$@--set--本物最初の値が)で始まる場合にのみ必要です-

要素が個別に単語に分割されないように(ファイル名のグロービングも含む)、二重引用符で囲む必要があります$@${options[@]}

答え3

出力が安定していて良いシェル構文の場合は、somecommand次のものを使用できますeval

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

ただし、出力に有効な参照などがあることを確認する必要があります。そうしないと、スクリプトの外でもコマンドが実行される可能性があります。

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

echo rm bar baz test.shこれはスクリプトに渡されず、別々;のコマンドで実行されます。これを強調するために|サラウンドを追加しました。$var


通常、出力が完全に信頼できない場合、somecommand出力を使用してコマンド文字列を安定させることはできません。

関連情報