コマンド
exec
現在実行中のシェルに代わり、指定したコマンドを実行する。実行中のシェルのリダイレクトを設定する
最終更新 2019-01-27
UNIX/Linux の exec コマンドは、現在実行中のシェルに代わり、指定したコマンドを実行する sh・bash・csh・tcsh などの内部コマンド (組み込みコマンド・ビルトインコマンド) である。シェルスクリプトから異なるコマンドを実行する際に、プロセス数を抑えてシステムリソースを節約する目的で使用される。また、sh・bash においては実行中のシェルのリダイレクトを変更する。
● exec コマンドの概要
まずは基礎知識から。sh・bash・csh・tcsh などのシェルから
などとコマンドを実行すると、シェルは以下のことを行う。
システムコール fork(2) を呼び、子プロセスを生成する。
子プロセスは ls を exec(2) する。
親プロセスであるシェルは、ls の実行が完了するのを待つ (wait する)。
一方、
と exec を使うと、シェルは fork(2) せず、いきなり ls コマンドを exec(2) する (シェルの内部コマンド exec(1) を実行すると、内部でシステムコール exec(2) が呼ばれるということ)。シェルのプロセス情報は ls のプロセスの情報で上書きされる。なお、子プロセスは生成されないので、シェルと ls のプロセス ID は同じになる。
実際にやってみるとわかるが、ssh でログインした状態で、
とすると一瞬 ls の結果が実行されるが、シェルは終了してしまう。これは sshd はシェルの終了を待っているが、シェルのプロセスは ls のプロセスに上書きされ、なおかつ ls のプロセスはすぐに終了してしまうので、ls の終了後、即座に sshd も終了してしまうわけである。
同様に、X Window System 上で kterm などのターミナルを実行中に
とすると、kterm は閉じてしまう。これも、kterm はシェルの終了をを待っているが、シェルのプロセスは ls のプロセスに上書きされるので、ls が終了すると kterm も終了してしまうということだ。
exec コマンドのつかいどころは以下のとおり。
● 例 1. ~/.xinitrc や ~/.xsession
~/.xinitrc や ~/.xsession では、通常 kterm などの端末エミュレータを数個呼び、twm や fvwm などのウィンドウマネージャを起動する。ここで
#!/bin/sh
kterm -g 102x60+10+10 &
kterm -g 90x52-10-10 &
twm
ではなく、
#!/bin/sh
kterm -g 102x60+10+10 &
kterm -g 90x52-10-10 &
exec twm # ここで exec を使う
と書くとよい。前者は twm を実行している間 (≒ユーザがログインしている間)、sh がずっと twm の終了を待っている。しかし後者は sh のプロセスは twm のプロセスで上書きされてしまうので、前者よりプロセス数が一つ少なくてすむ。
● 例 2. wrapper スクリプト
例えば FreeBSD 4.7-RELEASE の /usr/bin/pagesize。中身は以下のような sh スクリプトで、sysctl を実行するだけの wrapper (ラッパー) スクリプトである。
#!/bin/sh -
PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH
exec sysctl -n hw.pagesize
これも sysctl を実行する間、/bin/sh を待たせなくてよいという効果がある。
同様に、FreeBSD 4.7-RELEASE で調べてみると、
/usr/bin/clear は tput を exec する
/usr/bin/sockstat は netstat を exec する
/usr/bin/perldoc は perl を exec する
gs 付属のプログラム (pf2afm、ps2pdf など) は gs を exec する
などなど、多くの wrapper スクリプトが exec でコマンドを呼んでいる。
● 例3. kubernets のコンテナ内で実行するコマンド
kubernets のコンテナ内で実行するコマンド記述について、sh で複数コマンドを実行する場合、最後に実行するコマンドを exec で呼ぶことで、sh の分のプロセスを節約できる、かもしれない (試していない)。
command: ["/bin/sh"]
args: ["-c", "command1 && command2 && command3 && exec command4"]
● exec コマンドのまとめ
exec コマンドによる効果は以下の通り。
fork(2) で子プロセスを生成しなくてすむ。fork(2) はかなり重いシステムコールである。fork(2) が重いので、vfork(2) が作られたくらい重い。ただ、現代の UNIX では copy-on-write の効果により、以前ほど fork(2) が重いわけではない。
親プロセスが子プロセスの終了を待つ必要がない。1 プロセス分浮くわけである。
exec コマンドを 1回使うことによる速度向上・メモリ量削減効果はたいしたことはない。ただし数百回・数千回分続くようなら、塵も積もれば…となる。例えば大学のような「1つの大型サーバと数十台の X 端末」という状況で、上記の例 1 を考えると、
の部分を
とするだけで、一気に数十プロセスが浮くわけである。
下記のように複数コマンドで同じリダイレクトをしたい場合、
command1 1>>log.txt 2>/dev/null
command2 1>>log.txt 2>/dev/null
command3 1>>log.txt 2>/dev/null
exec コマンドを使うと、シェル内でリダイレクトを設定したままとすることができる。
exec 1>>log.txt 2>/dev/null
command1
command2
command3
元に戻す必要がある場合、n>&m とすることで、ディスクリプタ n を m に退避し、後から元に戻すこともできる。
exec 3>&1 4>&2
exec 1>stdout.log 2>stderr.log
ls exist-file no-exist-file
exec 1>&3 2>&4
ls exist-file no-exist-file
標準入力に対しても exec は可能である。
とすると下記と同じことが実現できる。
以下は、標準入力を切り替え、元に戻す例である。
# 標準入力から 1行取得
read v
echo $v
# 標準入力を 3 に退避し、標準入力を /etc/hosts に切り替える。
exec 3>&0
exec < /etc/hosts
cat -n
# 標準入力を元に戻す
exec 0>&3
# 再度標準入力から 1行取得
read v
echo $v
下記のように実行すると、標準入力を一時的に切り替えできていることがわかる。
% (echo aaa ; echo bbb) | sh [上記スクリプト]
関数
exec
コマンドを実行
exec 系のライブラリ関数には
execl
execle
execlp
exect
execv
execvp
execvP
がある。これらは最終的にシステムコール execve(2) を呼び出す。