eval
とexec
はどちらも、コマンドを実行するbash(1)の組み込みコマンドです。
exec
にはいくつかのオプションがあることもわかりますが、それだけが違いますか?彼らの文脈はどうなりますか?
eval
とexec
は完全に異なる獣です。 (両方がコマンドを実行するという事実は別ですが、シェルで行うすべてのことを実行します。)
_$ 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
を回避し、誤ってコードとデータを誤った方法で混在させることも回避できます。
exec
は新しいプロセスを作成しません。 置換現在のプロセスを新しいコマンドで置き換えます。コマンドラインでこれを行った場合、シェルセッションは事実上終了します(そして、おそらくログアウトするか、ターミナルウィンドウを閉じます!)
例えば.
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
ここではksh
(通常のシェル)にいます。 bash
を開始し、bashの内部でexec /bin/echo
を実行します。 ksh
プロセスが/bin/echo
に置き換えられたため、後でbash
に戻されたことがわかります。
exec
は、現在のシェルプロセスを新しいコマンドに置き換え、コマンドが指定されていない場合にストリームリダイレクト/ファイル記述子を処理するために使用されます。 eval
は、文字列をコマンドとして評価するために使用されます。どちらも、実行時に既知の引数を使用してコマンドを作成および実行するために使用できますが、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;
{
_
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
_
新しい子プロセスを作成し、引数を実行して終了ステータスを返します。
えっ? eval
の要点は、決して子プロセスを作成しないことです。私が行った場合
eval "cd /tmp"
シェルでは、その後currentシェルがディレクトリを変更します。また、exec
は新しい子プロセスを作成せず、代わりに、指定されたプロセスの現在の実行可能ファイル(つまり、シェル)を変更します。プロセスID(および開いているファイルなど)は同じままです。 eval
とは対照的に、exec
は、実行可能ファイルを検索またはロードできないか、引数展開の問題で死ぬことができないためにexec
自体が失敗しない限り、呼び出し元のシェルに戻りません。 。
eval
は基本的に、その引数を連結後の文字列として解釈します。つまり、ワイルドカード展開と引数分割の追加のレイヤーを実行します。 exec
はそのようなことは何もしません。