lệnh eval trong Bash và sử dụng điển hình của nó


165

Sau khi đọc các trang bash man và liên quan đến bài viết này .

Tôi vẫn gặp khó khăn trong việc hiểu chính xác evallệnh này làm gì và đó sẽ là cách sử dụng điển hình của nó. Ví dụ: nếu chúng ta làm:

bash$ set -- one two three  # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n}       ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n)       ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one

Chính xác thì chuyện gì đang xảy ra ở đây và làm thế nào để đồng đô la và dấu gạch chéo ngược vào vấn đề?


1
Đối với hồ sơ, nỗ lực thứ hai hoạt động. $($n)chạy $ntrong một subshell. Nó cố chạy lệnh 1không tồn tại.
Martin Wickman

1
@MartinWickman Nhưng yêu cầu là phải chạy echo $1cuối cùng chứ không phải 1. Tôi không nghĩ rằng nó có thể được thực hiện bằng cách sử dụng subshells.
Hari Menon


1
@ Raze2dust: Tôi không tin rằng anh ta đã gợi ý rằng nó có thể được chạy với các mạng con, nhưng giải thích tại sao lệnh thứ 5 mà OP liệt kê không hoạt động.
jedwards

Câu trả lời:


196

evallấy một chuỗi làm đối số của nó và đánh giá nó như thể bạn đã gõ chuỗi đó trên một dòng lệnh. (Nếu bạn vượt qua một số đối số, đầu tiên chúng được nối với khoảng trắng giữa chúng.)

${$n}là một lỗi cú pháp trong bash. Bên trong dấu ngoặc nhọn, bạn chỉ có thể có một tên biến, với một số tiền tố và hậu tố có thể, nhưng bạn không thể có cú pháp bash tùy ý và đặc biệt bạn không thể sử dụng mở rộng biến. Mặc dù vậy, có một cách để nói giá trị của biến có tên trong biến này, mặc dù:

echo ${!n}
one

$(…)chạy lệnh được chỉ định bên trong dấu ngoặc đơn trong một khung con (nghĩa là trong một quy trình riêng biệt kế thừa tất cả các cài đặt, chẳng hạn như các giá trị biến từ trình bao hiện tại) và tập hợp đầu ra của nó. Vì vậy, echo $($n)chạy $nnhư một lệnh shell, và hiển thị đầu ra của nó. Kể từ khi $nđánh giá 1, $($n)cố gắng chạy lệnh 1, không tồn tại.

eval echo \${$n}chạy các tham số được truyền đến eval. Sau khi mở rộng, các tham số là echo${1}. Vì vậy, eval echo \${$n}chạy lệnh echo ${1}.

Lưu ý rằng hầu hết thời gian, bạn phải sử dụng dấu ngoặc kép xung quanh các thay thế thay thế và thay thế lệnh (tức là bất cứ lúc nào có a $) : "$foo", "$(foo)". Luôn đặt dấu ngoặc kép xung quanh các thay thế biến và lệnh , trừ khi bạn biết bạn cần phải bỏ chúng đi. Không có dấu ngoặc kép, shell thực hiện phân tách trường (nghĩa là nó tách giá trị của biến hoặc đầu ra từ lệnh thành các từ riêng biệt) và sau đó coi mỗi từ là một mẫu ký tự đại diện. Ví dụ:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

evalkhông được sử dụng rất thường xuyên. Trong một số shell, việc sử dụng phổ biến nhất là lấy giá trị của một biến có tên không được biết cho đến khi runtime. Trong bash, điều này là không cần thiết nhờ ${!VAR}cú pháp. evalvẫn hữu ích khi bạn cần xây dựng một lệnh dài hơn chứa các toán tử, các từ dành riêng, v.v.


Đối với nhận xét của tôi ở trên, eval làm được bao nhiêu "lượt"?
kstratis

@ Konos5 Nhận xét gì? evalnhận được một chuỗi (có thể chính nó là kết quả của phân tích cú pháp và đánh giá) và diễn giải nó như một đoạn mã.
Gilles 'SO- ngừng trở nên xấu xa'

