อะไรคือความแตกต่างระหว่าง eval และ exec?


81

evalและexecทั้งสองอยู่ในคำสั่งของ bash (1) ที่รันคำสั่ง

ฉันเห็นด้วยexecมีตัวเลือกน้อย แต่นั่นคือความแตกต่างเท่านั้น? เกิดอะไรขึ้นกับบริบทของพวกเขา



คำตอบ:


124

evalและexecเป็นสัตว์ที่แตกต่างอย่างสิ้นเชิง (นอกเหนือจากข้อเท็จจริงที่ว่าทั้งสองจะรันคำสั่ง แต่ทำทุกอย่างที่คุณทำในเชลล์)

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

อะไรexec cmdจะเหมือนกับที่เพิ่งรันcmdยกเว้นว่าเชลล์ปัจจุบันจะถูกแทนที่ด้วยคำสั่งแทนที่จะเป็นกระบวนการแยกต่างหากที่กำลังรันอยู่ ภายในทำงานพูด/bin/lsจะเรียกfork()การสร้างกระบวนการเด็กแล้วในเด็กที่จะดำเนินการexec() ในทางกลับกันจะไม่แยก แต่เพียงแทนที่เปลือก/bin/lsexec /bin/ls

เปรียบเทียบ:

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo

กับ

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217

echo $$พิมพ์ PID ของเชลล์ที่ฉันเริ่มและการแสดงรายการ/proc/selfให้ PID ของlsเชลล์ที่เรารันจากเชลล์ โดยทั่วไปแล้ว ID กระบวนการจะแตกต่างกัน แต่มีexecเชลล์และlsมี ID กระบวนการเดียวกัน นอกจากนี้คำสั่งต่อไปนี้execไม่ได้ทำงานเนื่องจากเชลล์ถูกแทนที่


ในทางกลับกัน:

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

evalจะเรียกใช้อาร์กิวเมนต์เป็นคำสั่งในเชลล์ปัจจุบัน ในคำอื่น ๆเป็นเช่นเดียวกับเพียงeval foo bar foo barแต่ตัวแปรจะถูกขยายออกก่อนที่จะดำเนินการดังนั้นเราสามารถดำเนินการคำสั่งที่บันทึกไว้ในตัวแปรเชลล์:

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo

มันจะไม่สร้างกระบวนการลูกดังนั้นตัวแปรตั้งอยู่ในเปลือกปัจจุบัน (แน่นอนeval /bin/lsจะสร้างกระบวนการลูกแบบเดียวกับที่จะทำแบบเก่า/bin/ls)

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

eval $(ssh-agent)

และเชลล์ปัจจุบันจะได้รับตัวแปรสำหรับคำสั่งอื่นที่จะสืบทอด


แน่นอนหากตัวแปรcmdเกิดขึ้นเพื่อให้มีสิ่งที่ชอบการrm -rf $HOMEทำงานeval "$cmd"จะไม่เป็นสิ่งที่คุณต้องการทำ แม้สิ่งที่ต้องการแทนคำสั่งภายในสตริงจะถูกประมวลผลดังนั้นหนึ่งควรจริงๆให้แน่ใจว่าการป้อนข้อมูลเพื่อevalความปลอดภัยก่อนที่จะใช้

บ่อยครั้งที่เป็นไปได้ที่จะหลีกเลี่ยงevalและหลีกเลี่ยงการผสมรหัสและข้อมูลโดยไม่ได้ตั้งใจในทางที่ผิด


นั่นเป็นคำตอบที่ยอดเยี่ยม @ilkkachu ขอบคุณ!
Willian Paixao

รายละเอียดเพิ่มเติมเกี่ยวกับการใช้ eval สามารถดูได้ที่นี่: stackoverflow.com/a/46944004/2079103
clearlight

