Biến như lệnh; eval vs bash -c


41

Tôi đã đọc một tập lệnh bash do ai đó thực hiện và tôi nhận thấy rằng tác giả không sử dụng eval để đánh giá một biến như một lệnh
Tác giả đã sử dụng

bash -c "$1"

thay vì

eval "$1"

Tôi giả sử sử dụng eval là phương pháp ưa thích và dù sao nó cũng có thể nhanh hơn. Điều đó có đúng không?
Có sự khác biệt thực tế giữa hai? Sự khác biệt đáng chú ý giữa hai là gì?


Trong một số trường hợp, bạn có thể đi mà không cần một trong hai. e='echo foo'; $ehoạt động tốt
Dennis

Câu trả lời:


40

eval "$1"thực thi lệnh trong kịch bản hiện tại. Nó có thể thiết lập và sử dụng các biến shell từ tập lệnh hiện tại, đặt biến môi trường cho tập lệnh hiện tại, thiết lập và sử dụng các hàm từ tập lệnh hiện tại, đặt thư mục hiện tại, ô, giới hạn và các thuộc tính khác cho tập lệnh hiện tại, v.v. bash -c "$1"thực thi lệnh trong một tập lệnh hoàn toàn riêng biệt, kế thừa các biến môi trường, mô tả tệp và môi trường xử lý khác (nhưng không truyền lại bất kỳ thay đổi nào) nhưng không kế thừa các cài đặt shell bên trong (biến shell, hàm, tùy chọn, bẫy, v.v.).

Có một cách khác (eval "$1"), đó là thực thi lệnh trong một lớp con: nó kế thừa mọi thứ từ tập lệnh gọi nhưng không truyền lại bất kỳ thay đổi nào.

Ví dụ, giả sử rằng biến dirkhông được xuất khẩu và $1cd "$foo"; ls, sau đó:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdliệt kê nội dung /somewhere/elsevà bản in /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdliệt kê nội dung /somewhere/elsevà bản in /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdliệt kê nội dung của /starting/directory(vì cd ""không thay đổi thư mục hiện tại) và bản in /starting/directory.

Cảm ơn. Tôi không biết về (eval "$ 1"), nó có khác với nguồn không?
whoami

1
@whoami (eval "$1")không có gì để làm source. Nó chỉ là sự kết hợp của (…)eval. source foogần tương đương với eval "$(cat foo)".
Gilles 'SO- ngừng trở nên xấu xa'

Chúng tôi phải đã viết câu trả lời của chúng tôi cùng một lúc ...
mikeerv

@whoami Sự khác biệt chính giữa eval.dotevalhoạt động với các đối số.dothoạt động với các tệp.
mikeerv

Cảm ơn cả hai bạn. Nhận xét trước đây của tôi có vẻ hơi ngu ngốc khi tôi đọc lại ...
whoami

23

Sự khác biệt quan trọng nhất giữa

bash -c "$1" 

eval "$1"

Có phải cái trước chạy trong một subshell và cái sau thì không. Vì thế:

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

ĐẦU RA:

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

ĐẦU RA:

something

Tôi không biết tại sao mọi người sẽ sử dụng thực thi bashtheo cách đó, mặc dù. Nếu bạn phải gọi nó, hãy sử dụng POSIX được tích hợp sẵn sh. Hoặc (subshell eval)nếu bạn muốn bảo vệ môi trường của bạn.

Cá nhân, tôi thích vỏ .dothơn tất cả.

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

ĐẦU RA

something1
something2
something3
something4
something5

NHƯNG BẠN CÓ CẦN NÓ KHÔNG?

Nguyên nhân duy nhất để sử dụng một trong hai, thực sự là trong trường hợp biến của bạn thực sự gán hoặc đánh giá một cái khác, hoặc tách từ là quan trọng đối với đầu ra.

Ví dụ:

var='echo this is var' ; $var

ĐẦU RA:

this is var

Điều đó hoạt động, nhưng chỉ vì echokhông quan tâm đến số lượng đối số của nó.

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

ĐẦU RA:

"this is var"

Xem? Các trích dẫn kép xuất hiện vì kết quả của việc mở rộng shell $varkhông được đánh giá quote-removal.

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

ĐẦU RA:

"this
is
var"

Nhưng với evalhoặc sh:

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

ĐẦU RA:

this is var
this is var

Khi chúng ta sử dụng evalhoặc shshell sẽ vượt qua lần thứ hai với kết quả của việc mở rộng và đánh giá chúng là một lệnh tiềm năng, và vì vậy các trích dẫn tạo ra sự khác biệt. Bạn cũng có thể làm:

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

ĐẦU RA

this is var

5

Tôi đã làm một bài kiểm tra nhanh:

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'

(Vâng, tôi biết, tôi đã sử dụng bash -c để thực thi vòng lặp nhưng điều đó sẽ không tạo ra sự khác biệt).

Kết quả:

eval    : 1.17s
bash -c : 7.15s

Như vậy evallà nhanh hơn. Từ trang người đàn ông của eval:

Tiện ích eval sẽ xây dựng một lệnh bằng cách ghép các đối số lại với nhau, phân tách từng đối số bằng một ký tự. Lệnh được xây dựng sẽ được đọc và thực thi bởi shell.

bash -cTất nhiên, thực thi lệnh trong một bash shell. Một lưu ý: Tôi đã sử dụng /bin/echoechođược tích hợp sẵn vỏ bash, nghĩa là một quy trình mới không cần phải bắt đầu. Thay thế /bin/echobằng echocho bash -ckiểm tra, nó mất 1.28s. Đó là về cùng. Hovever, evallà nhanh hơn để chạy thực thi. Sự khác biệt chính ở đây là evalkhông khởi động shell mới (nó thực thi lệnh trong shell hiện tại) trong khi bash -ckhởi động shell mới, sau đó thực thi lệnh trong shell mới. Bắt đầu một lớp vỏ mới cần có thời gian, và đó là lý do tại sao bash -cchậm hơn eval.


Tôi nghĩ OP muốn so sánh bash -cvới evalkhông exec.
Joseph R.

@JosephR. Úi! Tôi sẽ thay đổi điều đó.
PlasmaPower

1
@JosephR. Nó nên được chữa ngay bây giờ. Ngoài ra tôi redid các cuộc thử nghiệm hơn một chút và bash -ckhông phải là xấu ...
PlasmaPower

3
Trong khi điều này là đúng, nó bỏ lỡ sự khác biệt cơ bản là lệnh được thực thi trong các môi trường khác nhau. Rõ ràng là bắt đầu một phiên bản mới của bash sẽ chậm hơn, đây không phải là một quan sát thú vị.
Gilles 'SO- ngừng trở nên xấu xa'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.