Bashのevalコマンドとその一般的な使用法


165

bashのmanページを読んだ後、この投稿に関して。

evalコマンドが正確に何を行うのか、およびその典型的な使用法を理解するのにまだ問題があります。たとえば、次の場合:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

ここで正確に何が起こっているのか、そしてドル記号とバックスラッシュはどのように問題に結びついていますか?


1
記録としては、2回目の試行でうまくいきます。サブシェルで$($n)実行さ$nれます。1存在しないコマンドを実行しようとします。
Martin Wickman、

1
@MartinWickmanしかし、要件はecho $1最終的に実行することであり、ではありません1。サブシェルを使用してそれを行うことができるとは思わない。
Hari Menon

5
を使用することによるセキュリティへの影響をeval認識する必要があります。
追って通知があるまで一時停止。

1
@ Raze2dust:彼がサブシェルで実行できることを示唆していたとは思わないが、OPがリストした5番目のコマンドが機能しなかった理由を説明した。
jedwards

回答:


196

eval文字列を引数として受け取り、コマンドラインでその文字列を入力したかのように評価します。(複数の引数を渡す場合、それらは最初にそれらの間にスペースで結合されます。)

${$n}bashの構文エラーです。中括弧内では、変数名と、可能な接頭辞と接尾辞のみを使用できますが、任意のbash構文を使用することはできず、特に変数展開を使用することはできません。ただし、「この変数に名前が含まれる変数の値」と言う方法があります。

echo ${!n}
one

$(…)サブシェルの括弧内に指定されたコマンドを実行し(つまり、現在のシェルから変数値などのすべての設定を継承する別のプロセスで)、その出力を収集します。したがって、シェルコマンドとしてecho $($n)実行さ$nれ、その出力が表示されます。はに$n評価されるため1、存在しない$($n)コマンドを実行しようとします1

eval echo \${$n}に渡されたパラメータを実行しますeval。拡張後のパラメータはechoおよび${1}です。したがってeval echo \${$n}、コマンドを実行しecho ${1}ます。

