12

eval は悪だと誰もが言うので、代わりに $() を使用する必要があります。しかし、引用符の解除が $() 内で同じように処理されない状況に遭遇しました。

背景として、私はスペースを含むファイル パスに頻繁に悩まされてきたので、そのようなパスをすべて引用したいと思います。私のすべての実行可能ファイルがどこから来ているのか知りたいというより多くのパラノイア。さらに偏執的で、自分自身を信頼していないので、これから実行しようとしている作成済みのコマンドを表示できるようになります。

以下では、eval と $() の使用のバリエーションと、コマンド名が引用符で囲まれているかどうか (スペースが含まれている可能性があるため) を試します。

  BIN_LS="/bin/ls"
  thefile="arf"
  thecmd="\"${BIN_LS}\" -ld -- \"${thefile}\""

  echo -e "\n    Running command   '${thecmd}'"
  $($thecmd)

          Running command   '"/bin/ls" -ld -- "arf"'
      ./foo.sh: line 8: "/bin/ls": No such file or directory

  echo -e "\n    Eval'ing command  '${thecmd}'"
  eval $thecmd

          Eval'ing command  '"/bin/ls" -ld -- "arf"'
      /bin/ls: cannot access arf: No such file or directory

  thecmd="${BIN_LS} -ld -- \"${thefile}\""

  echo -e "\n    Running command   '${thecmd}'"
  $($thecmd)

          Running command   '/bin/ls -ld -- "arf"'
      /bin/ls: cannot access "arf": No such file or directory

  echo -e "\n    Eval'ing command  '${thecmd}'"
  eval $thecmd

          Eval'ing command  '/bin/ls -ld -- "arf"'
      /bin/ls: cannot access arf: No such file or directory

  $("/bin/ls" -ld -- "${thefile}")

      /bin/ls: cannot access arf: No such file or directory

だから...これは紛らわしいです。引用符で囲まれたコマンド パスは、$() コンストラクト内を除いてどこでも有効ですか? より短く、より直接的な例:

$ c="\"/bin/ls\" arf"
$ $($c)
-bash: "/bin/ls": No such file or directory
$ eval $c
/bin/ls: cannot access arf: No such file or directory
$ $("/bin/ls" arf)
/bin/ls: cannot access arf: No such file or directory
$ "/bin/ls" arf
/bin/ls: cannot access arf: No such file or directory

$($c) 単純なケースをどのように説明し ますか?

4

3 に答える 3

14

単語を引用するために を使用することは"、Bash との対話の一部です。入力するとき

$ "/bin/ls" arf

/bin/lsプロンプトまたはスクリプトで、コマンドが単語andで構成されていることを Bash に伝えており、二重引用符はそれが単一の単語であるarfことを実際に強調しています。/bin/ls

入力するとき

$ eval '"/bin/ls" arf'

evalコマンドが単語と単語で構成されていることをBashに伝えています"/bin/ls" arf。の目的はeval、その引数が実際の人間の入力コマンドであるかのように装うことであるため、これは実行と同等です。

$ "/bin/ls" arf

プロンプトと"同じように処理されます。

この見せかけはeval;に固有のものであることに注意してください。通常、bash は、何かが実際に人間が入力したコマンドであるかのように振る舞うことはありません。

入力するとき

$ c='"/bin/ls" arf'
$ $c

the$cが置換され、単語分割が行われるため ( Bash リファレンス マニュアルの §3.5.7「Word Splitting」を参照)、コマンドの単語は"/bin/ls"(二重引用符に注意してください!) and arf. 言うまでもなく、これはうまくいきません。(単語分割に加えて、ファイル名の展開なども行われるため、あまり安全ではありません$c。通常、パラメーターの展開は常に二重引用符で囲む必要があり、それができない場合は、コードを書き直す必要があります引用符で囲まれていないパラメーター展開は問題を引き起こしています。)

入力するとき

$ c='"/bin/ls" arf'
$ $($c)

これは以前と同じですが、動作していないコマンドの出力を新しいコマンドとして使用しようとしている点が異なります。言うまでもなく、非稼働コマンドが突然機能するわけではありません。

Ignacio Vazquez-Abrams が彼の回答で述べているように、正しい解決策は配列を使用し、引用を適切に処理することです。

$ c=("/bin/ls" arf)
$ "${c[@]}"

これは、 と の 2 つの要素を持つ配列に設定さcれ、これら 2 つの要素をコマンドの単語として使用します。/bin/lsarf

于 2012-10-08T20:49:46.243 に答える
5

に関するbashのマニュアルページevalから:

eval [arg ...]: 引数が読み取られ、1 つのコマンドに連結されます。次に、このコマンドがシェルによって読み取られて実行され、その終了ステータスが eval の値として返されます。

cが として定義されている場合"\"/bin/ls\" arf"、外側の引用符によって全体が の最初の引数として処理されますeval。これは、コマンドまたはプログラムであると予想されます。evalターゲット コマンドとその引数が別々にリストされるように、引数を渡す必要があります。

この構文は、引数を取るコマンドではないため、$(...)動作が異なります。eval引数を 1 つずつ処理する代わりに、コマンド全体を一度に処理できます。

元の前提に関するメモ:人々がそれevalを悪だと言う主な理由は、ユーザーが提供した文字列をシェルコマンドとして実行するためにスクリプトで一般的に使用されているためです。便利な場合もありますが、これは重大なセキュリティ上の問題です (通常、文字列を実行する前に安全性を確認する実際的な方法はありません)。evalあなたがやっているように、スクリプト内のハードコードされた文字列を使用している場合、セキュリティの問題は当てはまりません。ただし、通常は、コマンド置換用のスクリプト内$(...)または`...`スクリプト内で使用する方が簡単でクリーンであり、実際の使用例は残っていませんeval

于 2012-10-08T21:05:06.833 に答える
5

そもそも意味がないということで。代わりに配列を使用してください。

$ c=("/bin/ls" arf)
$ "${c[@]}"
/bin/ls: cannot access arf: No such file or directory
于 2012-10-08T20:47:34.297 に答える