bash eval コマンドの使い方|シェルスクリプトでの活用例と注意点

スクリプト設計

Bashのevalコマンドは、文字列をシェルコマンドとして評価・実行できる組み込みコマンドです。eval $(ssh-agent -s)のようなSSH鍵管理や、eval "$(brew shellenv)"のようなツール初期化など、シェル環境のセットアップで欠かせない場面があります。一方で、外部入力をそのまま渡すと任意コード実行につながるリスクもあります。この記事では、evalコマンドの基本と動作の仕組み、実践的な活用例、安全な使い方と代替手段を体系的に解説します。

eval コマンドとは

evalの概要

evalは指定した引数を1つのコマンドとして評価し、それをシェルで実行するコマンドです。単純な例としては、eval [arguments]という形で使い、引数として渡された文字列をその場で解釈・実行します。これにより、変数や動的に生成されたコマンドを実行する際に便利です。

基本の使い方

まずは、evalの基本的な使い方を例で確認してみましょう。

#!/bin/bash

command="echo"
argument="Hello, World!"

eval "$command $argument"

このスクリプトを実行すると、結果として「Hello, World!」が表示されます。この例では、変数に格納されたコマンドや引数をevalが正しく組み合わせて実行しています。

evalの動作の仕組み(二重展開)

evalは引数を2回評価します。1回目は通常の変数展開・コマンド置換、2回目は展開結果をコマンドとして実行します。この「二重展開」がevalの本質です。

#!/bin/bash

greeting="Hello"
cmd='echo "$greeting, World!"'

# eval なし: 文字列をそのままechoする
echo "$cmd"         # → echo "$greeting, World!"

# eval あり: 二重展開で変数を解決してからコマンド実行
eval "$cmd"         # → Hello, World!

1回目の展開で $cmd の内容(echo "$greeting, World!")が取り出され、2回目の評価でその文字列がシェルコマンドとして解釈・実行されます。この仕組みにより、文字列の中の変数参照まで解決できるのがevalの特徴です。

evalの活用法

evalは特に動的な変数名やコマンドを扱う際にその真価を発揮します。以下は、少し高度な例です。

シェル変数の動的評価

場合によっては、変数名そのものを動的に処理したいことがあります。

#!/bin/bash

prefix="var"
suffix=1
value="dynamic variable"

eval "${prefix}${suffix}=\"$value\""

echo "$var1"

このスクリプトでは、変数名を動的に組み立ててvar1へ代入しています。evalがなければこのような動的変数の操作は面倒ですが、evalを使うことで簡潔に行えます。

コマンドの動的生成

ほかにも、スクリプト内部で状況によって異なるコマンドを実行するケースがあります。

#!/bin/bash

create_command() {
  local base_command="ls"
  local options="-l -a"
  eval "$base_command $options /"
}

create_command

この例では、条件に応じてコマンドを構築し、evalで実行しています。これにより、柔軟で再利用可能なスクリプトを書くことができます。

evalコマンドの安全性と注意点

eval は強力なコマンドですが、その分セキュリティリスクも大きく、利用には十分な注意が必要です。特に以下の点を理解しておくことが大切です。

任意コード実行のリスク

eval は文字列をそのままシェルコマンドとして実行するため、入力内容によっては意図しないコマンドが実行されてしまう可能性があります。
たとえば、外部から渡された変数を eval にそのまま展開すると、攻撃者が細工した文字列を埋め込むことでシステムを操作される危険があります。

# 危険な例
user_input="; rm -rf /"
eval "echo $user_input"

このような場合、eval はユーザー入力に含まれる悪意のあるコマンドを実行してしまうため、非常に危険です。

デバッグの難しさ

evalは文字列をコマンドとして実行するので、シンタックスエラーや実行時エラーが発生した場合のトレースが難しくなることがあります。

デバッグを行う際は、set -xによってスクリプトの実行をトレースし、実際に評価・実行されるコマンドが何かを確認することが重要です。

セキュリティ面での考慮点

  • ユーザー入力を直接evalに渡さない
    外部入力や未検証の変数を eval で処理するのは避けましょう。
  • 代替手段を優先する
    変数展開や関数呼び出しで解決できる場合は eval を使わずに済ませるべきです。
  • 必要な場合は入力をバリデーションする
    正規表現で許可する文字を限定するなど、意図しない文字列が入らないようにします。

evalを避ける実装例

以下は eval を使わずにコマンドを実行する安全な方法の一例です。

# evalを使わずに配列を利用する例
cmd=("ls" "-l" "/home")
"${cmd[@]}"