Theo câu trả lời của Raze2dust, tôi đã để lại một bình luận. Bây giờ tôi có xu hướng tin rằng eval chủ yếu được sử dụng cho mục đích hội nghị. Nếu tôi gõ eval echo \ $ {$ n} tôi sẽ nhận được một. Tuy nhiên nếu tôi gõ echo \ $ {$ n} thì tôi nhận được \ $ {1}. Tôi tin rằng điều này xảy ra do phân tích cú pháp "hai vượt qua" của eval. Bây giờ tôi đang tự hỏi điều gì sẽ xảy ra nếu tôi cần tăng gấp ba lần sử dụng một khai báo i = n. Trong trường hợp này theo Raze2dust tôi chỉ cần đặt thêm một eval. Tuy nhiên, tôi tin rằng nên có một cách tốt hơn ... (nó có thể dễ bị lộn xộn)
kstratis

@ Konos5 Tôi sẽ không sử dụng eval eval. Tôi không thể nhớ bao giờ cảm thấy cần thiết. Nếu bạn thực sự cần hai evallượt, hãy sử dụng một biến tạm thời, việc gỡ lỗi sẽ dễ dàng hơn : eval tmp="\${$i}"; eval x="\${$tmp}".
Gilles 'SO- ngừng trở nên xấu xa'

1
@ Konos5 "Phân tích hai lần" là hơi sai lệch. Một số người có thể được tin rằng điều này do khó khăn trong việc chỉ định một đối số chuỗi theo nghĩa đen trong Bash được bảo vệ khỏi các bản mở rộng khác nhau. evalchỉ cần lấy mã trong một chuỗi và đánh giá nó theo các quy tắc thông thường. Về mặt kỹ thuật, điều đó thậm chí không đúng, bởi vì có một vài trường hợp góc trong đó Bash sửa đổi phân tích cú pháp để thậm chí không thực hiện các mở rộng cho các đối số của eval - nhưng đó là một câu chuyện rất mơ hồ mà tôi nghi ngờ bất kỳ ai biết.
ormaaj

39

Đơn giản chỉ cần nghĩ về eval là "đánh giá biểu thức của bạn thêm một lần nữa trước khi thực hiện"

eval echo \${$n}trở thành echo $1sau vòng đánh giá đầu tiên. Ba thay đổi cần chú ý:

  • Đã \$trở thành $(Dấu gạch chéo ngược là cần thiết, nếu không, nó cố gắng đánh giá ${$n}, có nghĩa là một biến có tên {$n}, không được phép)
  • $n được đánh giá 1
  • Sự evalbiến mất

Trong vòng thứ hai, về cơ bản echo $1nó có thể được thực hiện trực tiếp.

Vì vậy, eval <some command>trước tiên sẽ đánh giá <some command>(bằng cách đánh giá ở đây tôi có nghĩa là các biến thay thế, thay thế các ký tự thoát bằng các ký tự chính xác, v.v.), và sau đó chạy biểu thức kết quả một lần nữa.

evalđược sử dụng khi bạn muốn tự động tạo các biến hoặc để đọc kết quả đầu ra từ các chương trình được thiết kế đặc biệt để được đọc như thế này. Xem http://mywiki.wooledge.org/BashFAQ/048 để biết ví dụ. Liên kết cũng chứa một số cách điển hình evalđược sử dụng và các rủi ro liên quan đến nó.


3
Như một lưu ý cho viên đạn đầu tiên, ${VAR}cú pháp được cho phép và được ưu tiên khi có bất kỳ sự mơ hồ nào (không $VAR == $V, theo sau ARhoặc $VAR == $VAtheo sau R). ${VAR}tương đương với $VAR. Trong thực tế, tên biến của $nnó không được phép.
jedwards

2
eval eval echo \\\${\${$i}}sẽ làm một sự bổ nhiệm ba. Tôi không chắc có cách nào đơn giản hơn để làm điều đó không. Ngoài ra, \${$n}hoạt động tốt (bản in one) trên máy của tôi ..
Hari Menon

2
@ Konos5 echo \\\${\${$i}}in \${${n}}. eval echo \\\${\${$i}}tương đương với echo \${${n}}`` and prints $ {1} . eval eval echo \\\ $ {\ $ {$ i}} `tương đương với eval echo ${1}và in one.
Gilles 'SO- ngừng trở nên xấu xa'

2
@ Konos5 Suy nghĩ theo cùng một dòng - ` escapes the second one, and the third `` đầu tiên thoát khỏi $sau đó. Vì vậy, nó trở thành \${${n}}sau một vòng đánh giá
Hari Menon

