Variabel sebagai perintah; eval vs bash -c

41

Saya membaca skrip bash yang dibuat seseorang dan saya perhatikan bahwa penulis tidak menggunakan eval untuk mengevaluasi variabel sebagai perintah yang
digunakan penulis

bash -c "$1"

dari pada

eval "$1"

Saya berasumsi menggunakan eval adalah metode yang disukai dan mungkin juga lebih cepat. Benarkah?
Apakah ada perbedaan praktis antara keduanya? Apa perbedaan penting antara keduanya?

siapa saya
sumber
Dalam beberapa kesempatan, Anda bisa pergi tanpa keduanya. e='echo foo'; $eberfungsi dengan baik.
Dennis

Jawaban:

40

eval "$1"mengeksekusi perintah dalam skrip saat ini. Itu dapat mengatur dan menggunakan variabel shell dari skrip saat ini, mengatur variabel lingkungan untuk skrip saat ini, mengatur dan menggunakan fungsi dari skrip saat ini, mengatur direktori saat ini, umask, batas dan atribut lainnya untuk skrip saat ini, dan sebagainya. bash -c "$1"mengeksekusi perintah dalam skrip yang benar-benar terpisah, yang mewarisi variabel lingkungan, deskriptor file dan lingkungan proses lainnya (tetapi tidak mengirimkan perubahan apa pun kembali) tetapi tidak mewarisi pengaturan shell internal (variabel shell, fungsi, opsi, jebakan, dll.).

Ada cara lain (eval "$1"), yang mengeksekusi perintah dalam sebuah subkulit: ia mewarisi segalanya dari skrip panggilan tetapi tidak mengirimkan perubahan apa pun kembali.

Misalnya, dengan asumsi bahwa variabel dirtidak diekspor dan $1adalah cd "$foo"; ls, maka:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwddaftar isi /somewhere/elsedan cetakan /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwddaftar isi /somewhere/elsedan cetakan /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwddaftar konten /starting/directory(karena cd ""tidak mengubah direktori saat ini) dan mencetak /starting/directory.
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih. Saya tidak tahu (eval "$ 1"), apakah ada bedanya dengan sumber?
whoami
1
@whoami (eval "$1")tidak ada hubungannya dengan source. Itu hanya kombinasi dari (…)dan eval. source fookira-kira setara dengan eval "$(cat foo)".
Gilles 'SANGAT berhenti menjadi jahat'
Kami pasti telah menulis jawaban kami pada saat yang sama ...
mikeserv
@whoami Perbedaan utama antara evaldan .dotadalah yang evalberfungsi dengan argumen dan .dotbekerja dengan file.
mikeserv
Terima kasih kalian berdua. Komentar saya sebelumnya tampak agak bodoh sekarang karena saya membacanya lagi ...
whoami
23

Perbedaan paling penting di antara keduanya

bash -c "$1" 

Dan

eval "$1"

Apakah yang pertama berjalan dalam subkulit dan yang terakhir tidak. Begitu:

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

KELUARAN:

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

KELUARAN:

something

Saya tidak tahu mengapa ada orang yang menggunakan executable bashseperti itu. Jika Anda harus memintanya, gunakan POSIX built-in dijamin sh. Atau (subshell eval)jika Anda ingin melindungi lingkungan Anda.

Secara pribadi, saya lebih suka shell di .dotatas segalanya.

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

KELUARAN

something1
something2
something3
something4
something5

TETAPI ANDA BUTUHKAN?

Satu-satunya alasan untuk menggunakan salah satunya adalah jika variabel Anda benar-benar menetapkan atau mengevaluasi yang lain, atau pemisahan kata penting untuk output.

Contohnya:

var='echo this is var' ; $var

KELUARAN:

this is var

Itu bekerja, tetapi hanya karena echotidak peduli dengan argumennya.

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

KELUARAN:

"this is var"

Lihat? Tanda kutip ganda muncul karena hasil ekspansi shell $vartidak dievaluasi quote-removal.

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

KELUARAN:

"this
is
var"

Tetapi dengan evalatau sh:

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

KELUARAN:

this is var
this is var

Ketika kita menggunakan evalatau shshell mengambil pass kedua pada hasil ekspansi dan mengevaluasinya sebagai perintah potensial juga, dan dengan demikian tanda kutip membuat perbedaan. Anda juga dapat melakukan:

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

KELUARAN

this is var
mikeserv
sumber
5

Saya melakukan tes cepat:

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'

(Ya, saya tahu, saya menggunakan bash -c untuk mengeksekusi loop tetapi itu seharusnya tidak membuat perbedaan).

Hasil:

eval    : 1.17s
bash -c : 7.15s

Jadi evallebih cepat. Dari halaman manual dari eval:

Utilitas eval harus membangun perintah dengan menggabungkan argumen bersama, memisahkan masing-masing dengan karakter. Perintah yang dibangun harus dibaca dan dieksekusi oleh shell.

bash -ctentu saja, jalankan perintah dalam bash shell. Satu catatan: Saya menggunakan /bin/echokarena echoshell built-in dengan bash, yang berarti proses baru tidak perlu dimulai. Mengganti /bin/echodengan echountuk bash -ctes, butuh 1.28s. Itu hampir sama. Hovever, evallebih cepat untuk menjalankan executables. Perbedaan utama di sini adalah bahwa evaltidak memulai shell baru (itu mengeksekusi perintah di saat ini) sedangkan bash -cmemulai shell baru, kemudian mengeksekusi perintah di shell baru. Memulai shell baru membutuhkan waktu, dan itulah sebabnya bash -clebih lambat daripada eval.

PlasmaPower
sumber
Saya pikir OP ingin membandingkan bash -cdengan evaltidak exec.
Joseph R.
@ JosephephR. Ups! Saya akan mengubahnya.
PlasmaPower
1
@ JosephephR. Seharusnya sudah diperbaiki sekarang. Juga saya redid tes lebih sedikit dan bash -ctidak terlalu buruk ...
PlasmaPower
3
Meskipun ini benar, ia melewatkan perbedaan mendasar bahwa perintah dieksekusi di lingkungan yang berbeda. Sudah jelas bahwa memulai instance baru bash akan lebih lambat, ini bukan pengamatan yang menarik.
Gilles 'SANGAT berhenti menjadi jahat'