Eval kullanarak bash değişkenlerine alan içeren değerler nasıl atanır


19

Dinamik olarak değişkenlere değerler atamak istiyorum eval. Aşağıdaki kukla örnek çalışıyor:

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

Ancak, değişken değeri boşluk içerdiğinde, çift ​​tırnak işaretleri arasına koyulsa evalbile bir hata döndürür $var_value:

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

Bunu atlatmanın herhangi bir yolu var mı?

Yanıtlar:


13

Eval kullanma, kullanma declare

$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<

11

Bunun için kullanmayın eval; kullanın declare.

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

Sözcük ayırmanın bir sorun olmadığını unutmayın, çünkü öğesinden sonraki her şey yalnızca ilk sözcük olarak değil =, değer olarak kabul edilir declare.

In bash4.3, adlandırılmış referanslar bu biraz daha basit hale.

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

Sen edebilir hale evalişi, ama yine de :) kullanılması gerektiğini evalgirmek kötü bir alışkanlıktır.

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

2
eval Bu şekilde kullanmak yanlıştır. Onu $var_valuegeçmeden önce genişliyorsunuz , evalyani kabuk kodu olarak yorumlanacak! (örneğin ile deneyin var_value="';:(){ :|:&};:'")
Stéphane Chazelas

1
İyi bir nokta; kullanarak güvenle atayamayacağınız bazı dizeler vardır eval( kullanmamalısınız dememizin bir nedeni budur eval).
chepner

@chepner - Bunun doğru olduğuna inanmıyorum. belki öyle, ama en azından bu değil. parametre ikameleri koşullu genişlemeye izin verir ve bence çoğu durumda yalnızca güvenli değerleri genişletebilirsiniz. yine de, asıl probleminiz $var_valuealıntı inversiyonundan biridir - için güvenli bir değer varsayarsak $var_name (ki bu gerçekten de tehlikeli bir varsayım olabilir) , o zaman sağ tarafın çift tırnaklarını tek tırnak içine almalısınız - değil tersine.
mikeserv

Ben çözdükten evalkullanarak printfve bashe özgü %qbiçimi. Bu hala bir öneri değil eval, ama eskisinden daha güvenli olduğunu düşünüyorum. Çalışması için bu kadar çaba sarf etmeniz gerektiği gerçeği, declarebunun yerine referansları kullanmanız veya adlandırmanız gerektiğinin kanıtıdır .
chepner

Aslında, benim görüşüme göre, adlandırılmış referanslar sorun. Benim durumumda - - kullanmak için en iyi yol gibi ... olduğu set -- a bunch of args; eval "process2 $(process1 "$@")"yerde process1sadece yazdırır gibi rakamlar verdi "${1}" "${8}" "${138}". Bu çok basit - ve '"${'$((i=$i+1))'}" 'çoğu durumda olduğu kadar kolay . endeksli referanslar onu güvenli, sağlam ve hızlı hale getirir . Yine de - kaldırdım.
mikeserv

4

Çalışmanın iyi bir yolu, evalonu echotest için değiştirmektir . echove evalaynı şekilde çalışın ( bazı koşullar altında olduğu gibi \xbazı echouygulamalar tarafından yapılan genişletmeyi bir kenara bırakırsak bash).

Her iki komut da argümanlarını aralarındaki bir boşlukla birleştirir. Fark olduğunu echo gösterir sonuç ise eval değerlendirir / yorumlayan ve kabuk kodu olarak sonuçlanır.

Hangi kabuk kodunu görmek için

eval $(echo $var_name=$var_value)

değerlendirirseniz, şunları çalıştırabilirsiniz:

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

İstediğiniz bu değil, istediğiniz şey:

fruit=$var_value

Ayrıca, $(echo ...)burada kullanmak mantıklı değil.

Yukarıdakilerin çıktısını almak için şunu çalıştırırsınız:

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

Yani, yorumlamak için, bu basitçe:

eval "$var_name=\$var_value"

Tek tek dizi öğelerini ayarlamak için de kullanılabileceğini unutmayın:

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

Diğerlerinin söylediği gibi, kodunuzun bashspesifik olmasını umursamıyorsanız , aşağıdaki gibi kullanabilirsiniz declare:

declare "$var_name=$var_value"

Ancak bazı yan etkileri olduğunu unutmayın.

Değişkenin kapsamını çalıştığı işlevle sınırlar. Böylece, örneğin aşağıdaki gibi şeyleri kullanamazsınız:

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

Çünkü bu fooyerel değişkenin böyle olacağını açıklayamaz setvar.

bash-4.2global bir değişken bildirmek -giçin bir seçenek ekledik , ancak arayan bir işlevse, arayanınkine göre global bir değişken belirleyeceğimiz için istediğimiz şey bu değildi :declaresetvar

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"

hangi çıktı:

1:
2: some value

Ayrıca, declaredeğişken çağrılmışsa declare(aslında bashkavramı Korn kabuğunun typesetyerleşikinden ödünç alınmış) çağrılırsa , declareyeni bir değişken bildirmez ve atamanın yapılma şekli değişkenin türüne bağlıdır.