1
@ ชัดเจนว่าดีที่เตือนฉันให้เพิ่มข้อจำกัดความรับผิดชอบตามปกติเกี่ยวกับการไม่ใช้evalในสถานที่แรกในคำตอบนี้ด้วย สิ่งต่าง ๆ เช่นตัวแปรการปรับเปลี่ยนทางอ้อมสามารถทำได้ในหลาย ๆ เชลล์ผ่านdeclare/ typeset/ namerefและการขยายเช่น${!var}ฉันจะใช้มันแทนevalเว้นแต่ฉันจะต้องหลีกเลี่ยงมันจริงๆ
ilkkachu

27

execไม่ได้สร้างกระบวนการใหม่ มันจะแทนที่กระบวนการปัจจุบันด้วยคำสั่งใหม่ หากคุณทำสิ่งนี้ในบรรทัดคำสั่งมันจะจบเซสชันเชลล์ของคุณอย่างมีประสิทธิภาพ (และอาจนำคุณออกจากระบบหรือปิดหน้าต่างเทอร์มินัล!)

เช่น

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 

ที่นี่ฉันอยู่ในksh(เปลือกปกติของฉัน) ฉันจะเริ่มต้นและจากนั้นภายในทุบตีฉันbash exec /bin/echoเราจะเห็นว่าฉันได้รับการลดลงกลับเข้ามาในkshภายหลังเพราะกระบวนการถูกแทนที่โดยbash/bin/echo


อืมบนใบหน้าที่หล่นลงไปในกระบวนการ ksh b / c ถูกแทนที่ด้วย echo ไม่ได้ทำให้รู้สึกว่ามาก?

14

TL; DR

execใช้เพื่อแทนที่กระบวนการเชลล์ปัจจุบันด้วยใหม่และจัดการการเปลี่ยนเส้นทาง / ตัวให้คำอธิบายไฟล์หากไม่มีการระบุคำสั่ง evalใช้เพื่อประเมินสตริงเป็นคำสั่ง ทั้งสองอาจถูกใช้เพื่อสร้างและเรียกใช้คำสั่งด้วยอาร์กิวเมนต์ที่รู้จักในเวลาทำงาน แต่execแทนที่กระบวนการของเชลล์ปัจจุบันนอกเหนือจากการดำเนินการคำสั่ง

ดำเนินการ buil-in

ไวยากรณ์:

exec [-cl] [-a name] [command [arguments]]

ตามคู่มือหากมีคำสั่งที่ระบุไว้ในตัวนี้

... แทนที่เชลล์ ไม่มีการสร้างกระบวนการใหม่ อาร์กิวเมนต์กลายเป็นอาร์กิวเมนต์ของคำสั่ง

กล่าวอีกนัยหนึ่งถ้าคุณกำลังทำงานbashกับ PID 1234 และถ้าคุณต้องการเรียกใช้exec top -u rootภายในเชลล์นั้นtopคำสั่งจะมี PID 1234 และแทนที่กระบวนการเชลล์ของคุณ

มันมีประโยชน์ที่ไหน? ในสิ่งที่เรียกว่าสคริปต์แรปเปอร์ สคริปต์ดังกล่าวสร้างชุดของการขัดแย้งหรือการตัดสินใจบางอย่างเกี่ยวกับตัวแปรที่จะส่งผ่านไปยังสภาพแวดล้อมและจากนั้นใช้execเพื่อแทนที่ตัวเองด้วยคำสั่งใดก็ตามที่ระบุไว้และแน่นอนว่าจะให้ข้อโต้แย้งเหล่านั้น

คู่มือยังระบุด้วยว่า:

หากไม่ได้ระบุคำสั่งการเปลี่ยนเส้นทางใด ๆ จะมีผลในเชลล์ปัจจุบัน

สิ่งนี้ทำให้เราสามารถเปลี่ยนทิศทางอะไรก็ได้จากเชลล์เอาต์พุตปัจจุบันไปยังไฟล์ นี้อาจจะเป็นประโยชน์สำหรับการเข้าสู่ระบบการกรองหรือวัตถุประสงค์ที่คุณไม่ต้องการที่จะเห็นstdoutคำสั่ง stderrแต่เพียง ตัวอย่างเช่น:

bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt 
2017 05 20 星期六 05:01:51 MDT
HELLO WORLD

พฤติกรรมนี้จะทำให้มันมีประโยชน์สำหรับการเข้าสู่ระบบในเชลล์สคริปต์เปลี่ยนเส้นทางลำธารไปยังไฟล์ที่แยกต่างหากหรือกระบวนการและอื่น ๆสิ่งที่สนุกกับอธิบายไฟล์

ในระดับซอร์สโค้ดอย่างน้อยสำหรับbashรุ่น 4.3 ที่ สร้างขึ้นในที่กำหนดไว้ในexec builtins/exec.defมันแยกวิเคราะห์คำสั่งที่ได้รับและถ้ามีคำสั่งจะส่งสิ่งที่ไปยังshell_execve()ฟังก์ชั่นที่กำหนดไว้ในexecute_cmd.cไฟล์

เรื่องสั้นสั้นมีตระกูลของexecคำสั่งในภาษาการเขียนโปรแกรม C และshell_execve()เป็นฟังก์ชั่น wrapper ของexecve:

/* Call execve (), handling interpreting shell scripts, and handling
   exec failures. */
int
shell_execve (command, args, env)
     char *command;
     char **args, **env;
{

มี eval ในตัว

คู่มือการทุบตี 4.3 (เน้นที่เพิ่มโดยฉัน):

args ถูกอ่านและต่อกันเป็นคำสั่งเดียว คำสั่งนี้จะถูกอ่านและ ดำเนินการโดยเชลล์และสถานะการออกของมันจะถูกส่งกลับเป็นค่าของ eval

โปรดทราบว่าไม่มีการแทนที่กระบวนการที่เกิดขึ้น ซึ่งแตกต่างจากexecที่เป้าหมายคือการจำลองการexecve()ทำงานฟังก์ชันบิวด์evalอินทำหน้าที่เพียงเพื่อ "ประเมิน" อาร์กิวเมนต์เช่นเดียวกับที่ผู้ใช้พิมพ์บนบรรทัดคำสั่ง ดังนั้นกระบวนการใหม่ถูกสร้างขึ้น

สิ่งนี้อาจมีประโยชน์ที่ไหน? ดังที่ Gilles ชี้ให้เห็นในคำตอบนี้ "... eval ไม่ได้ใช้บ่อยนักในบางเชลล์การใช้งานทั่วไปส่วนใหญ่คือการรับค่าของตัวแปรที่ไม่มีชื่อจนกระทั่งรันไทม์" โดยส่วนตัวฉันใช้ในสองสามสคริปต์บน Ubuntu ซึ่งจำเป็นต้องเรียกใช้ / ประเมินคำสั่งตามพื้นที่ทำงานเฉพาะที่ผู้ใช้กำลังใช้งานอยู่

ในระดับซอร์สโค้ดมันถูกกำหนดไว้ในbuiltins/eval.defและส่งผ่านสตริงการวิเคราะห์คำไปยังevalstring()ฟังก์ชัน

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

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found

@ ctrl-alt-delor ฉันได้แก้ไขส่วนนั้นแล้วขอบคุณแม้ว่าเนื้อหาจะวางไข่กระบวนการใหม่ PID จะยังคงเหมือนเดิมของเชลล์ปัจจุบัน ในอนาคตให้พิจารณาคำตอบการแก้ไขแทนที่จะปล่อยให้ความคิดเห็นและ downvote โดยเฉพาะอย่างยิ่งที่สิ่งเล็กน้อยเช่นวลี 7 คำอยู่ในคำถาม; ใช้เวลานานกว่าในการแก้ไขและแก้ไขคำตอบและมีประโยชน์มากกว่า
Sergiy Kolodyazhnyy

5
สร้างกระบวนการลูกใหม่เรียกใช้อาร์กิวเมนต์และส่งคืนสถานะออก

เอ๊ะอะไรนะ? ประเด็นทั้งหมดevalคือมันไม่ได้สร้างกระบวนการลูกในทางใดทางหนึ่ง ถ้าฉันทำ

eval "cd /tmp"

ในเชลล์หลังจากนั้นเชลล์ปัจจุบันจะเปลี่ยนไดเรกทอรี ไม่execสร้างกระบวนการลูกใหม่ แต่จะเปลี่ยนการดำเนินการปัจจุบัน (คือเชลล์) สำหรับกระบวนการที่กำหนด ID กระบวนการ (และไฟล์ที่เปิดและสิ่งอื่น ๆ ) ยังคงเหมือนเดิม ตรงข้ามกับevalที่execจะไม่กลับไปที่เปลือกเรียกเว้นแต่execตัวเองล้มเหลวเนื่องจากไม่สามารถค้นหาหรือโหลดปฏิบัติการหรือตายเพื่อโต้แย้งปัญหาการขยายตัว

evalโดยทั่วไปตีความอาร์กิวเมนต์ของมันเป็นสตริงหลังจากการต่อข้อมูลเข้าด้วยกันกล่าวคือมันจะทำการเพิ่มเลเยอร์พิเศษของการขยายตัวของสัญลักษณ์แทนและการแยกอาร์กิวเมนต์ execไม่ทำอะไรแบบนั้น


1
คำถามดั้งเดิมอ่าน "eval และ exec เป็นทั้งคำสั่งในตัวของ Bash (1) และดำเนินการคำสั่งสร้างกระบวนการลูกใหม่เรียกใช้อาร์กิวเมนต์และคืนสถานะทางออก"; ฉันแก้ไขข้อสันนิษฐานที่ผิดพลาดไปแล้ว
Charles Stewart

-1

การประเมินผล

งานเหล่านี้:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

อย่างไรก็ตามสิ่งเหล่านี้ไม่ได้:

$ exec "echo hi"
bash: exec: echo hi: not found

$ "echo hi"
bash: echo hi: command not found

การเปลี่ยนภาพกระบวนการ

ตัวอย่างนี้สาธิตวิธีexecแทนที่อิมเมจของกระบวนการเรียกใช้:

# Get PID of current shell
sh$ echo $$
1234

# Enter a subshell with PID 5678
sh$ sh

# Check PID of subshell
sh-subshell$ echo $$
5678

# Run exec
sh-subshell$ exec echo $$
5678

# We are back in our original shell!
sh$ echo $$
1234

สังเกตว่าexec echo $$วิ่งด้วย PID ของ subshell! ยิ่งกว่านั้นหลังจากเสร็จสมบูรณ์เราก็กลับมาอยู่ในsh$เปลือกเดิมของเรา

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

sh$ echo $$
1234

sh$ sh

sh-subshell$ echo $$
5678

sh-subshell$ eval echo $$
5678

# We are still in the subshell!
sh-subshell$ echo $$
5678

ฉันโพสต์คำตอบนี้เพราะตัวอย่างอื่น ๆ ไม่ได้อธิบายสิ่งนี้ในแบบที่เข้าใจได้อย่างเพียงพอสำหรับจิตใจที่อ่อนแอเช่น I
Mateen Ulhaq

ตัวเลือกที่ไม่เหมาะสมของคำ: การทดแทนกระบวนการเป็นแนวคิดที่มีอยู่แล้วซึ่งดูเหมือนจะไม่เกี่ยวข้องกับสิ่งที่คุณกำลังอธิบายอยู่ที่นี่
muru

@muru ดีกว่า "การแทนที่กระบวนการ" หรือไม่ ฉันไม่แน่ใจว่าคำศัพท์ที่ถูกต้องคืออะไร ดูเหมือนว่า manpage เรียกมันว่า "การแทนที่อิมเมจกระบวนการ"
Mateen Ulhaq

อืมไม่เป็นเลย ( แต่เมื่อผมได้ยินคำว่า "เปลี่ยนภาพกระบวนการ" ผมคิดว่า: exec)
Muru
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.