web-dev-qa-db-ja.com

evalとexecの違いは何ですか?

evalexecはどちらも、コマンドを実行するbash(1)の組み込みコマンドです。

execにはいくつかのオプションがあることもわかりますが、それだけが違いますか?彼らの文脈はどうなりますか?

85
Willian Paixao

evalexecは完全に異なる獣です。 (両方がコマンドを実行するという事実は別ですが、シェルで行うすべてのことを実行します。)

_$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the Shell with the given command.
_

_exec cmd_の機能は、cmdを実行するのとまったく同じですが、現在のシェルが、別のプロセスが実行されるのではなく、コマンドに置き換えられる点が異なります。内部的には、say _/bin/ls_を実行するとfork()が呼び出されて子プロセスが作成され、次に子でexec()が呼び出されて_/bin/ls_が実行されます。一方、_exec /bin/ls_はフォークをしないが、シェルを置き換えるだけです。

比較:

_$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo
_

_$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217
_

_echo $$_は、開始したシェルのPIDを出力し、_/proc/self_をリストすると、シェルから実行されたlsのPIDがわかります。通常、プロセスIDは異なりますが、execを使用すると、シェルとlsは同じプロセスIDを持ちます。また、シェルが置き換えられたため、execに続くコマンドは実行されませんでした。


一方:

_$ help eval
eval: eval [arg ...]
    Execute arguments as a Shell command.
_

evalは、現在のシェルでコマンドとして引数を実行します。つまり、_eval foo bar_は、_foo bar_と同じです。ただし、変数は実行前に展開されるため、シェル変数に保存されたコマンドを実行できます。

_$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo
_

それはしない子プロセスを作成するので、変数は現在のシェルで設定されます。 (もちろん、_eval /bin/ls_は、単純な古い_/bin/ls_と同じ方法で子プロセスを作成します。)

または、シェルコマンドを出力するコマンドを作成することもできます。 _ssh-agent_を実行すると、エージェントがバックグラウンドで起動し、現在のシェルで設定して子プロセス(実行するsshコマンド)で使用できる一連の変数割り当てを出力します。したがって、_ssh-agent_は次のように開始できます。

_eval $(ssh-agent)
_

そして現在のシェルは他のコマンドが継承する変数を取得します。


もちろん、変数cmdに_rm -rf $HOME_のようなものが含まれている場合、_eval "$cmd"_を実行するのはあなたがやりたいことではありません。文字列内のコマンド置換などの処理も​​行われるため、1つ本当にを使用する前に、evalへの入力が安全であることを確認してください。

多くの場合、evalを回避し、誤ってコードとデータを誤った方法で混在させることも回避できます。

136
ilkkachu

execは新しいプロセスを作成しません。 置換現在のプロセスを新しいコマンドで置き換えます。コマンドラインでこれを行った場合、シェルセッションは事実上終了します(そして、おそらくログアウトするか、ターミナルウィンドウを閉じます!)

例えば.

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 

ここではksh(通常のシェル)にいます。 bashを開始し、bashの内部でexec /bin/echoを実行します。 kshプロセスが/bin/echoに置き換えられたため、後でbashに戻されたことがわかります。

27
Stephen Harris

TL; DR

execは、現在のシェルプロセスを新しいコマンドに置き換え、コマンドが指定されていない場合にストリームリダイレクト/ファイル記述子を処理するために使用されます。 evalは、文字列をコマンドとして評価するために使用されます。どちらも、実行時に既知の引数を使用してコマンドを作成および実行するために使用できますが、execは、コマンドの実行に加えて、現在のシェルのプロセスを置き換えます。

exec組み込み

構文:

_exec [-cl] [-a name] [command [arguments]]
_

マニュアルによると、この組み込みコマンドが指定されている場合

...シェルを置き換えます。新しいプロセスは作成されません。引数はコマンドの引数になります。

つまり、PID 1234でbashを実行していて、そのシェル内で_exec top -u root_を実行した場合、topコマンドはPID 1234を持ち、シェルプロセスを置き換えます。

これはどこで役に立ちますか?ラッパースクリプトと呼ばれるもの。このようなスクリプトは、引数のセットを構築するか、環境に渡す変数について特定の決定を行った後、execを使用して、指定されたコマンドで自身を置き換えます。もちろん、ラッパースクリプトが構築したのと同じ引数を提供します道に沿って。

マニュアルには、次のようにも記載されています。

コマンドが指定されていない場合、リダイレクトは現在のシェルで有効になります

これにより、現在のシェルの出力ストリームからファイルに何かをリダイレクトできます。これは、コマンドのstdoutではなくstderrのみを表示したい場合に、ロギングまたはフィルタリングの目的で役立ちます。たとえば、次のようにします。

_bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt 
2017年 05月 20日 星期六 05:01:51 MDT
HELLO WORLD
_

この動作は、ストリームを個別のファイルまたは プロセス にリダイレクトする シェルスクリプトでのログ 、およびファイル記述子を使用する他の 楽しいもの に便利です。

少なくともbashバージョン4.3のソースコードレベルでは、組み込みのexecは_builtins/exec.def_で定義されています。受け取ったコマンドを解析し、コマンドがある場合は、_execute_cmd.c_ファイルで定義されているShell_execve()関数に渡します。

要するに、Cプログラミング言語にはexecコマンドのファミリーがあり、Shell_execve()は基本的にexecveのラッパー関数です。

_/* Call execve (), handling interpreting Shell scripts, and handling
   exec failures. */
int
Shell_execve (command, args, env)
     char *command;
     char **args, **env;
{
_

evalビルトイン

Bash 4.3マニュアルの状態(強調は私が追加):

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

プロセスの置き換えは発生しないことに注意してください。 execの目的がexecve()機能をシミュレートすることであるのとは異なり、組み込みのevalは、ユーザーがコマンドに入力したかのように、引数を「評価」するためだけに機能しますライン。そのため、新しいプロセスが作成されます。

これはどこで役に立ちますか? Gillesが指摘したように この回答では 、「... evalはあまり使用されません。一部のシェルでは、最も一般的な使用法は、実行時まで名前がわからない変数の値を取得することです」 。個人的には、ユーザーが現在使用している特定のワークスペースに基づいてコマンドを実行/評価する必要があるUbuntuのいくつかのスクリプトで使用しました。

ソースコードレベルでは、それは_builtins/eval.def_で定義され、解析された入力文字列をevalstring()関数に渡します。

とりわけ、evalは現在のシェル実行環境に残っているexecは次のことを行えません 変数を割り当てる はできません。

_$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
_
14
新しい子プロセスを作成し、引数を実行して終了ステータスを返します。

えっ? evalの要点は、決して子プロセスを作成しないことです。私が行った場合

eval "cd /tmp"

シェルでは、その後currentシェルがディレクトリを変更します。また、execは新しい子プロセスを作成せず、代わりに、指定されたプロセスの現在の実行可能ファイル(つまり、シェル)を変更します。プロセスID(および開いているファイルなど)は同じままです。 evalとは対照的に、execは、実行可能ファイルを検索またはロードできないか、引数展開の問題で死ぬことができないためにexec自体が失敗しない限り、呼び出し元のシェルに戻りません。 。

evalは基本的に、その引数を連結後の文字列として解釈します。つまり、ワイルドカード展開と引数分割の追加のレイヤーを実行します。 execはそのようなことは何もしません。

5
user180452