Örneğin:

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

varnameönceden skaler , dizi veya ilişkilendirilebilir dizi olarak bildirilmişse farklı bir sonuç üretir (ve muhtemelen kötü yan etkilere sahiptir) .


2
Bash'e özgü olmanın nesi yanlış? OP bash etiketini soruya koydu, o yüzden bash kullanıyor. Alternatif sunmak iyidir, ama bence birine bir kabuğun bir özelliğini kullanmamasını söylemek taşınabilir değil çünkü saçma.
Patrick

@ Patrick, suratı gördün mü? Bununla birlikte, taşınabilir sözdiziminin kullanılması, kodunuzu kullanılamayan başka bir sisteme bash(veya daha iyi / daha hızlı bir kabuğa ihtiyacınız olduğunu fark ettiğinizde) taşımanız gerektiğinde daha az çaba anlamına gelir . evalSözdizimi tüm Bourne benzeri kabukları içinde çalışır ve POSIX'e olan tüm sistemler bir olacak, böylece shnerede eserler. (bu aynı zamanda cevabımın tüm mermiler için geçerli olduğu anlamına gelir ve er ya da geç, burada sık sık olduğu gibi, bunun bir kopyası değildir, bunun bir kopyası olarak kapalıdır.
Stéphane Chazelas

ama $var_namejeton içeriyorsa ne olur ? ... gibi ;mi?
mikeserv

@mikeserv, o zaman değişken bir isim değil. İçeriğini güvenemeyekceksem, o zaman her ikisi ile de sterilize gerekir evalve declare(düşünmek PATH, TMOUT, PS4, SECONDS...).
Stéphane Chazelas

ama ilk geçişte her zaman değişken bir genişleme ve ikincisine kadar asla değişken bir isim. cevabımda bir parametre genişlemesi ile dezenfekte ediyorum, ancak ilk geçişte bir alt kabukta sanitizasyon yapmayı ima ediyorsanız, bu da taşınabilir olarak w / yapılabilir export. i sonunda parantez biraz takip yok.
mikeserv

1

Yaparsan:

eval "$name=\$val"

... ve kabuğun yürütülecek uygun kabuk sözdiziminden önce gelen basit bir komutu sınırlandıracağı yorumlayabileceği $namebir ;- veya başka birkaç belirteç içerir .

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

ÇIKTI

hi
be careful with eval

Bununla birlikte, bazen bu tür açıklamaların değerlendirilmesini ve yürütülmesini ayırmak mümkün olabilir. Örneğin, aliasbir komutu önceden değerlendirmek için kullanılabilir. Aşağıdaki örnekte değişken tanımı, aliasyalnızca $nmdeğerlendirmekte olduğu değişken ASCII alfasayısal karakterlerle eşleşmeyen bayt içermiyorsa veya başarıyla bildirilebilecek bir değere kaydedilir _.

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

evalburada aliasbir varyasyondan yeniyi çağırmak için kullanılır . Ancak sadece önceki aliastanım başarılı olursa çağrılır ve birçok farklı uygulamanın aliasadlar için birçok farklı türde değeri kabul edeceğini bilsem de, henüz tamamen boş olanı kabul edecek bir tanesiyle karşılaşmadım. .

İçinde tanım aliasiçindir _$nmancak, ve bu anlamlı çevre değerleri üzerine yazıldığı sağlamaktır. A ile başlayan kayda değer çevre değerlerini bilmiyorum _ve genellikle yarı özel beyan için güvenli bir bahistir.

Her neyse, eğer aliastanım başarılı olursa, değeri aliasiçin bir isim beyan edecektir $nm. Ve evalbunu sadece aliasbir sayı ile başlamazsa çağırır - başka evalsadece boş bir argüman alır. Bu nedenle, her iki koşul da karşılanırsa eval, diğer adı çağırır ve diğer adda kaydedilen değişken tanımı yapılır, daha sonra yeni alias, derhal karma tablosundan kaldırılır.


;değişken adlarında izin verilmiyor. Eğer içeriği üzerinde kontrolünüz yoksa $name, o zaman export/ için dezenfekte etmeniz gerekir declare. İken exportgibi bazı değişkenler ayarlayarak, kod yürütmüyor PATH, PS4ve bunların birçoğu, info -f bash -n 'Bash Variables'aynı derecede tehlikeli yan etkileri vardır.
Stéphane Chazelas

@ StéphaneChazelas - elbette izin verilmiyor, ancak daha önce olduğu gibi, evalilk geçişinde değişken bir isim değil - değişken bir genişlemedir. Başka bir yerde söylediğin gibi, bu bağlamda buna çok izin verilir. Hala $ PATH argümanı çok iyi bir tanesidir - küçük bir düzenleme yaptım ve daha sonra ekleyeceğim.
mikeserv

@ StéphaneChazelas - hiç olmadığı kadar iyi geç mi ...?
mikeserv

Uygulamada, zsh, pdksh, mksh, yashüzerinde şikayet etmeyin unset 'a;b'.
Stéphane Chazelas

Siz unset -v -- ...de isteyeceksiniz .
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.