2
@ Konos5 Từ trái sang phải là cách suy nghĩ đúng đắn để trích dẫn và phân tích cú pháp ngược. Đầu tiên \\ mang lại một dấu gạch chéo ngược. Sau đó thu được \$một đô la. Và như thế.
Gilles 'SO- ngừng trở nên xấu xa'

25

Theo kinh nghiệm của tôi, việc sử dụng eval "điển hình" là để chạy các lệnh tạo lệnh shell để đặt các biến môi trường.

Có lẽ bạn có một hệ thống sử dụng một tập hợp các biến môi trường và bạn có một tập lệnh hoặc chương trình xác định cái nào sẽ được đặt và giá trị của chúng. Bất cứ khi nào bạn chạy một tập lệnh hoặc chương trình, nó sẽ chạy trong một quá trình rẽ nhánh, do đó, bất cứ điều gì nó làm trực tiếp với các biến môi trường đều bị mất khi thoát. Nhưng tập lệnh hoặc chương trình đó có thể gửi các lệnh xuất tới thiết bị xuất chuẩn.

Nếu không có eval, bạn sẽ cần chuyển hướng thiết bị xuất chuẩn sang tệp tạm thời, nguồn tệp tạm thời và sau đó xóa tệp đó. Với eval, bạn có thể:

eval "$(script-or-program)"

Lưu ý các trích dẫn là quan trọng. Lấy ví dụ này (giả định):

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!

có bất kỳ ví dụ về các công cụ phổ biến làm điều này? Bản thân công cụ này có phương tiện để tạo ra một tập hợp các lệnh shell có thể được truyền cho eval không?
Joakim Erdfelt

@Joakim Tôi không biết bất kỳ công cụ mã nguồn mở nào làm việc đó, nhưng nó đã được sử dụng trong một số tập lệnh riêng tại các công ty nơi tôi đã làm việc. Tôi chỉ bắt đầu sử dụng kỹ thuật này một lần nữa với xampp. Các tệp .cs của Apache mở rộng các biến môi trường được viết ${varname}. Tôi thấy thuận tiện khi sử dụng các tệp .conf giống hệt nhau trên một số máy chủ khác nhau chỉ với một vài điều được tham số hóa bởi các biến môi trường. Tôi đã chỉnh sửa / opt / lampp / xampp (bắt đầu apache) để thực hiện loại eval này với một tập lệnh chọc quanh hệ thống và đưa ra các exportcâu lệnh bash để xác định các biến cho các tệp .conf.
sootsnoot

@Joakim Giải pháp thay thế sẽ là có một tập lệnh để tạo từng tệp .conf bị ảnh hưởng từ một mẫu, dựa trên cùng một câu chọc. Một điều tôi thích hơn theo cách của tôi là bắt đầu apache mà không thông qua / opt / lampp / xampp không sử dụng các tập lệnh đầu ra cũ, nhưng nó không khởi động được vì các biến môi trường mở rộng thành không có gì và tạo ra các lệnh không hợp lệ.
sootsnoot

@Anthony Sottile Tôi thấy bạn đã chỉnh sửa câu trả lời để thêm dấu ngoặc kép quanh $ (script hoặc chương trình), nói rằng chúng rất quan trọng khi chạy nhiều lệnh. Bạn có thể vui lòng cung cấp một ví dụ - phần sau hoạt động tốt với các lệnh được phân tách bằng dấu hai chấm trong thiết bị xuất chuẩn của foo.sh: echo '#! / Bin / bash'> foo.sh; echo 'echo "echo -na; echo -nb; echo -n c"' >> foo.sh; chmod 755 foo.sh; eval $ (./ foo.sh). Điều này tạo ra abc trên thiết bị xuất chuẩn. Chạy ./foo.sh tạo ra: echo -na; tiếng vang -nb; echo -nc
sootsnoot

1
Để biết ví dụ về một công cụ phổ biến sử dụng eval, xem pyenv . pyenv cho phép bạn dễ dàng chuyển đổi giữa nhiều phiên bản Python. Bạn đặt eval "$(pyenv init -)"vào .bash_profiletập tin cấu hình shell (hoặc tương tự) của bạn . Điều đó xây dựng một kịch bản shell nhỏ sau đó đánh giá nó trong shell hiện tại.
Jerry101

