Lệnh ev eval trong bash là gì?


176

Bạn có thể làm gì với evallệnh? Tại sao nó hữu ích? Đây có phải là một loại chức năng tích hợp trong bash không? Không có mantrang nào cho nó ..


28
Sử dụng type commandđể tìm hiểu loại lệnh là gì. ( type evaltrong trường hợp này)
rozcietrzewiacz

9
"eval là vỏ được tích hợp sẵn"
Nuno Rafael Figueiredo

3
help evalđể lấy trang "người đàn ông" từ vỏ của bạn
m-ric

eval là một bash-dựng và được ghi lại trong trang man của bash. Vì vậy, chỉ cần gõ "man bash" và tìm kiếm phần thích hợp cho eval. Điều này cũng áp dụng cho các bash-buildin khác.
Dirk Thannhäuser

Câu trả lời:


132

evallà một phần của POSIX. Đây là một giao diện có thể được tích hợp sẵn.

Nó được mô tả trong "Hướng dẫn lập trình POSIX": http://www.unix.com/man-page/poseix/1poseix/eval/

eval - construct command by concatenating arguments

Nó sẽ lấy một đối số và xây dựng một lệnh của nó, sẽ được thực thi bởi shell. Đây là ví dụ của trang web:

1) foo=10 x=foo
2) y='$'$x
3) echo $y
4) $foo
5) eval y='$'$x
6) echo $y
7) 10
  1. Trong dòng đầu tiên bạn xác định $foovới giá trị '10'$xvới giá trị 'foo'.
  2. Bây giờ xác định $y, bao gồm các chuỗi '$foo'. Ký hiệu đô la phải được thoát với '$'.
  3. Để kiểm tra kết quả , echo $y.
  4. Kết quả sẽ là chuỗi '$foo'
  5. Bây giờ chúng tôi lặp lại bài tập với eval. Đầu tiên nó sẽ đánh giá $xchuỗi 'foo'. Bây giờ chúng tôi có tuyên bố y=$foosẽ được đánh giá y=10.
  6. Kết quả echo $ybây giờ là giá trị '10'.

Đây là một chức năng phổ biến trong nhiều ngôn ngữ, ví dụ Perl và JavaScript. Hãy xem perldoc eval để biết thêm ví dụ: http://perldoc.perl.org/fifts/eval.html


4
Trong thuật ngữ shell, evallà một tích hợp, không phải là một chức năng. Trong thực tế, các hàm dựng sẵn hoạt động rất giống các hàm không có định nghĩa bằng ngôn ngữ, nhưng không hoàn toàn (sẽ trở nên rõ ràng nếu bạn bị xoắn đủ để xác định hàm được gọi eval).
Gilles

thx, đã sửa rằng :-)
echox

