eval / exec
| 対応: | 全Linux | |
|---|---|---|
| macOS(2001 Cheetah) | ||
| Bash 1.0(1989) |
eval は文字列をコマンドとして評価・実行する組み込みコマンドです。exec は現在のシェルプロセスを別のコマンドに置き換えるか、ファイルディスクリプタを操作します。どちらも汎用性の高い機能ですが、使い方を誤るとセキュリティ上の問題やスクリプトの予期しない終了を引き起こすため、仕組みをよく理解した上で使うことが重要です。
構文
eval は引数として渡された文字列を連結し、シェルコマンドとして評価・実行します。
eval [文字列 ...]
exec にコマンドを渡すと、現在のシェルプロセスがそのコマンドに置き換わります(新しいプロセスは作成されません)。
exec コマンド [引数 ...]
exec をコマンドなしでリダイレクトだけ指定すると、現在のシェルの標準入出力を切り替えます。
exec >ファイル 2>&1 exec <ファイル
eval と exec の比較
| コマンド | 動作 | プロセス | 主な用途 |
|---|---|---|---|
| eval | 文字列をコマンドとして評価・実行 | 現在のシェルで実行(サブシェルなし) | 動的変数名・コマンド文字列の組み立て |
| exec コマンド | 現在のシェルを別コマンドに置き換え | 新プロセスを作らず上書き | スクリプト末尾でコマンドに引き継ぐ |
| exec リダイレクト | 現在のシェルの FD を切り替え | プロセス変化なし | スクリプト全体のログ出力先を変更 |
サンプルコード
eval を使った動的変数名の参照です。変数名を文字列で組み立てて、その変数の値を取り出します。
time_leap.sh
#!/bin/bash
# 世界線ごとのメッセージを動的に参照する
msg_alpha="α世界線: シュタインズゲートへの道"
msg_beta="β世界線: 別の運命"
msg_steinsgate="Steins;Gate世界線: 目標に到達"
for worldline in alpha beta steinsgate; do
eval "echo \$msg_${worldline}"
done
実行するコマンドは次の通りです。
bash time_leap.sh α世界線: シュタインズゲートへの道 β世界線: 別の運命 Steins;Gate世界線: 目標に到達
eval でコマンド文字列を組み立てて実行する例です。条件によって異なるコマンドを動的に構築します。
gadget_launcher.sh
#!/bin/bash
# 未来ガジェットを動的に起動するスクリプト
gadget_no=8
action="send"
target="okabe@example.com"
# コマンド文字列を動的に組み立てる
cmd="mail -s 'Future Gadget No.${gadget_no} activated' ${target}"
echo "実行するコマンド: $cmd"
eval "$cmd <<< 'D-Mail: operation complete'"
exec でスクリプト全体の出力をログファイルにリダイレクトする例です。exec 以降の全コマンドの標準出力と標準エラー出力がファイルに書き込まれます。
reading_steiner.sh
#!/bin/bash # スクリプト全体の出力をログファイルに記録する LOG_FILE="reading_steiner.log" # 以降の stdout と stderr をすべてログファイルへ exec >"$LOG_FILE" 2>&1 echo "オペレーション・スタート: $(date)" echo "世界線変動率: 0.571024%" # 何らかの処理 ping -c 1 example.com echo "オペレーション終了: $(date)"
実行するコマンドは次の通りです。
bash reading_steiner.sh cat reading_steiner.log オペレーション・スタート: Thu Apr 9 10:00:00 JST 2026 世界線変動率: 0.571024% PING example.com (93.184.216.34): 56 data bytes 64 bytes from 93.184.216.34: icmp_seq=0 ttl=56 time=150.3 ms オペレーション終了: Thu Apr 9 10:00:01 JST 2026
exec でプロセスを別のコマンドに置き換える例です。exec 以降のスクリプトコードは実行されません。
worldline_shift.sh
#!/bin/bash # 現在のシェルプロセスを /bin/bash に置き換える echo "置き換え前: PID=$$" # 現在のシェルが /bin/bash に置き換わる # この exec より後のコードは実行されない exec /bin/bash --login echo "この行は実行されない"
実行するコマンドは次の通りです。
bash worldline_shift.sh 置き換え前: PID=12345 bash-5.2$
概要
eval はシェルの二重評価(double evaluation)を行います。まず変数展開・コマンド置換などが行われて文字列が確定し、その確定した文字列をもう一度コマンドとして解釈・実行します。この二重評価により、通常の構文では書けない動的なコードを実現できます。
exec コマンド を実行すると、現在のシェルプロセス(PID)がそのコマンドに上書きされます。fork(子プロセス生成)を行わないため、スクリプトの末尾で exec を使うと余分なシェルプロセスを消費せずに別のプログラムに処理を引き継げます。
exec リダイレクト(コマンドなし)はプロセスの置き換えを行わず、現在のシェルのファイルディスクリプタ(FD)だけを切り替えます。スクリプト冒頭に exec >logfile 2>&1 と書くと、以降の全出力をログファイルに向けられます。
セキュリティ上の注意: eval にユーザー入力を渡さない
eval に外部からの入力(ユーザー入力・ファイル内容・環境変数)を直接渡すと、任意のコマンドを実行されるインジェクション脆弱性が生じます。これは Bash スクリプトで最も危険なパターンのひとつです。
gadget_launcher_ng.sh
#!/bin/bash
# 危険な例: ユーザー入力をそのまま eval に渡す
read -p "名前を入力してください: " user_input
eval "echo こんにちは、${user_input}さん"
上記のスクリプトに悪意ある入力を渡すと、任意のコマンドが実行されます。
bash gadget_launcher_ng.sh 名前を入力してください: Okabe; rm -rf /tmp/important_data; echo hacked こんにちは、Okabe hacked
上記では rm -rf /tmp/important_data と echo hacked がセミコロンで区切られ、eval によって実行されます。ユーザー入力を扱う場合は eval を避け、配列や安全な文字列処理を使います。
#!/bin/bash
# 安全な例: eval を使わずに配列でコマンドを組み立てる
cmd_args=("echo" "こんにちは、${user_input}さん")
"${cmd_args[@]}"
よくあるミス
よくあるミス1: eval のインジェクション脆弱性
eval の危険性は意図しないところから入力が来る場合にも生じます。スクリプトの引数($1)や環境変数を eval に渡すパターンは特に要注意です。
check_var_ng.sh
#!/bin/bash # 危険: スクリプト引数をそのまま eval に渡す target_var=$1 eval "echo \$$target_var"
修正後は次の通りです。
bash check_var_ng.sh 'PATH; cat /etc/passwd' /usr/local/bin:/usr/bin:/bin root:x:0:0:root:/root:/bin/bash ...
引数に ; を含む文字列を渡すと、変数参照と無関係なコマンドが実行されます。変数名として使う場合は、英数字とアンダースコアだけを受け付けるバリデーションが必要です。
#!/bin/bash
# 安全: 変数名のバリデーションを行ってから参照する
target_var=$1
# 変数名として有効な文字(英数字・アンダースコア)のみ許可
if [[ ! "$target_var" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "エラー: 無効な変数名です: $target_var" >&2
exit 1
fi
eval "echo \$$target_var"
よくあるミス2: exec 後は元のスクリプトに戻れない
exec でプロセスを置き換えると、現在のシェルプロセスは消滅します。exec より後のコードは一切実行されず、元のスクリプトに戻ることもできません。
worldline_shift.sh
#!/bin/bash
# exec 後のコードは実行されない
echo "処理を開始します"
echo "ログを記録します"
exec /usr/bin/python3 -c "print('Pythonに切り替わりました')"
# 以下は実行されない
echo "この行は実行されません"
cleanup_function
修正後は次の通りです。
bash worldline_shift.sh 処理を開始します ログを記録します Pythonに切り替わりました
「exec の後に後片付けをしたい」という意図でコードを書くと、後片付けが実行されずリソースリークが起きます。クリーンアップ処理は exec より前に完了させるか、trap コマンドと組み合わせる方法があります(trap はプロセス置き換え後には機能しない点に注意)。
ファイルディスクリプタのリダイレクトだけを行う exec リダイレクト(コマンドなし)はプロセスを置き換えないため、スクリプトは継続します。
fd_redirect.sh
#!/bin/bash # exec リダイレクトはプロセスを置き換えない exec >"reading_steiner.log" 2>&1 # FD の切り替えのみ echo "この行は実行される(ログファイルに書かれる)" echo "スクリプトは継続します"
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。