10

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 >

4

Cập nhật: Một số người nói rằng mọi người nên sử dụng eval. Tôi không đồng ý. Tôi nghĩ rằng rủi ro phát sinh khi đầu vào tham nhũng có thể được chuyển qua eval. Tuy nhiên, có nhiều tình huống phổ biến trong đó không phải là rủi ro, và do đó, đáng để biết cách sử dụng eval trong mọi trường hợp. Câu trả lời stackoverflow này giải thích các rủi ro của eval và các lựa chọn thay thế cho eval. Cuối cùng, tùy thuộc vào người dùng để xác định xem / khi eval an toàn và hiệu quả để sử dụng.


Câu evallệnh bash cho phép bạn thực thi các dòng mã được tính hoặc thu được, bằng tập lệnh bash của bạn.

Có lẽ ví dụ đơn giản nhất sẽ là một chương trình bash mở một tập lệnh bash khác dưới dạng tệp văn bản, đọc từng dòng văn bản và sử dụng evalđể thực hiện chúng theo thứ tự. Về cơ bản đó là hành vi tương tự như sourcecâu lệnh bash , đó là những gì người ta sẽ sử dụng, trừ khi cần phải thực hiện một số loại chuyển đổi (ví dụ: lọc hoặc thay thế) trên nội dung của tập lệnh được nhập.

Tôi hiếm khi cần eval, nhưng tôi thấy hữu ích khi đọc hoặc viết các biến có tên được chứa trong các chuỗi được gán cho các biến khác. Ví dụ, để thực hiện các hành động trên các bộ biến, trong khi giữ cho dấu chân mã nhỏ và tránh dư thừa.

evallà khái niệm đơn giản. Tuy nhiên, cú pháp chặt chẽ của ngôn ngữ bash và thứ tự phân tích cú pháp của trình thông dịch bash có thể bị thay đổi và làm cho nó trở nên evalkhó hiểu và khó sử dụng hoặc khó hiểu. Dưới đây là những điều cần thiết:

  1. Đối số được truyền vào evallà một biểu thức chuỗi được tính toán trong thời gian chạy. evalsẽ thực hiện kết quả được phân tích cú pháp cuối cùng của đối số dưới dạng một dòng mã thực tế trong tập lệnh của bạn.

  2. Cú pháp và thứ tự phân tích cú pháp là nghiêm ngặt. Nếu kết quả không phải là dòng mã bash có thể thực thi được, trong phạm vi tập lệnh của bạn, chương trình sẽ bị lỗi trên evalcâu lệnh khi nó cố gắng thực thi rác.

  3. Khi kiểm tra, bạn có thể thay thế evalcâu lệnh echovà xem những gì được hiển thị. Nếu đó là mã hợp pháp trong bối cảnh hiện tại, chạy nó evalsẽ hoạt động.


Các ví dụ sau đây có thể giúp làm rõ cách thức hoạt động của eval ...

Ví dụ 1:

eval tuyên bố trước mã 'bình thường' là một NOP

$ eval a=b
$ eval echo $a
b

Trong ví dụ trên, các evaltuyên bố đầu tiên không có mục đích và có thể được loại bỏ. evallà vô nghĩa trong dòng đầu tiên vì không có khía cạnh động đối với mã, tức là nó đã được phân tích cú pháp thành dòng cuối cùng của mã bash, do đó nó sẽ giống hệt như một câu lệnh mã thông thường trong tập lệnh bash. Thứ 2 evalcũng vô nghĩa, bởi vì, mặc dù có một bước phân tích chuyển đổi $athành chuỗi ký tự tương đương của nó, nhưng không có sự gián tiếp (ví dụ: không tham chiếu qua giá trị chuỗi của một danh từ bash thực tế hoặc biến tập lệnh bash), do đó, nó sẽ hoạt động giống hệt như một dòng mã không có evaltiền tố.



Ví dụ 2:

Thực hiện gán var bằng cách sử dụng tên var được truyền dưới dạng giá trị chuỗi.

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

Nếu bạn đã echo $key=$val, đầu ra sẽ là:

mykey=myval

Rằng , là kết quả cuối cùng của phân tích chuỗi, là những gì sẽ được thực thi bởi eval, do đó là kết quả của câu lệnh echo ở cuối ...