4
Sự khác biệt giữa eval và backticks `là gì?
JohnyTex 20/03/2015

5
Backticks là viết tắt để thực hiện toàn bộ shell khác, có nghĩa là bạn sẽ có một quá trình bash / sh con khác chạy trong tập lệnh của bạn.
Aaron R.

Tại sao echo $ y (giai đoạn 3) mang lại foo (10) thay vì $ foo? (tức là chỉ giá trị của foo thay vì chuỗi $ + giá trị của foo)?
JohnDoea

60

Có, evallà một lệnh nội bộ bash vì vậy nó được mô tả trong bashtrang man.

eval [arg ...]
    The  args  are read and concatenated together into a single com-
    mand.  This command is then read and executed by the shell,  and
    its  exit status is returned as the value of eval.  If there are
    no args, or only null arguments, eval returns 0.

Thông thường nó được sử dụng kết hợp với Thay thế lệnh . Không có một tường minh eval, shell cố gắng thực hiện kết quả của một sự thay thế lệnh, không đánh giá nó.

Nói rằng bạn muốn mã tương đương VAR=value; echo $VAR. Lưu ý sự khác biệt trong cách vỏ xử lý các bài viết của echo VAR=value:

  1. andcoz@...:~> $( echo VAR=value )
    bash: VAR=value: command not found
    andcoz@...:~> echo $VAR
    <empty line>
    

    Shell cố gắng thực thi echoVAR=valuenhư hai lệnh riêng biệt. Nó ném một lỗi về chuỗi thứ hai. Việc chuyển nhượng vẫn không hiệu quả.

  2. andcoz@...:~> eval $( echo VAR=value )
    andcoz@...:~> echo $VAR
    value
    
    Shell hợp nhất (nối) hai chuỗi echoVAR=value, phân tích đơn vị đơn lẻ này theo các quy tắc phù hợp và thực hiện nó.

Cuối cùng nhưng không kém phần quan trọng, evalcó thể là một mệnh lệnh rất nguy hiểm. Bất kỳ đầu vào cho một evallệnh phải được kiểm tra cẩn thận để tránh các vấn đề bảo mật.


18

evalkhông có trang man vì nó không phải là một lệnh bên ngoài riêng biệt, mà là một shell được tích hợp sẵn, nghĩa là một lệnh bên trong và chỉ được biết bởi shell ( bash). Phần có liên quan của bashtrang người đàn ông nói:

eval [arg ...]
    The args are read and concatenated together into a single command.  
    This command is then  read  and executed by the shell, and its exit 
    status is returned as the value of eval.  If there are no args, or only 
    null arguments, eval returns 0

Ngoài ra, đầu ra nếu help evallà:

eval: eval [arg ...]
    Execute arguments as a shell command.

    Combine ARGs into a single string, use the result as input to the shell,
    and execute the resulting commands.

    Exit Status:
    Returns exit status of command or success if command is null.

evallà một lệnh mạnh mẽ và nếu bạn có ý định sử dụng nó, bạn nên hết sức cẩn thận để tránh các rủi ro bảo mật có thể xảy ra với nó.


16

Câu lệnh eval nói với shell để lấy các đối số của eval làm lệnh và chạy chúng thông qua dòng lệnh. Nó rất hữu ích trong một tình huống như dưới đây:

Trong tập lệnh của bạn nếu bạn đang xác định một lệnh thành một biến và sau này bạn muốn sử dụng lệnh đó thì bạn nên sử dụng eval:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >

3
Nhận xét trong dòng 4 của ví dụ của bạn là sai: Đó là "Lệnh trên không hoạt động vì bash đã cố gắng tìm một lệnh được gọi ls | more. Nói cách khác: Tên lệnh duy nhất bao gồm chín ký tự, bao gồm khoảng trắng và ký hiệu ống dẫn .
cfi

Tôi nhận được chính xác hành vi tương tự anh báo cáo. bash --version == GNU bash, phiên bản 3.2.57 (1) -release (x86_64-apple-darwin18)
Alexander Bird

10

Eval là gì?

eval là một lệnh shell thường được thực hiện dưới dạng dựng sẵn.

Trong POSIX, nó được liệt kê như một phần của "2.14. Tiện ích tích hợp đặc biệt" tại mục "eval" .
Nội dung có nghĩa là:

Thuật ngữ "tích hợp" ngụ ý rằng trình bao có thể thực thi trực tiếp tiện ích và không cần tìm kiếm nó.

Nó làm gì?

Nói một cách đơn giản: làm cho một dòng đầu vào được phân tích cú pháp hai lần .

sao làm được vậy?

Shell có một chuỗi các bước mà nó tuân theo để "xử lý" một dòng. Bạn có thể nhìn vào hình ảnh này và nhận ra rằng eval là dòng duy nhất đi lên, quay lại bước 1, bên trái. Từ mô tả POSIX :

2.1 Giới thiệu Shell

  1. Shell đọc đầu vào của nó ....
  2. Shell phá vỡ đầu vào thành mã thông báo: từ và toán tử
  3. Shell phân tích cú pháp đầu vào thành các lệnh đơn giản và ghép.
  4. Shell thực hiện các mở rộng khác nhau (riêng biệt) ...
  5. Shell thực hiện chuyển hướng và loại bỏ các toán tử chuyển hướng và toán hạng của chúng khỏi danh sách tham số.
  6. Shell thực thi một chức năng, tập tin tích hợp, tập tin thực thi hoặc tập lệnh ...
  7. Shell tùy chọn chờ lệnh hoàn thành và thu thập trạng thái thoát.

Ở bước 6, một tích hợp sẽ được thực thi.
Ở bước 6 eval làm cho dòng được xử lý được gửi trở lại bước 1.
Đây là điều kiện duy nhất theo đó trình tự thực hiện quay trở lại.

Đó là lý do tại sao tôi nói: Với eval, một dòng đầu vào được phân tích cú pháp hai lần .

Tác dụng của phân tích cú pháp hai lần.

Việc đầu tiên.

Và hiệu quả quan trọng nhất để hiểu. Có phải đó là một hệ quả của lần đầu tiên một dòng chịu bảy bước vỏ được trình bày ở trên, được trích dẫn . Bên trong bước 4 (bản mở rộng), cũng có một chuỗi các bước để thực hiện tất cả các bản mở rộng , phần cuối cùng là Trích dẫn loại bỏ :

Trích dẫn loại bỏ sẽ luôn luôn được thực hiện cuối cùng.

Vì vậy, luôn luôn, có một mức trích dẫn được loại bỏ.

Thứ hai.

Do hậu quả của hiệu ứng đầu tiên đó, các phần bổ sung / khác nhau của dòng trở nên tiếp xúc với phân tích cú pháp shell và tất cả các bước khác.

Thí dụ.

Vô cảm.

Điều đó cho phép thực hiện mở rộng gián tiếp:

a=b b=c    ;    eval echo \$$a            ### shall produce "c"

Tại sao? Bởi vì trên vòng lặp đầu tiên, đầu tiên $được trích dẫn.
Như vậy, nó được bỏ qua cho việc mở rộng bởi vỏ.
Cái tiếp theo $với tên a được mở rộng để tạo ra "b".
Sau đó, một mức trích dẫn được loại bỏ, làm cho lần đầu tiên không $được trích dẫn.
Kết thúc vòng lặp đầu tiên.

Sau đó, trên vòng lặp thứ hai, chuỗi $bđược đọc bởi trình bao.
Sau đó mở rộng thành "c"
và đưa ra làm đối số cho echo.

Để "xem" những gì eval sẽ tạo ra trên vòng lặp đầu tiên (sẽ được đánh giá lại), hãy sử dụng echo. Hoặc bất kỳ lệnh / script / chương trình nào hiển thị rõ ràng các đối số:

$ a=b b=c
$ eval echo \$$a;
c

Thay thế eval bằng echo để "xem" những gì đang xảy ra:

$ echo echo \$$a
echo $b

Cũng có thể hiển thị tất cả "phần" của một dòng với:

$ printf '<%s> ' echo \$$a
<echo> <$b>

Trong ví dụ này, chỉ có một tiếng vang và một biến, nhưng hãy nhớ nó để giúp đánh giá các trường hợp phức tạp hơn.

Một điểm chính xác.

Phải nói rằng: có một lỗi trong đoạn mã trên, bạn có thể thấy nó không?.
Dễ dàng: có một số trích dẫn bị thiếu.

Làm sao? bạn có thể yêu cầu. Đơn giản, hãy thay đổi các biến (không phải mã):

$ a=b b="hi     jk"
$ eval echo \$$a
hi jk

Thấy chỗ trống?
Đó là bởi vì giá trị bên trong $bđã bị chia tách bởi vỏ.

Nếu điều đó không thuyết phục bạn, hãy thử điều này:

$ a=b b="hi  *  jk"
$ eval echo \$$a              ### warning this will expand to the list  
                              ### of all files in the present directory.

Tại sao?

Thiếu dấu ngoặc kép. Để làm cho nó hoạt động chính xác (thêm dấu ngoặc kép "$a"bên trong và bên ngoài \").
Hãy thử điều này (hoàn toàn an toàn):

$ a=b b="hi      *       jk"
$ eval echo \" \$"$a" \"
hi      *       jk

Về hướng dẫn:

Không có trang nào cho nó ..

Không, không có một trang người đàn ông độc lập cho việc này. Việc tìm kiếm hướng dẫn sử dụng có man -f evalhoặc thậm chí apropos evalkhông hiển thị mục nhập.

Nó được bao gồm bên trong man bash. Như là bất kỳ tích hợp.
Tìm kiếm "SHELL BUILTIN THÔNG TIN" và sau đó cho "eval".

Một cách dễ dàng hơn để nhận trợ giúp là: Trong bash, bạn có thể làm help evalđể xem trợ giúp cho tích hợp.

Tại sao eval được gọi là ác?

Bởi vì nó là văn bản ràng buộc để mã động.

Nói cách khác: nó chuyển đổi danh sách các đối số của nó (và / hoặc mở rộng các đối số đó) thành một dòng thực thi. Nếu vì bất kỳ lý do nào, một đối số đã được đặt bởi kẻ tấn công, bạn sẽ thực thi mã kẻ tấn công.

Hoặc thậm chí đơn giản hơn, với eval bạn đang nói với bất kỳ ai xác định giá trị của một hoặc một số đối số:

Thôi nào, ngồi đây và gõ bất kỳ dòng lệnh nào, tôi sẽ thực thi nó với quyền hạn của mình.

Điều đó có nguy hiểm không? Nên rõ ràng cho mọi người rằng nó là.

Quy tắc an toàn cho eval nên là:
Chỉ thực hiện eval trên các biến mà bạn đã đưa ra giá trị của nó.

Đọc thêm chi tiết tại đây .


10

evallà một tính năng của ngôn ngữ giải thích nhất ( TCL, python, ruby...), không chỉ vỏ. Nó được sử dụng để đánh giá mã động.

Trong shell, nó được triển khai như một lệnh dựng sẵn shell.

Về cơ bản, evallấy một chuỗi làm đối số và đánh giá / giải thích mã trong đó. Trong shell, evalcó thể lấy nhiều hơn một đối số, nhưng evalchỉ cần ghép các đối số đó để tạo thành chuỗi để đánh giá.

Nó rất mạnh bởi vì bạn có thể xây dựng mã một cách linh hoạt và chạy nó, điều mà bạn không thể làm trong các ngôn ngữ được biên dịch như C.

Như:

varname=$1 varvalue=$2
eval "$varname=\$varvalue" # evaluate a string like "foo=$varvalue"
                           # which in Bourne-like shell language
                           # is a variable assignment.

Nhưng nó cũng nguy hiểm vì điều quan trọng là phải vệ sinh các phần động (được cung cấp bên ngoài) của những gì được truyền cho evalvì lý do chính nó được hiểu là mã vỏ.

Ví dụ, ở trên nếu $1evil-command; var, evalcuối cùng sẽ đánh giá evil-command; var=$varvaluemã shell và sau đó chạy nó evil-command.

Sự xấu xa của evalthường được phóng đại quá mức.

OK, nó nguy hiểm, nhưng ít nhất chúng ta biết nó nguy hiểm.

Rất nhiều lệnh khác sẽ đánh giá mã shell trong đối số của nó nếu không vệ sinh, giống như (tùy thuộc vào vỏ), [aka test, export, printf, GNU sed, awkvà dĩ nhiên sh/ bash/ perlvà tất cả các thông dịch viên ...

Ví dụ (ở đây sử dụng unamenhư evil-command$anhư bên ngoài cung cấp dữ liệu unsanitized):

$ a='$(uname>&2)' sh -c 'eval "echo $a"'
Linux

$ a='x[0$(uname>&2)]' mksh -c 'export "$a=$b"'
Linux
$ a='x[0$(uname>&2)]' ksh93 -c 'printf "%d\n" "$a"'
Linux
0
$ a='x[0$(uname>&2)]' ksh93 -c '[ "$a" -gt 0 ]'
Linux
$ a=$'bar/g;e uname>&2\n;s//'; echo foo | sed "s/foo/$a/g"
Linux
bar
$ a='";system("uname");"'; awk "BEGIN{print \"$a\"}"

Linux
$ a=';uname'; sh -c "echo $a"

Linux

Những sed, export... lệnh có thể được coi là nguy hiểm hơn vì trong khi nó rõ ràng eval "$var"sẽ làm cho nội dung của $varđược đánh giá là mã shell, nó không phải là quá rõ ràng với sed "s/foo/$var/"hoặc export "$var=value"hoặc [ "$var" -gt 0 ]. Sự nguy hiểm là như nhau, nhưng nó được che giấu trong các lệnh khác.


@BinaryZebra, sedgiống như evalđang được truyền một chuỗi chứa trong một biến và đối với cả hai, nội dung của chuỗi đó cuối cùng được đánh giá là mã shell, vì vậy sedcũng nguy hiểm như evalnhững gì tôi đang nói. sedđang được thông qua một chuỗi chứa uname(uname chưa được thực thi cho đến nay) và thông qua việc gọi sed, lệnh uname kết thúc được thực thi. Thích cho eval. Trong sed 's/foo/$a/g', bạn không chuyển dữ liệu không được xác nhận đến sed, đó không phải là những gì chúng ta đang nói ở đây.
Stéphane Chazelas

2

Ví dụ này có thể làm sáng tỏ:

#!/bin/bash
VAR1=25
VAR2='$VAR1'
VAR3='$VAR2'
echo "$VAR3"
eval echo "$VAR3"
eval eval echo "$VAR3"

Đầu ra của đoạn script trên:

$VAR2
$VAR1
25

Cảm ơn sự đóng góp của bạn, nhưng, cũng thú vị như chúng, chúng tôi không thực sự quan tâm đến việc biên soạn một danh sách các ví dụ (hơi khác biệt) về việc sử dụng eval. Bạn có tin rằng có một số khía cạnh cơ bản, quan trọng của chức năng evalkhông được đề cập trong các câu trả lời hiện có không? Nếu vậy, sau đó giải thích nó, và sử dụng ví dụ để minh họa giải thích của bạn. Xin vui lòng không trả lời trong các ý kiến; chỉnh sửa  câu trả lời của bạn để làm cho nó rõ ràng và đầy đủ hơn.
Scott
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.