コマンド置換を介して別のコマンドの引数を生成するにはどうすればよいですか


11

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

私は引数の膨大なリストを取ることができるコマンドを持っています、それらのいくつかは合法的にスペースを含むことができます(そしておそらく他のもの)

これらの引数を引用符で生成できるスクリプトを書きましたが、出力をコピーして貼り付ける必要があります。

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

私は単にすることでこれを合理化しようとしました

./othercommand $(./somecommand)

そして、上記の問題で言及された予期しない動作に遭遇しました。問題はothercommand、いくつかの引数が引用符を必要とし、これを変更できないことを前提として、コマンド置換を使用して引数を確実に生成できるかどうかです。


「信頼できる」という意味に依存します。次のコマンドで画面に表示されるとおりに出力を取得し、それにシェルルールを適用する場合は、おそらくeval使用できますが、通常はお勧めしません。xargsも考慮する必要があります
セルギーコロディアズニー

からの出力somecommandが通常のシェル解析を受けることを期待(期待)します
user1207217

私の答えで言ったように、フィールド分割に他の文字を使用します(など:)...文字が確実に出力に含まれないと仮定します。
Olorin

しかし、引用規則に従わないため、実際には正しくありません。これは、これが何であるかです
-user1207217

2
実際の例を投稿していただけますか?つまり、最初のコマンドの実際の出力と、2番目のコマンドとどのようにやり取りするかです。
nxnev

回答:


10

私はそれらの引数を引用符で生成できるスクリプトを書きました

シェルに対して出力が適切に引用されていて、その出力を信頼できる場合は、その出力で実行できますeval

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

./gen_args.shような出力が生成された場合は'foo bar' '*' asdf、実行して結果をeval "args=( $(./gen_args.sh) )"呼び出す配列を生成できargsます。それは三つの要素だろう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自体が悪意のあるものである場合、何も出力する必要はなく、悪意のあるコマンドを直接実行できます。)


evalなしでは解析が困難なシェルクォートの代わりに、スクリプトの出力でセパレータとして他の文字を使用することもできます。実際の引数では不要なものを選択する必要があります。

を選択#して、スクリプトを出力しますfoo bar#*#asdf。これで、引用符なしのコマンド展開を使用して、コマンドの出力を引数に分割できます。

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

IFSスクリプト内の他の場所で単語の分割に依存している場合は後で設定し直す必要があり(unset IFSデフォルトにするために機能するはずです)、set +f後でグロビングを使用する場合にも使用する必要があります。

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


両方の長所!
Olorin

引用の重要性を示す発言を追加しますか?${args[@]}これは私には
うまくいき

@ user1207217、はい、そうです。配列で"${array[@]}"もでも同じです"$@"。両方とも引用符で囲む必要があります。そうしないと、単語分割によって配列要素がパーツに分割されます。
-ilkkachu

6

問題は、あなたの後ということであるsomecommandスクリプトがためのオプションを出力しothercommand、オプションは本当にただのテキストがあり、シェルの標準解析のなすがままに(何によって影響を受ける$IFSこととオプションは、一般的なケースでこれを使用するだろう、実際には何シェル起こりません制御する)。

を使用somecommandしてオプションを出力する代わりに、を呼び出す ために使用する方が簡単、安全、かつ堅牢othercommandです。somecommandスクリプトは次のようになり、ラッパースクリプト周りothercommandの代わりに、あなたはコマンドラインの一部として、いくつかの特別な方法で呼び出すことを覚えておく必要があろうとヘルパースクリプトのいくつかの並べ替えotherscript。ラッパースクリプトは、他の同様のツールを別のオプションセットで呼び出すツールを提供する非常に一般的な方法です(実際にシェルスクリプトラッパーでfileあるコマンドを確認するだけ/usr/binです)。

bashkshまたはzsh、あなたは簡単に配列を使用するラッパースクリプトはのための個々のオプションを保持する可能性がありothercommandそう等:

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

次にothercommand(まだラッパースクリプト内で)呼び出します。

othercommand "${options[@]}"

の展開により"${options[@]}"options配列の各要素が個別に引用されothercommand、個別の引数として提示されます。

ラッパーのユーザーは、実際にを呼び出しているという事実に気付かないothercommandでしょう。スクリプトが代わりに出力用のコマンドラインオプションを生成しただけの場合、これは当てはまりませんothercommand

では/bin/sh$@オプションを保持するために使用します。

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

othercommand "$@"

set位置パラメータを設定するために使用するコマンドである$1$2$3などが挙げられる。これらは、どのような配列占めている$@初期には、標準のPOSIXシェルでは。--に知らせるためにあるset与えられたはオプション、唯一の引数がないこと。--されて本当に場合にのみ必要最初の値は-)で始まるものです。

これは前後の二重引用符で$@あり${options[@]}、要素が個別に単語分割されていない(およびファイル名が展開されていない)ことに注意してください。


説明してもらえますset --か?
user1207217

@ user1207217回答に説明を追加しました。
Kusalananda

4

場合は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その出力を確実に使用してコマンド文字列を作成することはできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.