Ví dụ 3:

Thêm nhiều hướng dẫn hơn vào ví dụ 2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

Ở trên là một chút phức tạp hơn so với ví dụ trước, phụ thuộc nhiều hơn vào thứ tự phân tích cú pháp và đặc thù của bash. Các evaldòng sẽ xấp xỉ được phân tích nội bộ theo thứ tự sau (lưu ý những điều khoản sau đây là giả, không phải mã thực, chỉ cần cố gắng để hiển thị như thế nào tuyên bố sẽ được chia nhỏ thành các bước trong nội bộ để đi đến kết quả cuối cùng) .

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

Nếu thứ tự phân tích giả định không giải thích những gì eval đang làm đủ, ví dụ thứ ba có thể mô tả phân tích chi tiết hơn để giúp làm rõ những gì đang diễn ra.



Ví dụ 4:

Khám phá xem các vars, có tên được chứa trong chuỗi, bản thân chúng có chứa các giá trị chuỗi hay không.

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

Trong lần lặp đầu tiên:

varname="myvarname_a"

Bash phân tích cú pháp đối số evalevalthấy nghĩa đen này trong thời gian chạy:

eval varval=\$$myvarname_a

Mã giả sau đây cố gắng minh họa cách bash diễn giải dòng trên của mã thực , để đến giá trị cuối cùng được thực thi bởi eval. (các dòng mô tả sau đây, không phải mã bash chính xác):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

Một khi tất cả các phân tích cú pháp được thực hiện, kết quả là những gì được thực thi và hiệu quả của nó là rõ ràng, chứng tỏ không có gì đặc biệt bí ẩn về evalchính nó, và sự phức tạp nằm trong phân tích đối số của nó.

varval="User-provided"

Mã còn lại trong ví dụ trên chỉ đơn giản là kiểm tra xem giá trị được gán cho $ varval có phải là null không và nếu có, sẽ nhắc người dùng cung cấp giá trị.


3

Ban đầu tôi cố tình không bao giờ học cách sử dụng eval, bởi vì hầu hết mọi người sẽ khuyên bạn nên tránh xa nó như bệnh dịch hạch. Tuy nhiên, gần đây tôi đã phát hiện ra một trường hợp sử dụng khiến tôi phải đối mặt vì không nhận ra nó sớm hơn.

Nếu bạn có các công việc định kỳ mà bạn muốn chạy tương tác để kiểm tra, bạn có thể xem nội dung của tệp bằng mèo, sao chép và dán công việc định kỳ để chạy nó. Thật không may, điều này liên quan đến việc chạm vào chuột, đó là một tội lỗi trong cuốn sách của tôi.

Hãy nói rằng bạn có một công việc định kỳ tại /etc/cron.d/repeatme với nội dung:

*/10 * * * * root program arg1 arg2

Bạn không thể thực thi điều này như một tập lệnh với tất cả rác ở phía trước nó, nhưng chúng ta có thể sử dụng cắt để loại bỏ tất cả rác, bọc nó trong một khung con và thực hiện chuỗi bằng eval

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

Lệnh cắt chỉ in ra trường thứ 6 của tệp, được phân cách bằng dấu cách. Eval sau đó thực hiện lệnh đó.

Tôi đã sử dụng một công việc định kỳ ở đây làm ví dụ, nhưng khái niệm là định dạng văn bản từ thiết bị xuất chuẩn, và sau đó đánh giá văn bản đó.

Việc sử dụng eval trong trường hợp này không phải là không an toàn, bởi vì chúng tôi biết chính xác những gì chúng tôi sẽ đánh giá trước khi ra tay.


2

Gần đây tôi đã phải sử dụng evalđể buộc phải mở rộng nhiều niềng răng theo thứ tự tôi cần. Bash thực hiện nhiều lần mở rộng cú đúp từ trái sang phải, vì vậy

xargs -I_ cat _/{11..15}/{8..5}.jpg

mở rộng đến

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

nhưng tôi cần mở rộng cú đúp thứ hai được thực hiện trước, mang lại

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Điều tốt nhất tôi có thể nghĩ ra để làm điều đó là

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

