Shellでevalの有用なユースケースは何ですか?

Shellでevalの有用なユースケースは何ですか?

eval is evilShell / POSIXの世界やPythonなどの他の言語でよく聞くことができます。

ところで本当に役に立たないのだろうか?それとも不思議で文書化されておらず、興味深いまたは有用なユースケースはありますか?

答えがsh/bash中心的であれば良いでしょうが、他のシェルとも関係があれば良いでしょう。

PS:私は素晴らしい 知っている なぜ評価を検討してくださいevil

答え1

私は2つの...一般的な...使用事例を知っていますeval

  1. パラメータ処理getopt:

    [T]この実装は引用された出力を生成することができ、これはシェルで再解釈されるべきです(通常はevalコマンドを使用して)

  2. 設定するSSHエージェント:

    [T]エージェントは、呼び出しシェルで評価できる必須シェルコマンド(sh(1)またはcsh(1)構文を生成できます)を印刷します(たとえば、eval `ssh-agent -s`sh(1)またはksh(1)などのBourneタイプシェルの場合)eval `ssh-agent -c`csh(1)と派生。

どちらの用途にも異なるオプションがあるかもしれませんが、どちらにも驚かないでしょう。

答え2

Bash & Co. slicing などの拡張子なしで POSIX シェルから最後の引数を取得するには、次のように${@: -1}します。

eval "v=\${$#}" 

$#これはシェルの内部であり、スクリプト/関数の引数の数だけを含む可能性があるため、不快なトリックに脆弱ではありません。

これは私が思いついたものではありません。スティーブン・チャジェラス コメント。でも言及この回答evalを使用しない理由と時期は何ですか?

答え3

より良い選択肢が見つかりませんでしたが、eval作業をきちんと完了した「実際の」ユースケースのいくつかの例です。


「条件付き拡張」のユースケース。ここでは$rmsg_pfx、値がある場合にのみリダイレクトを使用したいと思います。

eval 'printf -- %s%s\\n "$rmsg_pfx" "$line" '"${rmsg_pfx:+>&2}"

なしではこれを行うことはできません。evalこれは、ビットがリダイレクトではなく>&2パラメータに拡張されるためです。printf

$rmsg_pfxnullであることを確認するために行をコピーすることができますが、それは…まあ…コードの重複になります。


リダイレクトについて言えば、「間接」のユースケースとして、私は{varname}>&...リダイレクト構文に頼るのが好きです。私は次のようにPOSIXlyをモックします。

# equivalent of bash/ksh `exec {rses_fd0}>&- {rses_fd1}<&-` redirection syntax
eval "exec $rses_fd0>&- $rses_fd1<&-"

上記はfdsを閉じることです。同様に、fdsオープンをシミュレートするために同様の間接的な作業を行っています。明らかにおよび$rses_fd0$rses_fd1スクリプトの内部変数であり、最初から最後までスクリプトによって完全に制御されます。


eval時々、他のシェルを邪魔することなく、特定のシェルのためのシェルコードの断片を単に「保護」する必要があるかもしれません。

たとえば、次のコードは、一部のシェル固有の最適化も含む移植可能な(POSIXly)スクリプトからのものです。

sochars='][ (){}:,!'"'\\"
# NOTE: wrapped in an eval to protect it from dash which croaks over the regex
eval 'o=; while [[ "$s" =~ ([^$sochars]*)([$sochars])(.*) ]]; do
    ...
done'

dashその構文が実際のコードパスにまったく入らない場合でも、語彙レベルでは不明な(しかし直接的な)構文によってブロックされます。


別の意味では、「保護」のもう一つのユースケースです。時々私は保存と復元の目的のために「不思議な」名前を作るにはあまりにも怠惰です。たとえば、次の場合は$r値だけを保持したいと思います。

# wrapped in eval just to make sure that $r is not overwritten by (the call chain of) coolf
eval '
    coolf "$tmp" || return "$lerrno"'"
    return $r
"

実際、私は次のようにクリーンアップ操作を実行し、ループのコレクションの終了状態を維持するために上記のトリックを頻繁に使用します。

    done <&3
    eval "unset ret vals; exec 3<&-; return $?"
}

または「遅延実行」などの上記のような状況で:

    done
    # return boolean set by loop while also unsetting it
    eval "unset ok; ${ok:-false}"
}

上記の2つのコードスニペットの暗黙の意図は、関数がインタラクティブに実行されることを意図した場合、関数の実行に「アーティファクト」を残さないことです。後者の場合は、次のようにすることができます。

[ "${ok:-false}" = false ] && { unset ok; return 1; } || { unset ok; return 0; }

しかし、私は見るには粗いようです。


最後に、小さな動作の変更や呼び出し元の一部のフックをサポートするために、呼び出しに基づいて関数を少し変更または拡張する必要がある場合があります。コールバックに似ていますが、「インライン」方式ではあまり面倒ではありません。特に、フックの断片が関数の独自のパラメータ$@にアクセスする必要がある場合は、さらにそうです。もちろん、変数によって提供され、後でeval関数によって編集されるこれらのフラグメント自体は、完全に静的/手動で作成または事前制御/衛生処理されます。

答え4

eval私が見た実際の用途の1つは次のとおりです。Fluxbox.startfluxbox.dbus.diff.gz スラックウェアから。次のようになります。

# Start DBUS session bus:
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
   eval $(dbus-launch --sh-syntax --exit-with-session)
fi

この使用法はeval何百万人もの人々によってテストされていませんが(Slackwareはあまり一般的ではありません)、作業を完了します。それでも私はevalシェルスクリプトでこれを避けようとします。たとえば、配列を実装したり、変数間接指定を実行したりする必要があると感じた場合は、Bashに切り替えます。

関連情報