eval
และexec
ทั้งสองอยู่ในคำสั่งของ bash (1) ที่รันคำสั่ง
ฉันเห็นด้วยexec
มีตัวเลือกน้อย แต่นั่นคือความแตกต่างเท่านั้น? เกิดอะไรขึ้นกับบริบทของพวกเขา
eval
และexec
ทั้งสองอยู่ในคำสั่งของ bash (1) ที่รันคำสั่ง
ฉันเห็นด้วยexec
มีตัวเลือกน้อย แต่นั่นคือความแตกต่างเท่านั้น? เกิดอะไรขึ้นกับบริบทของพวกเขา
คำตอบ:
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/ls
exec /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
และหลีกเลี่ยงการผสมรหัสและข้อมูลโดยไม่ได้ตั้งใจในทางที่ผิด
eval
ในสถานที่แรกในคำตอบนี้ด้วย สิ่งต่าง ๆ เช่นตัวแปรการปรับเปลี่ยนทางอ้อมสามารถทำได้ในหลาย ๆ เชลล์ผ่านdeclare
/ typeset
/ nameref
และการขยายเช่น${!var}
ฉันจะใช้มันแทนeval
เว้นแต่ฉันจะต้องหลีกเลี่ยงมันจริงๆ
exec
ไม่ได้สร้างกระบวนการใหม่ มันจะแทนที่กระบวนการปัจจุบันด้วยคำสั่งใหม่ หากคุณทำสิ่งนี้ในบรรทัดคำสั่งมันจะจบเซสชันเชลล์ของคุณอย่างมีประสิทธิภาพ (และอาจนำคุณออกจากระบบหรือปิดหน้าต่างเทอร์มินัล!)
เช่น
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
ที่นี่ฉันอยู่ในksh
(เปลือกปกติของฉัน) ฉันจะเริ่มต้นและจากนั้นภายในทุบตีฉันbash
exec /bin/echo
เราจะเห็นว่าฉันได้รับการลดลงกลับเข้ามาในksh
ภายหลังเพราะกระบวนการถูกแทนที่โดยbash
/bin/echo
exec
ใช้เพื่อแทนที่กระบวนการเชลล์ปัจจุบันด้วยใหม่และจัดการการเปลี่ยนเส้นทาง / ตัวให้คำอธิบายไฟล์หากไม่มีการระบุคำสั่ง eval
ใช้เพื่อประเมินสตริงเป็นคำสั่ง ทั้งสองอาจถูกใช้เพื่อสร้างและเรียกใช้คำสั่งด้วยอาร์กิวเมนต์ที่รู้จักในเวลาทำงาน แต่exec
แทนที่กระบวนการของเชลล์ปัจจุบันนอกเหนือจากการดำเนินการคำสั่ง
ไวยากรณ์:
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;
{
คู่มือการทุบตี 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
สร้างกระบวนการลูกใหม่เรียกใช้อาร์กิวเมนต์และส่งคืนสถานะออก
เอ๊ะอะไรนะ? ประเด็นทั้งหมดeval
คือมันไม่ได้สร้างกระบวนการลูกในทางใดทางหนึ่ง ถ้าฉันทำ
eval "cd /tmp"
ในเชลล์หลังจากนั้นเชลล์ปัจจุบันจะเปลี่ยนไดเรกทอรี ไม่exec
สร้างกระบวนการลูกใหม่ แต่จะเปลี่ยนการดำเนินการปัจจุบัน (คือเชลล์) สำหรับกระบวนการที่กำหนด ID กระบวนการ (และไฟล์ที่เปิดและสิ่งอื่น ๆ ) ยังคงเหมือนเดิม ตรงข้ามกับeval
ที่exec
จะไม่กลับไปที่เปลือกเรียกเว้นแต่exec
ตัวเองล้มเหลวเนื่องจากไม่สามารถค้นหาหรือโหลดปฏิบัติการหรือตายเพื่อโต้แย้งปัญหาการขยายตัว
eval
โดยทั่วไปตีความอาร์กิวเมนต์ของมันเป็นสตริงหลังจากการต่อข้อมูลเข้าด้วยกันกล่าวคือมันจะทำการเพิ่มเลเยอร์พิเศษของการขยายตัวของสัญลักษณ์แทนและการแยกอาร์กิวเมนต์ exec
ไม่ทำอะไรแบบนั้น
การประเมินผล
งานเหล่านี้:
$ 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
exec
)