Điều này hoạt động vì các dấu ngoặc đơn bảo vệ bộ dấu ngoặc đầu tiên khỏi sự mở rộng trong quá trình phân tích cú pháp của evaldòng lệnh, khiến chúng được mở rộng bởi lớp con được gọi bởi eval.

Có thể có một số kế hoạch xảo quyệt liên quan đến việc mở rộng niềng răng lồng nhau cho phép điều này xảy ra trong một bước, nhưng nếu có thì tôi quá già và ngu ngốc khi nhìn thấy nó.


1

Bạn hỏi về cách sử dụng điển hình.

Một khiếu nại phổ biến về kịch bản shell là bạn (được cho là) ​​không thể vượt qua tham chiếu để lấy lại các giá trị ngoài chức năng.

Nhưng thực tế, thông qua "eval", bạn có thể vượt qua bằng cách tham khảo. Callee có thể gửi lại một danh sách các bài tập biến để người gọi đánh giá. Nó được truyền bằng tham chiếu vì người gọi có thể cho phép chỉ định (các) tên của (các) biến kết quả - xem ví dụ bên dưới. Kết quả lỗi có thể được chuyển trở lại tên tiêu chuẩn như errno và errstr.

Dưới đây là một ví dụ về chuyển qua tham chiếu trong bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

Đầu ra trông như thế này:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

Có độ rộng băng tần gần như không giới hạn trong đầu ra văn bản đó! Và có nhiều khả năng hơn nếu nhiều dòng đầu ra được sử dụng: ví dụ: dòng đầu tiên có thể được sử dụng cho các bài tập biến đổi, dòng thứ hai cho "dòng suy nghĩ" liên tục, nhưng vượt quá phạm vi của bài này.


nói rằng có "nhiều khả năng hơn" là không đáng kể, tầm thường và dư thừa để nói rằng ít nhất.
dotbit

0

Tôi thích câu trả lời "đánh giá biểu thức của bạn thêm một lần nữa trước khi thực hiện" và muốn làm rõ bằng một ví dụ khác.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

Kết quả tò mò trong Tùy chọn 2 là chúng ta sẽ truyền 2 tham số như sau:

  • Thông số đầu tiên: "value
  • Thông số thứ hai: content"

Làm thế nào là cho truy cập trực quan? Việc bổ sung evalsẽ khắc phục điều đó.

Chuyển thể từ https://stackoverflow.com/a/40646371/744133


0

Trong câu hỏi:

who | grep $(tty | sed s:/dev/::)

lỗi xuất ra tuyên bố rằng tập tin a và tty không tồn tại. Tôi hiểu điều này có nghĩa là tty không được giải thích trước khi thực hiện grep, nhưng thay vào đó, bash đã chuyển tty như một tham số cho grep, nó diễn giải nó như một tên tệp.

Ngoài ra còn có một tình huống chuyển hướng lồng nhau, được xử lý bằng dấu ngoặc đơn phù hợp sẽ chỉ định một quy trình con, nhưng bash chủ yếu là một dấu tách từ, tạo các tham số được gửi đến một chương trình, do đó, dấu ngoặc đơn không được khớp trước, nhưng được hiểu là đã xem.

Tôi đã nhận được cụ thể với grep và chỉ định tệp dưới dạng tham số thay vì sử dụng đường ống. Tôi cũng đã đơn giản hóa lệnh cơ sở, chuyển đầu ra từ một lệnh dưới dạng tệp, để đường ống i / o sẽ không được lồng nhau:

grep $(tty | sed s:/dev/::) <(who)

hoạt động tốt

who | grep $(echo pts/3)

không thực sự mong muốn, nhưng loại bỏ các ống lồng nhau và cũng hoạt động tốt.

Tóm lại, bash dường như không thích pipping lồng nhau. Điều quan trọng là phải hiểu rằng bash không phải là một chương trình sóng mới được viết theo cách đệ quy. Thay vào đó, bash là một chương trình 1,2,3 cũ, đã được gắn thêm các tính năng. Đối với mục đích đảm bảo khả năng tương thích ngược, cách giải thích ban đầu chưa bao giờ được sửa đổi. Nếu bash được viết lại thành dấu ngoặc đơn đầu tiên, có bao nhiêu lỗi sẽ được đưa vào bao nhiêu chương trình bash? Nhiều lập trình viên thích được khó hiểu.

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.