evalを使用してスペースを含む値をbashの変数に割り当てる方法


19

を使用して変数に値を動的に割り当てたいeval。次のダミーの例が機能します。

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

ただし、変数値にスペースが含まれevalている場合$var_value、二重引用符で囲まれていてもエラーを返します。

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

これを回避する方法はありますか?

回答:



11

evalこれには使用しないでください。を使用しますdeclare

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

単語分割は問題ではないことに注意してください。これは、後続のすべてが最初の単語だけでなく、=による値として扱われるためですdeclare

ではbash4.3、名前の参照は、この少し簡単にします。

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

あなたeval仕事することができますが、あなたはまだすべきではありません:)使用することevalは悪い習慣です。

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"

2
eval その方法を使用するのは間違っています。$var_value渡す前に展開しているためeval、シェルコードとして解釈されます。(たとえば、試してみてくださいvar_value="';:(){ :|:&};:'"
ステファンシャゼル14

1
いい視点ね; 安全に割り当てられない文字列がありますeval(これは、使用すべきでないと私が言った理由の1つですeval)。
chepner 14

@chepner-それが本当だとは思わない。多分そうですが、少なくともこれはそうではありません。パラメーターの置換により条件付き展開が可能になるため、ほとんどの場合、安全な値のみを展開できると思います。それでも、あなたの主な問題$var_valueは引用の反転の1つです- $var_name 本当に安全な値を仮定すると(実際には同じように危険である可能性があります)、右側の二重引用符を単一引用符で囲む必要があります-逆に。
mikeserv 14

eval、を使用してを修正し、printfそのbash固有の%q形式を使用したと思います。これはまだ使用することを推奨するものevalではありませんが、以前より安全だと思います。これを機能させるためにこれだけの努力をする必要があるという事実は、declare代わりに参照を使用するか名前付きリファレンスを使用する必要があることの証拠です。
chepner 14

実際、私の意見では、名前付き参照が問題です。それを使用するための最良の方法は-私の経験では-のようなものです... set -- a bunch of args; eval "process2 $(process1 "$@")"どこprocess1と同じように引用された数字を印刷します"${1}" "${8}" "${138}"。これは非常に単純'"${'$((i=$i+1))'}" 'で、ほとんどの場合と同じくらい簡単です。インデックス付き参照により、安全で堅牢、高速になります。それでも-私は賛成しました。
mikeserv 14

4

使用する良い方法evalecho、テスト用に置き換えることです。echoそしてeval同じように動作します(ある条件下でのような\xいくつかのecho実装によって行われる拡張を脇に置いたbash場合)。

両方のコマンドは、間にスペースを1つ入れて引数を結合します。違いは、結果をシェルコードとして評価 / 解釈する間、結果をecho 表示することです。eval

だから、どのシェルコードを見るために

eval $(echo $var_name=$var_value)

評価すると、実行できます:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

それはあなたが望むものではなく、あなたが望むものは:

fruit=$var_value

また、$(echo ...)ここで使用しても意味がありません。

上記を出力するには、次を実行します。

$ echo "$var_name=\$var_value"
fruit=$var_value

それで、それを解釈するために、それは単純です:

eval "$var_name=\$var_value"

個々の配列要素を設定するためにも使用できることに注意してください。

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

他の人が言ったように、コードがbash特定であることを気にしない場合は、次のように使用できますdeclare

declare "$var_name=$var_value"

ただし、いくつかの副作用があることに注意してください。

変数のスコープを、それが実行される関数に制限します。したがって、たとえば次のような場合に使用することはできません。

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

それは宣言するから fooローカル変数setvarそれは役に立たないでしょう。

bash-4.2追加-gのオプションdeclare宣言するグローバル変数を、それは私達が私達のいずれかとして欲しいものではありませんsetvar設定しますグローバル呼び出し側が中のような関数であった場合、発信者のそれとは対照的に、VARを:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

出力されるもの:

1:
2: some value

また、declare呼び出されるdeclare(実際にbashはKornシェルのtypeset組み込みから概念を借用)が、変数が既に設定されている場合、declare新しい変数を宣言せず、割り当てが行われる方法は変数のタイプに依存することに注意してください。

例えば:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

varname以前にスカラー配列、または連想配列として宣言されていた場合、異なる結果を生成します(そして潜在的に厄介な副作用があります)。


2
bash固有であることの何が問題になっていますか?OPは質問にbashタグを付けたため、bashを使用しています。代替を提供することは良いことですが、移植性がないためにシェルの機能を使用しないように誰かに言うのは愚かなことだと思います。
パトリック14

@パトリック、スマイリーを見た?とはいえ、移植可能な構文を使用すると、コードをbash使用できない別のシステムにコードを移植する必要がある場合(または、より優れた/より高速なシェルが必要な場合)の労力が少なくなります。eval構文は、すべてのBourneのようなシェルで動作し、すべてのシステムがありますので、POSIXあるshところという作品。(つまり、私の答えはすべてのシェルに適用されることを意味し、遅かれ早かれ、ここでよくあるように、bash固有ではない質問がこの質問の複製として閉じられます。
StéphaneChazelas 14

しかし、$var_nameトークンが含まれている場合はどうなりますか?...のような;
mikeserv 14

@mikeserv、それは変数名ではありません。あなたがその内容を信頼することができない場合は、両方でそれをサニタイズする必要があるevaldeclare(と思いますPATHTMOUTPS4SECONDS...)。
ステファンシャゼル14

ただし、最初のパスでは常に変数展開が行われ、2番目まで変数名は渡されません。私の答えでは、パラメータ展開でサニタイズしますが、最初のパスでサブシェルでサニタイズを行うことを暗示している場合は、移植可能にできexportます。私は最後に括弧でくくられていません。
mikeserv 14

1

もしあなたがそうするなら:

eval "$name=\$val"

...および-シェルが単純なコマンドを区切ると解釈する可能性の$nameある;-またはその他のトークンのいずれかを含みます-実行される適切なシェル構文が前に付きます。

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

出力

hi
be careful with eval

ただし、このようなステートメントの評価と実行を分離することもできます。たとえばalias、コマンドを事前評価するために使用できます。次の例では、評価aliasする$nm変数にASCIIの英数字またはに一致しないバイトが含まれていない場合にのみ正常に宣言できるに変数定義が保存されます_

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

evalここではalias、varnameからnewの呼び出しを処理するために使用されます。しかし、前のalias定義が成功した場合にのみ呼び出され、多くの異なる実装が多くの異なる種類の値を受け入れることを知っていますがalias名前のいますが、完全に空のものを受け入れるものにはまだ遭遇していません。

ただし、内の定義aliasはのためのもの_$nmであり、これは重要な環境値が上書きされないようにするためです。で始まる注目すべき環境値がわからない_で通常はセミプライベート宣言の安全な方法です。

とにかく、alias定義が成功した場合、alias名前付きfor $nmの値を宣言します。また、番号で始まらない場合にevalのみ呼び出します。aliasそれ以外の場合evalは、null引数のみを取得します。したがって、両方の条件が満たされるevalと、エイリアスが呼び出され、エイリアスに保存された変数定義が作成aliasされます。その後、新しいものがハッシュテーブルから即座に削除されます。


;変数名には使用できません。のコンテンツを制御できない場合は$nameexport/ に対してもサニタイズする必要がありdeclareます。一方では、exportのようないくつかの変数を設定し、コードを実行していないPATHPS4とそれらの多くは、info -f bash -n 'Bash Variables'同じように危険な副作用を持っています。
ステファンシャゼル14

@StéphaneChazelas-もちろん許可されていませんが、以前のように、eval最初のパスの変数名ではありません-変数展開です。他の場所で言ったように、その文脈ではそれは非常に許可されています。それでも$ PATH引数は非常に良いものです-私は小さな編集を行い、後でいくつかを追加します。
mikeserv 14

@StéphaneChazelas-絶対に遅れるよりは...
mikeserv 14年

実際には、zshpdkshmkshyash上の文句を言いませんunset 'a;b'
ステファンシャゼル14年

あなたも望むでしょうunset -v -- ...
ステファンシャゼル14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.