ほとんどの場合、変数の置換とコマンドの置換は二重引用符で囲む必要があります(つまり、がある場合は常に$"$foo", "$(foo)"変数とコマンドの置換を二重引用符で囲みます。ただし、省略する必要がある場合を除きます。二重引用符がないと、シェルはフィールド分割(つまり、変数の値またはコマンドからの出力を個別の単語に分割する)を実行し、各単語をワイルドカードパターンとして扱います。例えば:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

evalあまり使用されません。一部のシェルでは、最も一般的な用途は、実行時まで名前がわからない変数の値を取得することです。bashでは、${!VAR}構文のおかげでこれは必要ありません。eval演算子や予約語などを含むより長いコマンドを作成する必要がある場合にも役立ちます。


上記の私のコメントに関して、評価は何回「パス」しますか?
kstratis

@ Konos5コメントは?eval文字列(それ自体が解析と評価の結果である可能性があります)を受け取り、それをコードスニペットとして解釈します。
Gilles「SO-悪をやめなさい」

Raze2dustの回答の下にコメントを残しました。今ではevalは主に逆参照の目的で使用されていると信じがちです。eval echo \ $ {$ n}と入力すると、表示されます。ただし、echo \ $ {$ n}と入力すると、\ $ {1}が表示されます。これはevalの「2パス」解析により発生していると思います。余分なi = n宣言を使用して逆参照を3重にする必要がある場合、どうなるのだろうと思っています。この場合、Raze2dustによると、評価を追加するだけです。しかし、もっと良い方法があるはずだと思います...(簡単に乱雑になる可能性があります)
kstratis

@ Konos5使用しませんeval eval。必要を感じたことは今まで思い出せません。2つのevalパスが本当に必要な場合は、一時変数を使用すると、デバッグが簡単になりますeval tmp="\${$i}"; eval x="\${$tmp}"
Gilles「SO-邪悪なことをやめよう」

1
@ Konos5「2回解析」は少し誤解を招きます。さまざまな拡張から保護されているBashでリテラル文字列引数を指定することが難しいため、これを信じるように誘導される人もいるでしょう。eval文字列内のコードを受け取り、通常のルールに従ってそれを評価します。技術的には、それは正しくありません。Bashがevalの引数の拡張を実行しないように解析を変更するいくつかのまれなケースがあるためです。
ormaaj 2012年

39

単にevalを「実行前にもう一度式を評価する」と考えてください

eval echo \${$n}echo $1評価の最初のラウンドの後になります。注意すべき3つの変更:

  • \$なった$(バックスラッシュが必要です。それ以外の場合は評価しようとし${$n}ます。これは、という名前の変数を意味します{$n}
  • $n 評価された 1
  • eval消えました

第2ラウンドでは、基本的echo $1に直接実行できるものです。

したがって、eval <some command>最初に評価し<some command>(ここでの評価とは、代替変数を意味し、エスケープ文字を正しいものに置き換えるなど)、次に結果の式をもう一度実行します。

eval動的に変数を作成する場合、またはこのように読み取るように特別に設計されたプログラムから出力を読み取る場合に使用します。例については、http://mywiki.wooledge.org/BashFAQ/048を参照してください。リンクにevalは、使用されるいくつかの典型的な方法とそれに関連するリスクも含まれています。


3
最初の箇条書きの注記として、${VAR}構文許可され、あいまいさが存在する場合に推奨されます(does $VAR == $V、その後に続く、ARまたは$VAR == $VA後に続くR)。 ${VAR}と同等$VARです。実際、その変数名は$n許可されていません。
jedwards '16 / 06/17

2
eval eval echo \\\${\${$i}}トリプル逆参照を行います。それを行う簡単な方法があるかどうかはわかりません。また、\${$n}罰金(版画作品one私のマシン上)...
ハリメノン

2
@ Konos5のecho \\\${\${$i}}プリント\${${n}}。$ {1} eval echo \\\${\${$i}}と同等ですeval eval echo \\\ $ {\ $ {$ i}} `はと同等で、を印刷します。echo \${${n}}`` and prints . eval echo ${1}one
Gilles「SO-邪悪なことをやめよう」

2
@ Konos5同じように考える-最初の` escapes the second one, and the third `は$その後にエスケープします。つまり\${${n}}、1ラウンドの評価後となります
Hari Menon

2
@ Konos5左から右は、引用とバックスラッシュの解析について考える正しい方法です。最初に\\ 1つのバックスラッシュを生成します。その後\$、1ドルを生み出します。等々。
Gilles「SO-邪悪なことをやめよう」

25

私の経験では、evalの「典型的な」使用は、環境変数を設定するシェルコマンドを生成するコマンドを実行するためのものです。

おそらく、環境変数のコレクションを使用するシステムがあり、設定する変数とその値を決定するスクリプトまたはプログラムがあるとします。スクリプトまたはプログラムを実行するときはいつでも、分岐したプロセスで実行されるため、環境変数に対して直接実行することはすべて、終了時に失われます。ただし、そのスクリプトまたはプログラムは、エクスポートコマンドをstdoutに送信できます。

evalがない場合は、stdoutを一時ファイルにリダイレクトし、一時ファイルを入手して削除する必要があります。evalを使用すると、次のことができます。

eval "$(script-or-program)"

引用符が重要であることに注意してください。次の(不自然な)例を見てみましょう。

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!

これを行う一般的なツールの例はありますか?ツール自体に、evalに渡すことができるシェルコマンドのセットを生成する手段がありますか?
Joakim Erdfelt 2014年

@Joakim私はそれを行うオープンソースツールを知りませんが、私が働いている会社のいくつかのプライベートスクリプトで使用されていました。私はxamppでこのテクニックを使い始めました。Apache .confファイルは、書き込まれ${varname}た環境変数を拡張します。環境変数によってパラメーター化されたいくつかのことだけを使用して、複数の異なるサーバーで同一の.confファイルを使用すると便利です。私は/ opt / lampp / xampp(これはapacheを起動します)を編集して、システムをポークし、bash exportステートメントを出力して.confファイルの変数を定義するスクリプトでこの種の評価を行いました。
sootsnoot 14年

@Joakim代わりの方法は、影響を受ける同じ.confファイルを、同じポーキングに基づいてテンプレートから生成するスクリプトを作成することです。私の方法の良い点の1つは、/ opt / lampp / xamppを経由せずにapacheを起動すると、古い出力スクリプトが使用されず、環境変数が何も展開せず、無効なディレクティブが作成されるため、起動に失敗することです。
sootsnoot 2014年

@Anthony Sottile回答を編集して$(script-or-program)を引用符で囲み、複数のコマンドを実行するときに重要であると述べました。例を挙げていただけますか-次は、foo.shのstdout内のセミコロンで区切られたコマンドで正常に動作します。echo '#!/ bin / bash'> foo.sh; echo 'echo "echo -na; echo -nb; echo -n c"' >> foo.sh; chmod 755 foo.sh; eval $(./ foo.sh)。これにより、stdoutにabcが生成されます。./foo.shを実行すると、echo -na;が生成されます。echo -nb; echo -nc
sootsnoot

1
evalを使用する一般的なツールの例については、pyenvを参照してください。pyenvを使用すると、Pythonの複数のバージョンを簡単に切り替えることができます。あなたは置くeval "$(pyenv init -)"あなたに.bash_profile(または類似の)シェルの設定ファイル。これにより、小さなシェルスクリプトが作成され、現在のシェルで評価されます。
Jerry101 2018年

10

evalステートメントは、evalの引数をコマンドとして受け取り、それらをコマンドラインから実行するようにシェルに指示します。以下のような状況で役立ちます。

スクリプトでコマンドを変数に定義していて、後でそのコマンドを使用する場合は、evalを使用する必要があります。

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >

4

更新:一部の人々は、evalを-決して-使用すべきではないと述べています。同意しません。破損した入力がに渡される可能性がある場合、リスクが発生すると思いますeval。ただし、それが危険ではない多くの一般的な状況があるため、どのような場合でもevalの使用方法を知っておく価値があります。このスタックオーバーフローの回答は、evalのリスクとevalの代替案を説明しています。最終的には、evalが安全で効率的に使用できるかどうか、いつ使用するかを決定するのはユーザーの責任です。


bash evalステートメントを使用すると、bashスクリプトによって計算または取得されたコード行を実行できます。

おそらく最も簡単な例は、別のbashスクリプトをテキストファイルとして開き、テキストの各行を読み取り、evalそれらを順番に実行するために使用するbashプログラムでしょう。sourceインポートされたスクリプトのコンテンツに対して何らかの変換(フィルタリングや置換など)を実行する必要がない限り、これは基本的にbash ステートメントと同じ動作です。

必要になることはめったにありませんがeval、他の変数に割り当てられた文字列に名前が含まれている変数を読み書きすると便利です。たとえば、コードのフットプリントを小さく保ち、冗長性を回避しながら、変数のセットに対してアクションを実行する場合。

eval概念的には単純です。ただし、bash言語の厳密な構文、およびbashインタープリターの解析順序は微妙に異なり、evalわかりにくく、使用または理解が困難なように見えます。ここに必須事項があります:

  1. 渡される引数evalは、実行時に計算される文字列式です。eval引数の最終的な解析結果を実際のものとして実行しますスクリプトののコード行します。

  2. 構文と解析順序は厳格です。結果がbashコードの実行可能な行ではない場合、スクリプトのスコープでは、プログラムはevalガベージを実行しようとするステートメントでます。

  3. テストするときは、evalステートメントを置き換えて、echo何が表示されるかを確認できます。それが現在のコンテキストで正当なコードである場合、それを実行することevalは機能します。


次の例は、evalの動作を明確にするのに役立ちます...

例1:

eval 「通常の」コードの前のステートメントはNOPです

$ eval a=b
$ eval echo $a
b

上記の例では、最初のevalステートメントには目的がなく、削除できます。evalコードには動的な側面がないため、つまり最初の行では意味がありません。つまり、すでにbashコードの最終行に解析されているため、bashスクリプトの通常のコードステートメントと同じになります。2番目evalも意味$aがありません。これは、同等のリテラル文字列に変換する解析ステップがありますが、間接指定がないためです(たとえば、実際の bash名詞またはbash-heldスクリプト変数の文字列値による参照がないため)、同じように動作します。eval接頭辞なしのコード行として。



例2:

文字列値として渡された変数名を使用して変数割り当てを実行します。

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

にした場合echo $key=$val、出力は次のようになります。

mykey=myval

これは、文字列解析の最終結果であり、evalによって実行されるものであり、したがって、最後のechoステートメントの結果です...



例3:

例2に間接を追加する

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

上記は、前の例よりも少し複雑で、bashの解析順序と特殊性に大きく依存しています。evalラインはおおよそ次の順序で内部的に解析さになるだろう(次の文はちょうど文が最終的な結果に到達するための内部のステップに分けなるだろうどのように表示するように試みるように、擬似コードではなく、実際のコードであることに注意してください)

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

想定される解析順序でevalの処理が十分に説明されない場合、3番目の例では、何が起こっているのかを明確にするために、解析をより詳細に説明できます。



例4:

名前が文字列に含まれているvar 自体に文字列値が含まれているかどうかを確認します。

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

最初の反復では:

varname="myvarname_a"

バッシュは、引数を解析しevalて、eval実行時に、文字通り、これを見ています:

eval varval=\$$myvarname_a

次の擬似コードは、bashが上記の実際のコード行をどのように解釈して、によって実行される最終的な値に到達するを示しています。(次の行は説明的なもので、正確なbashコードではありません):eval

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

すべての解析が完了すると、結果が実行され、その効果は明白です。evalそれ自体、特に不思議なことは何もなく、その引数の解析には複雑さが伴うことを示しています。

varval="User-provided"

上記の例の残りのコードは、$ varvalに割り当てられた値がnullかどうかを確認するテストを行い、nullの場合は、ユーザーに値を入力するように求めます。


3

ほとんどの人はペストのようにevalから離れることを勧めるので、私はもともと意図的にevalの使い方を学んだことがありません。しかし、私は最近、私がそれをすぐに認識しないとfacepalmになったユースケースを発見しました。

テストのために対話的に実行するcronジョブがある場合は、catを使用してファイルの内容を表示し、cronジョブをコピーして貼り付けて実行することができます。残念ながら、これにはマウスに触れることが含まれますが、これは私の本では罪です。

/etc/cron.d/repeatmeに次の内容のcronジョブがあるとします。

*/10 * * * * root program arg1 arg2

すべてのジャンクを前に置いたスクリプトとしてこれを実行することはできませんが、cutを使用してすべてのジャンクを取り除き、サブシェルにラップして、evalで文字列を実行できます。

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

cutコマンドは、スペースで区切られたファイルの6番目のフィールドのみを出力します。次に、Evalはそのコマンドを実行します。

ここでは例としてcronジョブを使用しましたが、コンセプトはstdoutからテキストをフォーマットし、そのテキストを評価することです。

この場合のevalの使用は安全ではありません。事前に何を評価するかが正確にわかっているからです。


2

私は最近、eval複数のブレース展開を必要な順序で強制的に評価するために使用する必要がありました。Bashは左から右へ複数のブレース展開を行うので、

xargs -I_ cat _/{11..15}/{8..5}.jpg

に拡大する

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

しかし、私は最初に2番目のブレース拡張を行う必要があり、

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

それをするために私が思いつくことができた最高のものは

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

これが機能するのは、単一引用符がevalコマンドラインの解析中に中括弧の最初のセットを展開から保護し、それらがによって呼び出されるサブシェルによって展開されるためevalです。

ネストされたブレース展開を含むいくつかの狡猾なスキームがあるかもしれません。


1

あなたは典型的な用途について尋ねました。

シェルスクリプトに関する一般的な不満の1つは、(申し立てによると)関数から値を取得するために参照渡しすることができないことです。

しかし実際には、「エバール」を介して、あなたがすることができます、参照渡しするます。呼び出し先は、呼び出し元によって評価される変数割り当てのリストを返すことができます。呼び出し側は結果変数の名前を指定できるため、参照渡しです。以下の例を参照してください。エラー結果は、errnoやerrstrなどの標準名に戻すことができます。

bashで参照渡しする例を次に示します。

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

出力は次のようになります。

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

そのテキスト出力にはほぼ無制限のバンド幅があります!そして、複数の出力行が使用される場合、より多くの可能性があります。たとえば、最初の行は変数の割り当てに使用でき、2番目は継続的な「思考の流れ」に使用できますが、この投稿の範囲を超えています。


「より多くの可能性」があると言うことは、控えめに言ってもささいなこと、取るに足らないこと、冗長です。
dotbit

0

「実行前にもう一度式を評価する」という答えが好きで、別の例で明確にしたいと思います。

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

オプション2の奇妙な結果は、次のように2つのパラメーターを渡したということです。

  • 最初のパラメータ: "value
  • 2番目のパラメーター: content"

直感に反するのはどうですか?追加evalはそれを修正します。

https://stackoverflow.com/a/40646371/744133から適応


0

質問では:

who | grep $(tty | sed s:/dev/::)

ファイルaとttyが存在しないと主張するエラーを出力します。これは、ttyがgrepの実行前に解釈されないことを理解しましたが、代わりにbashがttyをパラメーターとしてgrepに渡し、それがファイル名として解釈されました。

ネストされたリダイレクトの状況もあります。これは、子プロセスを指定する必要があるかっこで処理する必要がありますが、bashは基本的に単語の区切り文字であり、プログラムに送信されるパラメーターを作成するため、かっこは最初に一致しませんが、次のように解釈されます見た。

grepで具体的に取得し、パイプを使用する代わりにファイルをパラメーターとして指定しました。また、ベースコマンドを簡略化し、コマンドからの出力をファイルとして渡し、I / Oパイピングがネストされないようにしました。

grep $(tty | sed s:/dev/::) <(who)

うまくいきます。

who | grep $(echo pts/3)

本当に望ましいわけではありませんが、ネストされたパイプがなくなり、うまく機能します。

結論として、bashはネストされたpippingを好まないようです。bashは再帰的に記述されたnew-waveプログラムではないことを理解することが重要です。代わりに、bashは古い1,2,3プログラムであり、機能が追加されています。下位互換性を確保するために、最初の解釈方法は変更されていません。括弧が最初に一致するようにbashが書き直された場合、いくつのbashプログラムにいくつのバグが導入されますか?多くのプログラマーは不可解であることを好みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.