このように、配列を利用すれば動的にコマンドを組み立てつつ、安全に実行することができます。


ポイント:

  • eval は便利だが「任意コード実行」という大きなリスクを伴う。
  • 外部入力を扱うときは特に使用を避けるべき。
  • 代替手段(配列や関数)を優先して、安全にスクリプトを書くことが重要です。

evalを使わずに書ける代替方法

eval は動的にコマンドを実行できる便利な仕組みですが、セキュリティリスクを伴うため可能な限り代替手段を使うことが推奨されます。ここでは代表的な方法を紹介します。

1. bash -c を利用する

bash -c は文字列を新しいシェルに渡して実行する方法です。eval と似ていますが、変数展開を明示的に制御できるため、安全性を高められます。

command="ls -l /home"
bash -c "$command"

この方法では eval のように多重展開が行われないため、意図しない挙動が減ります。

2. 配列を使って引数を安全に扱う

配列を使うことで、コマンドと引数を安全に分離できます。特に動的に引数を組み立てたい場合に有効です。

cmd=("grep" "error" "/var/log/syslog")
"${cmd[@]}"

この書き方なら、スペースを含む引数も正しく処理され、eval を使う必要がありません。

3. 関数化して動的に呼び出す

複数の処理を分けたい場合は、関数化して呼び出すのが安全です。関数名を変数に持たせれば、eval なしで柔軟に実行できます。

# 関数定義
list_files() {
    ls -l "$1"
}

show_date() {
    date "+%Y-%m-%d"
}

# 関数名を動的に指定
action="list_files"
$action "/home"

このようにすれば、実行対象を動的に切り替えつつ、安全に制御できます。

4. case文や連想配列を使う

関数やコマンドを切り替える場合、case 文や連想配列を利用するのも効果的です。

declare -A actions=(
  ["list"]="ls -l"
  ["date"]="date"
)

choice="list"
${actions[$choice]}

5. declare -n(ネームリファレンス)を使う(Bash 4.3以降)

Bash 4.3以降では declare -n でネームリファレンス(別名参照)を宣言できます。変数名を間接的に参照する用途では、evalよりも安全でわかりやすい代替手段です。

#!/bin/bash

# eval を使った間接参照(古い方法)
varname="greeting"
greeting="Hello"
eval "echo \$$varname"   # → Hello

# declare -n を使った間接参照(推奨)
declare -n ref="$varname"
echo "$ref"              # → Hello

# 関数内での活用例
set_value() {
    declare -n target="$1"
    target="$2"
}

set_value myvar "World"
echo "$myvar"   # → World

declare -nは変数名の二重展開を内部で安全に処理するため、evalのような任意コード実行リスクがありません。ただしBash 4.3未満の環境(古いmacOSのデフォルトシェルなど)では利用できないため、環境の確認が必要です。


ポイント:

  • eval の代わりに bash -c / 配列 / 関数化 / 連想配列 / declare -n を利用すると、安全に同等の処理が実現可能。
  • Bash 4.3以降の環境では、変数の間接参照に declare -n が最も安全で推奨されます。
  • 動的にコマンド引数を組み立てる場合は「配列」が最も安全で推奨されます。

実践ユースケース

ssh-agent の起動と環境変数設定(最頻出の用途)

evalが実際のシェルスクリプトで最もよく使われる場面が、ssh-agentの起動です。ssh-agentは起動すると環境変数(SSH_AUTH_SOCKSSH_AGENT_PID)を設定するシェルコマンドを標準出力に出力しますが、それをそのまま実行するにはevalが必要です。

# ssh-agent を起動し、環境変数を現在のシェルに設定する
eval "$(ssh-agent -s)"

# 出力イメージ(ssh-agentが出力するコマンド文字列):
# SSH_AUTH_SOCK=/tmp/ssh-XXXXXXX/agent.1234; export SSH_AUTH_SOCK;
# SSH_AGENT_PID=1234; export SSH_AGENT_PID;
# echo Agent pid 1234;

# 秘密鍵を登録
ssh-add ~/.ssh/id_rsa

evalなしで$(ssh-agent -s)だけを実行しても、子プロセスで変数が設定されるだけで現在のシェルには反映されません。evalを使うことで、export文を含む文字列を現在のシェルで実行できます。詳しくはssh-agent の使い方も参照してください。

開発ツールの初期化(rbenv / nvm / Homebrew)

rbenv、nvm、Homebrewなどのツールも、シェル環境を設定するためにevalパターンを使います。これらは~/.bashrc~/.bash_profileに書いておく定番の初期化コードです。

