คำสั่ง "eval" ใน bash คืออะไร?


176

คุณสามารถทำอะไรกับevalคำสั่ง? ทำไมถึงมีประโยชน์ มันมีฟังก์ชั่นในตัวในการทุบตี? ไม่มีmanหน้าสำหรับมัน ..


28
ใช้type commandเพื่อเรียนรู้ว่าคำสั่งประเภทใด ( type evalในกรณีนี้)
rozcietrzewiacz

9
"eval is shell builtin"
Nuno Rafael Figueiredo

3
help evalเพื่อรับหน้า "คน" จากเปลือกของคุณ
m-ric

eval เป็น bash-builtin และบันทึกไว้ใน man page ของ bash ดังนั้นเพียงแค่พิมพ์ "man bash" และค้นหาหัวข้อที่เหมาะสมสำหรับ eval สิ่งนี้ใช้ได้กับ bash-builtins อื่นด้วย
Dirk Thannhäuser

คำตอบ:


132

evalเป็นส่วนหนึ่งของ POSIX มันเป็นอินเตอร์เฟซที่สามารถเป็นเปลือกในตัว

มันอธิบายไว้ใน "คู่มือโปรแกรมเมอร์ POSIX": http://www.unix.com/man-page/posix/1posix/eval/

eval - construct command by concatenating arguments

มันจะรับอาร์กิวเมนต์และสร้างคำสั่งของมันซึ่งจะถูกดำเนินการโดยเชลล์ นี่คือตัวอย่างของ manpage:

1) foo=10 x=foo
2) y='$'$x
3) echo $y
4) $foo
5) eval y='$'$x
6) echo $y
7) 10
  1. ในบรรทัดแรกที่คุณกำหนด$fooด้วยค่า'10'และมีค่า$x'foo'
  2. ตอนนี้กำหนดซึ่งประกอบด้วยสตริง$y เครื่องหมายดอลลาร์จะต้องหนีด้วย'$foo''$'
  3. เพื่อตรวจสอบผลลัพธ์, echo $y.
  4. ผลลัพธ์จะเป็นสตริง '$foo'
  5. evalตอนนี้เราทำซ้ำที่ได้รับมอบหมายด้วย มันจะประเมินค่า$xสตริง'foo'ก่อน ตอนนี้เรามีคำสั่งที่จะได้รับการประเมินเพื่อy=$fooy=10
  6. ผลมาจากในขณะนี้คือค่าecho $y'10'

นี่เป็นฟังก์ชั่นทั่วไปในหลายภาษาเช่น Perl และ JavaScript ดู perldoc eval เพื่อดูตัวอย่างเพิ่มเติม: http://perldoc.perl.org/functions/eval.html


4
ในเชลล์คำศัพท์evalเป็นแบบในตัวไม่ใช่ฟังก์ชั่น ในทางปฏิบัติบิวด์อินจะทำหน้าที่เหมือนกับฟังก์ชั่นที่ไม่มีคำจำกัดความในภาษา แต่ไม่มากevalนัก
Gilles

ขอบคุณคง :-) ว่า
echox

