Komut olarak değişken; eval vs bash -c


41

Birinin yaptığı bir bash betiği okuyordum ve yazarın bir değişkeni komut olarak değerlendirmek için eval kullanmadığını fark ettim
Yazar

bash -c "$1"

onun yerine

eval "$1"

Eval kullanmanın tercih edilen yöntem olduğunu ve muhtemelen daha hızlı olduğunu tahmin ediyorum. Bu doğru mu?
İkisi arasında pratik bir fark var mı? İkisi arasındaki dikkate değer farklar nelerdir?


Bazı durumlarda ikisinden de kurtulabilirsiniz. e='echo foo'; $esadece iyi çalışıyor.
Dennis,

Yanıtlar:


40

eval "$1"komutu geçerli komut dosyasında yürütür. Geçerli betiğin kabuk değişkenlerini ayarlayabilir ve kullanabilir, mevcut betiğin ortam değişkenlerini ayarlayabilir, mevcut betiğin işlevlerini ayarlayabilir ve kullanabilir, geçerli dizini, umask, limitleri ve geçerli betiğin diğer özelliklerini ayarlayabilir ve benzeri işlemleri yapabilir. bash -c "$1"komutu, ortam değişkenlerini, dosya tanımlayıcılarını ve diğer işlem ortamını (ancak herhangi bir değişikliği geri iletmez) miras alan ancak dahili kabuk ayarlarını (kabuk değişkenleri, işlevler, seçenekler, tuzaklar vb.) devralmayan tamamen ayrı bir komut dosyasında yürütür.

(eval "$1")Komutu bir alt kabukta yürüten başka bir yol daha var : çağıran komut dosyasındaki her şeyi devralır ancak herhangi bir değişikliği geri iletmez.

Örneğin, değişkenin dirdışa aktarılmadığını ve $1şöyle olmadığını varsayarsak cd "$foo"; ls:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdiçeriğini listeler /somewhere/elseve yazdırır /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdiçeriğini listeler /somewhere/elseve yazdırır /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdiçeriğini listeler /starting/directory(çünkü cd ""geçerli dizini değiştirmez) ve yazdırır /starting/directory.

Teşekkürler. Bilmiyordum (eval "1 $"), kaynaktan farklı mı?
whoami

1
@whoami ile (eval "$1")ilgisi yok source. Bu sadece bir kombinasyonu var (…)ve eval. source fookabaca eşittir eval "$(cat foo)".
Gilles 'SO- kötü olmayı bırak'

Cevaplarımızı aynı anda
yazmış olmalıyız

@whoami arasındaki temel fark evalve .dotolmasıdır evalile çalışır argümanları ve .dotçalışır dosyaları.
mikeserv

İkinizide tesekkurler. Önceki yorumum şimdi tekrar okuduğum için aptal gibi görünüyor ...
whoami

23

Arasındaki en önemli fark

bash -c "$1" 

Ve

eval "$1"

Eski bir deniz kabuğu içinde çalışır ve ikincisi yoktur. Yani:

set -- 'var=something' 
bash -c "$1"
echo "$var"

ÇIKTI:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

ÇIKTI:

something

bashYine de, neden herkesin yürütülebilir dosyasını bu şekilde kullandığı hakkında hiçbir fikrim yok . Çalıştırmanız gerekiyorsa, garantili yerleşik POSIX kullanın sh. Ya (subshell eval)da çevrenizi korumak istiyorsanız.

Şahsen ben .dother şeyden önce kabuğun tercih ederim .

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

ÇIKTI

something1
something2
something3
something4
something5

AMA HER ŞEYE İHTİYACINIZ VAR MI?

Gerçekten kullanmak için tek neden, değişkeninizin gerçekte başka bir atama veya değerlendirme yapması veya kelime ayırmanın çıktı için önemlidir.

Örneğin:

var='echo this is var' ; $var

ÇIKTI:

this is var

Bu işe yarıyor, ancak yalnızca bunun nedeni echo, argüman sayısını umursamıyor.

var='echo "this is var"' ; $var

ÇIKTI:

"this is var"

Görmek? Çift tırnak, kabuğun genişlemesinin sonucu olarak $vardeğerlendirilmediğinden ortaya çıkar quote-removal.

var='printf %s\\n "this is var"' ; $var

ÇIKTI:

"this
is
var"

Ancak evalveya sh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

ÇIKTI:

this is var
this is var

Kullandığımızda evalveya shkabuk, genişlemelerin sonuçlarından ikinci bir kez geçer ve bunları potansiyel bir komut olarak değerlendirir ve bu yüzden tırnaklar bir fark yaratır. Ayrıca şunları da yapabilirsiniz:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

ÇIKTI

this is var

5

Hızlı bir test yaptım:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(Evet, biliyorum, döngü yürütmek için bash-c kullandım ama bu bir fark yaratmamalıydı).

Sonuçlar:

eval    : 1.17s
bash -c : 7.15s

Yani evaldaha hızlı. Man sayfasından eval:

Eval programı, argümanları bir araya getirerek, her birini bir karakterle ayırarak bir komut oluşturacaktır. Oluşturulan komut kabuk tarafından okunur ve uygulanır.

bash -cTabii ki, komutu bir bash kabuğunda çalıştırır. Bir not: Ben kullandım /bin/echoçünkü echoyerleşik bir kabuk bash, yani yeni bir sürecin başlatılması gerekmiyor. Test için /bin/echoile değiştirerek aldı . Bu yaklaşık aynı. Hovever, çalıştırılabilir dosyaları çalıştırmak için daha hızlıdır. Buradaki en önemli fark, yeni bir kabuk başlatmaması (geçerli olandaki komutu çalıştırması), yeni bir kabuk başlatması, ardından komutu yeni kabukta çalıştırmasıdır. Yeni bir kabuk başlatmak zaman alır ve bu yüzden daha yavaş .echobash -c1.28sevalevalbash -cbash -ceval


Ben OP karşılaştırmak istiyor düşünüyorum bash -cile evaldeğil exec.
Joseph R.,

@JosephR. Hata! Bunu değiştireceğim.
PlasmaPower

1
@JosephR. Şimdi düzeltilmeli. Ayrıca testleri biraz daha değiştirdim ve bash -co kadar da kötü değil ...
PlasmaPower

3
Bu doğru olsa da, komutun farklı ortamlarda yürütülmesiyle ilgili temel farkı özlüyor. Yeni bir bash örneğinin başlamasının daha yavaş olacağı açıktır, bu ilginç bir gözlem değildir.
Gilles 'SO- kötü olmaktan'
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.