# rbenv の初期化(PATH設定・シェル関数の定義)
eval "$(rbenv init -)"

# Homebrew の環境変数設定(Apple Silicon Mac)
eval "$(/opt/homebrew/bin/brew shellenv)"

# nvm の初期化
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

これらのコマンドがevalを必要とする理由は、ツールが出力するシェルコードに export 文や関数定義が含まれており、それを現在のシェルセッションに反映させる必要があるからです。

for ループと組み合わせた動的変数アクセス

ループ内で動的に変数名を生成して代入・参照するパターンでもevalが活用されます。

#!/bin/bash

# for ループで動的変数に代入
for i in 1 2 3; do
    eval "server_${i}='192.168.1.${i}'"
done

# 動的変数を参照して処理
for i in 1 2 3; do
    eval "ip=\$server_${i}"
    echo "サーバー${i}: ${ip}"
done
# 出力:
# サーバー1: 192.168.1.1
# サーバー2: 192.168.1.2
# サーバー3: 192.168.1.3

ただし、Bash 4.3以降では後述するdeclare -n(ネームリファレンス)を使うほうが安全です。配列が使える場面では配列のほうがシンプルです。詳しくは間接展開と printf -v|変数名を安全に参照も参照してください。

環境変数を展開してコマンド実行

eval がよく使われるのは、環境変数を展開してコマンドを実行する場面です。
たとえば、変数にコマンドやオプションを格納しておき、実行時にまとめて展開できます。

OPTIONS="-l -a"
COMMAND="ls $OPTIONS"
eval $COMMAND

このように書けば、ls -l -a が実行されます。ただし、外部から渡された文字列を展開する場合はセキュリティリスクに注意が必要です。

動的に関数を呼び出す

複数の処理を関数に分けて定義しておき、変数の内容に応じて動的に関数を呼び出すときにも eval が利用されます。

build() {
    echo "Building..."
}
deploy() {
    echo "Deploying..."
}

ACTION="deploy"
eval $ACTION

この例では ACTION に応じて関数が呼び出されます。ただし、安全性を考えるなら eval ではなく変数展開のみで呼び出せる書き方を選ぶのが望ましいです。

makefile / CI環境での利用例

makefile や CI/CD 環境では、環境変数や設定ファイルから動的にコマンドを組み立てるケースがあります。eval を使うことで、柔軟に変数展開を行えるのが特徴です。

CMD = echo "Deploy to $(ENV)"
run:
	eval $(CMD)

CI 環境でも、ジョブごとに環境変数を切り替えてコマンドを生成する際に使われることがあります。ただし、本番環境では意図しないコマンドが実行される危険があるため、必要性を見極めて利用することが重要です。

evalを安全に使うためのガイドライン

  • なるべくevalの使用を避け、本当に必要な場合に限り使用する。
  • ユーザー入力を含める際には、正規表現やホワイトリストを使って入力を検証する。
  • 可能な限り、直接シェルコマンドを組み立てず、変数やオプションを適切にサニタイズする。
  • デバッグは入念に行い、エラーメッセージをしっかりと解析してください。

evalを使うべきケース・避けるべきケースの判断基準

状況推奨する方法
ssh-agent やツールの初期化コードを現在のシェルに反映eval を使う(必須)
動的なコマンド引数の組み立て配列 ("${cmd[@]}") を使う
変数名を間接的に参照(Bash 4.3以降)declare -n を使う
変数名を間接的に参照(Bash 4.3未満)${!varname} または eval
外部入力を含む文字列の実行絶対に避ける
スクリプト内の固定コマンド実行直接コマンドを書く

関連記事

まとめ

evalは、文字列をシェルコマンドとして評価・実行するBashの組み込みコマンドです。eval $(ssh-agent -s)のようなツール初期化や、動的な変数名への代入といった「通常の変数展開では解決できない」場面で真価を発揮します。

一方で、外部入力を含む文字列をevalに渡すと任意コード実行につながるため、利用には十分な注意が必要です。動的なコマンド引数の組み立てには配列、変数の間接参照にはBash 4.3以降のdeclare -nを優先し、evalは本当に必要なケースに限定して使いましょう。

  • evalの本質は「二重展開」— 引数を展開してからコマンドとして再評価する
  • 最頻出の正当な用途は eval $(ssh-agent -s) のような環境変数の現在シェルへの反映
  • 外部入力を直接渡すのは厳禁。代替手段(配列・関数・declare -n)を優先する
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする