web-dev-qa-db-ja.com

コマンドとしての変数。 eval対bash -c

誰かが作成したbashスクリプトを読んでいて、作成者がevalを使用して変数をコマンドとして評価していないことに気付きました
著者が使用した

bash -c "$1"

の代わりに

eval "$1"

私はevalの使用が推奨される方法であり、とにかく高速だと思います。本当?
2つの間に実際的な違いはありますか? 2つの間の顕著な違いは何ですか?

42
whoami

_eval "$1"_は、現在のスクリプトでコマンドを実行します。現在のスクリプトからのシェル変数の設定と使用、現在のスクリプトの環境変数の設定、現在のスクリプトからの関数の設定と使用、現在のスクリプトの現在のディレクトリ、umask、制限およびその他の属性の設定などを行うことができます。 _bash -c "$1"_は、環境変数、ファイル記述子、およびその他のプロセス環境を継承する(ただし、変更を送信しない)完全に別のスクリプトでコマンドを実行しますが、内部シェル設定(シェル変数、関数、オプション、トラップ)を継承しませんなど)。

別の方法として、サブシェルでコマンドを実行する_(eval "$1")_があります。これは、呼び出し元のスクリプトからすべてを継承しますが、変更を送信しません。

たとえば、変数dirがエクスポートされておらず、_$1_が_cd "$foo"; ls_であるとすると、次のようになります。

  • _cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd_は、_/somewhere/else_の内容をリストし、_/somewhere/else_を出力します。
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdは、_/somewhere/else_の内容をリストし、_/starting/directory_を出力します。
  • _cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd_は、_/starting/directory_の内容をリストし(_cd ""_は現在のディレクトリを変更しないため)、_/starting/directory_を出力します。

間の最も重要な違い

bash -c "$1" 

そして

eval "$1"

前者はサブシェルで実行され、後者は実行されないということです。そう:

set -- 'var=something' 
bash -c "$1"
echo "$var"

出力:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

出力:

something

しかし、なぜこのように実行可能ファイルbashを使用するのか、私にはわかりません。呼び出す必要がある場合は、POSIX保証の組み込みshを使用してください。または、環境を保護する場合は(subshell eval)

個人的には、何よりもシェルの.dotを好みます。

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

出力

something1
something2
something3
something4
something5

しかし、あなたはそれが必要ですかAT ALL?

実際にどちらかを使用する唯一の原因は、変数が実際に別の変数を割り当てたり評価したりする場合、またはWord分割が出力にとって重要な場合です。

例えば:

var='echo this is var' ; $var

出力:

this is var

これは機能しますが、echoが引数の数を気にしないためです。

var='echo "this is var"' ; $var

出力:

"this is var"

見る?シェルの$varの展開の結果がquote-removalに対して評価されないため、二重引用符が付きます。

var='printf %s\\n "this is var"' ; $var

出力:

"this
is
var"

ただし、evalまたはshの場合:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

出力:

this is var
this is var

evalまたはshを使用すると、シェルは展開の結果で2回目のパスを取得し、それらを潜在的なコマンドとしても評価するため、引用符が違いを生みます。次のこともできます:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

出力

this is var
23
mikeserv

私は簡単なテストをしました:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(はい、わかっています。ループを実行するためにbash -cを使用しましたが、違いはないはずです)。

結果:

eval    : 1.17s
bash -c : 7.15s

したがって、evalの方が高速です。 evalのmanページから:

Evalユーティリティは、引数を連結し、それぞれを文字で区切ってコマンドを作成します。構築されたコマンドは、シェルによって読み取られ、実行されます。

bash -cはもちろん、bashシェルでコマンドを実行します。注:echobashに組み込まれたシェルであり、新しいプロセスを開始する必要がないため、/bin/echoを使用しました。 /bin/echoテストをbash -cからechoに置き換えると、1.28sかかりました。それはほぼ同じです。ただし、evalは実行可能ファイルを実行する方が高速です。ここでの主な違いは、evalは新しいシェルを開始しない(現在のシェルでコマンドを実行する)のに対し、bash -cは新しいシェルを開始し、新しいシェルでコマンドを実行することです。新しいシェルの開始には時間がかかるため、bash -cevalよりも低速です。

5
PlasmaPower