4
อะไรคือความแตกต่างระหว่าง Eval และ Backticks `
JohnyTex

5
Backticks กำลังจดชวเลขสำหรับการเรียกใช้เชลล์ทั้งหมดอีกครั้งซึ่งหมายความว่าคุณจะมีกระบวนการ bash / sh ลูกอื่นทำงานอยู่ในสคริปต์ของคุณ
แอรอนอาร์

ทำไม echo $ y (ด่าน 3) นำกลับมาฟู (10) แทนที่จะเป็น $ foo (เช่นเพียงแค่ค่าของ foo แทนสตริง $ + มูลค่าของ foo)
JohnDoea

60

ใช่evalเป็นคำสั่งภายใน bash ดังนั้นจึงได้อธิบายไว้ในbashman page

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.

มักจะถูกนำมาใช้ร่วมกับการแทนที่คำสั่ง โดยไม่ต้องชัดเจนevalเปลือกพยายามที่จะดำเนินการผลมาจากการแทนคำสั่งที่ไม่ประเมินมัน

VAR=value; echo $VARสมมติว่าคุณต้องการรหัสเทียบเท่า สังเกตความแตกต่างในวิธีที่เชลล์จัดการกับงานเขียนของecho VAR=value:

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

    เชลล์พยายามดำเนินการechoและVAR=valueเป็นสองคำสั่งแยกกัน มันจะโยนข้อผิดพลาดเกี่ยวกับสายที่สอง การมอบหมายดังกล่าวยังไม่มีประสิทธิภาพ

  2. andcoz@...:~> eval $( echo VAR=value )
    andcoz@...:~> echo $VAR
    value
    เชลล์ผสาน (เชื่อมโยง) สองสตริงechoและVAR=valueแยกวิเคราะห์ยูนิตเดี่ยวนี้ตามกฎที่เหมาะสมและดำเนินการ

สุดท้าย แต่ไม่evalท้ายสุดอาจเป็นคำสั่งที่อันตรายมาก การป้อนevalคำสั่งใด ๆ จะต้องตรวจสอบอย่างรอบคอบเพื่อหลีกเลี่ยงปัญหาด้านความปลอดภัย


18

evalไม่มี man page เนื่องจากไม่ใช่คำสั่งภายนอกที่แยกต่างหาก แต่เป็นเชลล์ในตัวหมายถึงคำสั่งภายในและโดยเชลล์ ( bash) เท่านั้น ส่วนที่เกี่ยวข้องของbashหน้าคนพูดว่า:

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

นอกจากนี้ผลลัพธ์ถ้าhelp evalเป็น:

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.

evalเป็นคำสั่งที่ทรงพลังและถ้าคุณตั้งใจจะใช้มันคุณควรระวังให้ดีเพื่อป้องกันความเสี่ยงที่อาจเกิดขึ้นจากการใช้งาน


16

คำสั่ง eval บอกให้เชลล์ใช้อาร์กิวเมนต์ของ eval เป็นคำสั่งและรันมันผ่าน command-line มันมีประโยชน์ในสถานการณ์เช่นนี้:

ในสคริปต์ของคุณหากคุณกำหนดคำสั่งให้เป็นตัวแปรและหลังจากนั้นคุณต้องการใช้คำสั่งนั้นแล้วคุณควรใช้ 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
ความคิดเห็นในบรรทัดที่ 4 ของตัวอย่างของคุณผิด: ควรเป็น "คำสั่ง Above ไม่ทำงานเพราะทุบตีพยายามหาคำสั่งที่เรียกว่าls | moreในคำอื่น ๆ : ชื่อคำสั่งเดียวประกอบด้วยอักขระเก้าตัวรวมถึงช่องว่างและสัญลักษณ์ไปป์ .
cfi

ฉันได้รับพฤติกรรมเดียวกับที่เขารายงาน bash --version == GNU bash รุ่น 3.2.57 (1) - ปล่อย (x86_64-apple-darwin18)
Alexander Bird

10

Eval คืออะไร

eval เป็นคำสั่งเชลล์ซึ่งโดยปกติจะนำมาใช้เป็น builtin

ใน POSIX มันถูกระบุว่าเป็นส่วนหนึ่งของ "2.14. พิเศษ Built-In ยูทิลิตี้"ในรายการ"EVAL"
สิ่งที่บิวอินหมายถึงคือ:

คำว่า "ในตัว" หมายถึงว่าเชลล์สามารถรันยูทิลิตีโดยตรงและไม่จำเป็นต้องค้นหามัน

มันทำอะไร?

ในแง่ง่ายๆทำให้สายการป้อนข้อมูลที่จะถูกแยกเป็นสองเท่า

มันทำอย่างไร

เชลล์มีลำดับขั้นตอนที่ตามมาเพื่อ "ประมวลผล" บรรทัด คุณสามารถดูภาพนี้และรู้ว่า eval เป็นบรรทัดเดียวที่ขึ้นไปกลับไปยังขั้นตอนที่ 1 ทางด้านซ้าย จากคำอธิบาย POSIX :

2.1 การแนะนำเชลล์

  1. เชลล์อ่านอินพุตของมัน ....
  2. เชลล์แบ่งอินพุตเป็นโทเค็น: คำและตัวดำเนินการ
  3. เชลล์แยกวิเคราะห์อินพุตเป็นคำสั่งแบบง่ายและแบบผสม
  4. เชลล์ทำการขยายที่หลากหลาย (แยกกัน) ...
  5. เชลล์ทำการเปลี่ยนเส้นทางและลบโอเปอเรเตอร์การเปลี่ยนเส้นทางและตัวถูกดำเนินการออกจากรายการพารามิเตอร์
  6. เชลล์เรียกใช้งานฟังก์ชันในตัวไฟล์ที่เรียกใช้งานได้หรือสคริปต์ ...
  7. เชลล์เลือกที่จะรอให้คำสั่งดำเนินการจนเสร็จสิ้นและรวบรวมสถานะการออก

ในขั้นตอนที่ 6 จะทำการประมวลผลในตัว
ในขั้นตอนที่ 6 eval ทำให้สายการประมวลผลถูกส่งกลับไปที่ขั้นตอนที่ 1
มันเป็นเงื่อนไขเดียวที่ลำดับการดำเนินการกลับไป

นั่นคือเหตุผลที่ผมบอกว่า: ด้วย EVAL สายการป้อนข้อมูลจะแยกเป็นสองเท่า

ผลของการแยกวิเคราะห์สองครั้ง

ครั้งแรก

และผลกระทบที่สำคัญที่สุดที่จะเข้าใจ เป็นที่หนึ่งเป็นผลมาจากครั้งแรกบรรทัดเป็นเรื่องที่เจ็ดขั้นตอนเปลือกแสดงข้างต้นเป็นข้อความ ภายในขั้นตอนที่ 4 (การขยาย) นอกจากนี้ยังมี ลำดับขั้นตอนในการดำเนินการขยายทั้งหมดซึ่งสุดท้ายคือการลบคำพูด :

การลบคำพูดจะต้องดำเนินการครั้งสุดท้ายเสมอ

ดังนั้นจึงมีการลบข้อความหนึ่งระดับ

ที่สอง

ผลที่ตามมาของเอฟเฟกต์แรกนั้นส่วนเพิ่มเติม / ส่วนต่าง ๆ ของบรรทัดจะถูกเปิดเผยกับการแยกวิเคราะห์เชลล์และขั้นตอนอื่น ๆ ทั้งหมด

ตัวอย่าง.

ความร้าย

ที่อนุญาตให้เรียกใช้งานการขยายทางอ้อม:

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

ทำไม? เพราะในวงแรกวงแรก$จะถูกยกมา
ดังนั้นเชลล์จะถูกละเว้นสำหรับการขยายตัว
ถัดไป$ด้วยชื่อ a ถูกขยายเพื่อสร้าง "b"
จากนั้นหนึ่งระดับของการอ้างอิงจะถูกลบออกทำให้$unquote แรก
จุดสิ้นสุดของลูปแรก

มันเป็นแล้วในวงที่สองที่สตริง$bจะอ่านโดยเปลือก
จากนั้นขยายไป "C" และได้รับเป็นอาร์กิวเมนต์ไปยัง
echo

หากต้องการ "ดู" สิ่งที่ eval จะสร้างในลูปแรก (เพื่อประเมินอีกครั้ง) ให้ใช้ echo หรือคำสั่ง / สคริปต์ / โปรแกรมใด ๆ ที่แสดงข้อโต้แย้งอย่างชัดเจน:

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

แทนที่ eval ด้วย echo เพื่อ "ดู" สิ่งที่เกิดขึ้น:

$ echo echo \$$a
echo $b

นอกจากนี้ยังเป็นไปได้ที่จะแสดง "ส่วน" ทั้งหมดของบรรทัดด้วย:

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

ซึ่งในตัวอย่างนี้เป็นเพียงเสียงสะท้อนเดียวและตัวแปรเดียว แต่จำไว้เพื่อช่วยในการประเมินกรณีที่ซับซ้อนมากขึ้น

การแก้ไข

ต้องบอกว่า: มีข้อผิดพลาดในรหัสข้างต้นคุณเห็นหรือไม่
ง่าย: มีบางคำพูดหายไป

อย่างไร? คุณอาจถาม ง่าย ๆ ลองเปลี่ยนตัวแปร (ไม่ใช่รหัส):

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

เห็นช่องว่างที่หายไป?
นั่นเป็นเพราะค่าภายใน$bถูกแบ่งโดยเชลล์

หากสิ่งนั้นไม่ทำให้คุณมั่นใจลองสิ่งนี้:

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

ทำไม?

คำพูดที่ขาดหายไป เพื่อให้ทำงานได้อย่างถูกต้อง (เพิ่มคำพูดภายใน"$a"และภายนอก\")
ลองนี้ (ปลอดภัยอย่างสมบูรณ์):

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

เกี่ยวกับคู่มือ:

ไม่มีหน้าคนสำหรับมัน ..

ไม่ไม่มีหน้าคนที่เป็นอิสระสำหรับเรื่องนี้ ค้นหาด้วยตนเองด้วยman -f evalหรือแม้กระทั่งapropos evalไม่แสดงรายการ

man bashมันรวมอยู่ภายใน เช่นเดียวกับในตัว
ค้นหา "คำสั่งเชลล์อาคาร" จากนั้นค้นหาคำว่า "eval"

วิธีที่ง่ายกว่าในการรับความช่วยเหลือคือ: ในการทุบตีคุณสามารถทำได้help evalเพื่อดูความช่วยเหลือสำหรับการใช้งานในตัว

ทำไมความชั่วจึงเรียกว่าความชั่ว?

เพราะมันเป็นข้อความที่มีผลผูกพันกับรหัสแบบไดนามิก

กล่าวอีกนัยหนึ่ง: มันจะแปลงรายการของอาร์กิวเมนต์ (และ / หรือการขยายของอาร์กิวเมนต์ดังกล่าว) เป็นบรรทัดที่ถูกดำเนินการ หากมีเหตุผลใดก็ตามอาร์กิวเมนต์ถูกตั้งค่าโดยผู้โจมตีคุณจะเรียกใช้งานรหัสผู้โจมตี

หรือแม้แต่ง่ายขึ้นด้วย eval คุณกำลังบอกใครก็ตามที่กำหนดค่าของหนึ่งหรือหลายอาร์กิวเมนต์:

C'mon นั่งที่นี่และพิมพ์บรรทัดคำสั่งใด ๆ ฉันจะดำเนินการด้วยพลังของฉัน

มันอันตรายไหม? ควรมีความชัดเจนสำหรับทุกคนเลยก็ว่าได้

กฎความปลอดภัยสำหรับการ eval ควร:
ดำเนินการ eval เฉพาะกับตัวแปรที่คุณได้รับมันคุ้มค่า

อ่านรายละเอียดเพิ่มเติมได้ที่นี่


10

evalเป็นคุณลักษณะของการแปลภาษามากที่สุด (กTCL, python, ruby... ) ไม่เพียง แต่เปลือกหอย มันถูกใช้เพื่อประเมินโค้ดแบบไดนามิก

ในเชลล์มันถูกใช้เป็นคำสั่ง shell builtin

โดยทั่วไปevalรับสตริงเป็นอาร์กิวเมนต์และประเมิน / ตีความรหัสในนั้น ใน shells evalสามารถรับได้มากกว่าหนึ่งอาร์กิวเมนต์ แต่evalเพียงเชื่อมต่อสิ่งเหล่านั้นเพื่อสร้างสตริงเพื่อประเมิน

มีประสิทธิภาพมากเพราะคุณสามารถสร้างรหัสแบบไดนามิกและเรียกใช้สิ่งที่คุณไม่สามารถทำได้ในภาษาที่รวบรวมเช่น C

ชอบ:

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

แต่มันก็อันตรายเช่นกันเพราะมันเป็นสิ่งสำคัญที่จะต้องทำความสะอาดส่วนที่มีการพลวัต (ให้จากภายนอก) ของสิ่งที่ถูกส่งไปevalด้วยเหตุผลที่ถูกตีความว่าเป็นรหัสเชลล์

ตัวอย่างข้างต้นถ้า$1เป็นevil-command; var, evalจะสิ้นสุดขึ้นการประเมินรหัสเปลือกแล้วเรียกว่าevil-command; var=$varvalueevil-command

ความชั่วร้ายของevalมักเกินความจริง

ตกลงมันเป็นอันตราย แต่อย่างน้อยเราก็รู้ว่ามันอันตราย

จำนวนมากของคำสั่งอื่น ๆ จะประเมินรหัสเปลือกในการขัดแย้งถ้าไม่ได้ปรุงแต่งเช่น (ขึ้นอยู่กับเปลือก), [อาคาtest, export, printfแอฟริกาsed, awkและแน่นอนsh/ bash/ perlและล่ามทั้งหมด ...

ตัวอย่าง (ที่นี่ใช้unameเป็นevil-commandและไม่$aได้ให้ข้อมูลที่ไม่ได้แก้ไข):

$ 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

ผู้sed, export... คำสั่งอาจจะถือว่าเป็นอันตรายมากขึ้นเพราะในขณะที่มันเป็นที่ชัดเจนeval "$var"จะทำให้เนื้อหาของ$varจะได้รับการประเมินว่าเป็นรหัสเปลือกก็ไม่ชัดเจนดังนั้นมีsed "s/foo/$var/"หรือหรือexport "$var=value" [ "$var" -gt 0 ]ความเป็นอันตรายนั้นเหมือนกัน แต่มันซ่อนอยู่ในคำสั่งอื่น ๆ


@BinaryZebra, sedlike evalถูกส่งผ่านสตริงที่มีอยู่ในตัวแปรและสำหรับทั้งสองเนื้อหาของสตริงนั้นสิ้นสุดลงที่ได้รับการประเมินว่าเป็นรหัสเชลล์ดังนั้นsedสิ่งที่อันตรายevalก็คือสิ่งที่ฉันพูด sedจะถูกส่งผ่านสตริงที่มีuname(uname ยังไม่ได้ดำเนินการจนถึง) และผ่านการร้องขอsedคำสั่ง uname จะถูกดำเนินการ ชอบ eval ในsed 's/foo/$a/g'คุณไม่ได้ส่งข้อมูลที่ไม่ได้รับอนุญาตถึงsedนั่นไม่ใช่สิ่งที่เรากำลังพูดถึงที่นี่
Stéphane Chazelas

2

ตัวอย่างนี้อาจทำให้เกิดแสง:

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

ผลลัพธ์ของสคริปต์ด้านบน:

$VAR2
$VAR1
25

ขอบคุณสำหรับการสนับสนุนของคุณ แต่เป็นความบันเทิงที่พวกเขาเป็นเราจะไม่สนใจจริงๆในการรวบรวมรายชื่อของ (แตกต่างกันเล็กน้อย) evalตัวอย่างของการใช้ คุณเชื่อหรือไม่ว่ามีฟังก์ชั่นพื้นฐานที่สำคัญบางอย่างevalที่ไม่ได้กล่าวถึงในคำตอบที่มีอยู่ ถ้าเป็นเช่นนั้นให้อธิบายและใช้ตัวอย่างเพื่ออธิบายคำอธิบายของคุณ กรุณาอย่าตอบในความคิดเห็น; แก้ไข  คำตอบของคุณเพื่อให้ชัดเจนและสมบูรณ์ยิ่งขึ้น
สกอตต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.