ตัวแปรเป็นคำสั่ง; eval vs bash -c


41

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

bash -c "$1"

แทน

eval "$1"

ฉันถือว่าการใช้ eval เป็นวิธีที่ต้องการและอาจเร็วกว่าอยู่ดี มันเป็นเรื่องจริงเหรอ?
มีความแตกต่างในทางปฏิบัติระหว่างทั้งสองหรือไม่? อะไรคือความแตกต่างที่โดดเด่นระหว่างสองสิ่งนี้?


ในบางโอกาสคุณสามารถหนีไปได้โดยไม่ทำเช่นนั้น e='echo foo'; $eทำงานได้ดี
เดนนิส

คำตอบ:


40

eval "$1"รันคำสั่งในสคริปต์ปัจจุบัน มันสามารถตั้งค่าและใช้ตัวแปรเชลล์จากสคริปต์ปัจจุบันตั้งค่าตัวแปรสภาพแวดล้อมสำหรับสคริปต์ปัจจุบันตั้งค่าและใช้ฟังก์ชั่นจากสคริปต์ปัจจุบันตั้งไดเรกทอรีปัจจุบัน umask ข้อ จำกัด และคุณลักษณะอื่น ๆ สำหรับสคริปต์ปัจจุบันและอื่น ๆ bash -c "$1"เรียกใช้งานคำสั่งในสคริปต์ที่แยกต่างหากอย่างสมบูรณ์ซึ่งสืบทอดตัวแปรสภาพแวดล้อมตัวอธิบายไฟล์และสภาพแวดล้อมกระบวนการอื่น ๆ (แต่ไม่ส่งการเปลี่ยนแปลงใด ๆ กลับมา) แต่ไม่สืบทอดการตั้งค่าเชลล์ภายใน (ตัวแปรเชลล์ฟังก์ชั่นตัวเลือกกับดัก ฯลฯ )

มีอีกวิธีหนึ่ง(eval "$1")ซึ่งดำเนินการคำสั่งใน subshell: มันสืบทอดทุกอย่างจากสคริปต์การโทร แต่ไม่ส่งการเปลี่ยนแปลงใด ๆ กลับมา

ตัวอย่างเช่นสมมติว่าตัวแปรdirไม่ถูกส่งออกและ$1เป็นcd "$foo"; lsดังนี้:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdรายการเนื้อหาของและภาพพิมพ์/somewhere/else/somewhere/else
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdรายการเนื้อหาของและภาพพิมพ์/somewhere/else/starting/directory
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdแสดงรายการเนื้อหาของ/starting/directory(เพราะcd ""ไม่ได้เปลี่ยนไดเรกทอรีปัจจุบัน) /starting/directoryและภาพพิมพ์

ขอบคุณ ฉันไม่รู้เกี่ยวกับ (eval "$ 1") มันแตกต่างจากแหล่งที่มาหรือไม่?
whoami

1
@whoami มีอะไรจะทำอย่างไรกับ(eval "$1") sourceมันเป็นเพียงแค่การรวมกันของและ(…) คือประมาณเทียบเท่ากับ evalsource fooeval "$(cat foo)"
Gilles 'หยุดชั่วร้าย'

เราต้องเขียนคำตอบของเราในเวลาเดียวกัน ...
mikeserv

@whoami ความแตกต่างหลักระหว่างevalและ.dotที่evalทำงานกับข้อโต้แย้งและ.dotทำงานกับไฟล์
mikeserv

ขอบคุณทั้งคู่ ความคิดเห็นก่อนหน้าของฉันดูเหมือนจะโง่แล้วตอนนี้ที่ฉันอ่านอีกครั้ง ...
whoami

23

ความแตกต่างที่สำคัญที่สุดระหว่าง

bash -c "$1" 

และ

eval "$1"

คือว่าอดีตทำงานใน subshell และหลังไม่ได้ ดังนั้น:

set -- 'var=something' 
bash -c "$1"
echo "$var"

เอาท์พุท:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

เอาท์พุท:

something

ฉันไม่มีความคิดว่าทำไมทุกคนจะใช้ความสามารถbashในการปฏิบัติการในลักษณะนั้น หากคุณต้องเรียกใช้ให้ใช้ POSIX รับประกันในshตัว หรือ(subshell eval)ถ้าคุณต้องการปกป้องสภาพแวดล้อมของคุณ

โดยส่วนตัวแล้วฉันชอบเชลล์.dotมากกว่าสิ่งอื่นใด

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

เอาท์พุท

something1
something2
something3
something4
something5

แต่คุณต้องการมันทั้งหมดหรือไม่

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

ตัวอย่างเช่น

var='echo this is var' ; $var

เอาท์พุท:

this is var

ใช้งานได้ แต่เพียงเพราะechoไม่สนใจเกี่ยวกับการนับอาร์กิวเมนต์

var='echo "this is var"' ; $var

เอาท์พุท:

"this is var"

ดู? สองคำพูดมาพร้อมเพราะผลของการขยายตัวของเปลือกของไม่ได้ประเมิน$varquote-removal

var='printf %s\\n "this is var"' ; $var

เอาท์พุท:

"this
is
var"

แต่ด้วยevalหรือsh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

เอาท์พุท:

this is var
this is var

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

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

เอาท์พุท

this is var

5

ฉันทำการทดสอบอย่างรวดเร็ว:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(ใช่ฉันรู้ว่าฉันใช้ bash -c เพื่อดำเนินการลูป แต่ไม่ควรสร้างความแตกต่าง)

ผลลัพธ์ที่ได้:

eval    : 1.17s
bash -c : 7.15s

ดังนั้นevalจะเร็ว จากหน้าคนของeval:

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

bash -cแน่นอนรันคำสั่งในเปลือกทุบตี One note: ฉันใช้/bin/echoเพราะechoเป็นเชลล์ในตัวbashซึ่งหมายความว่ากระบวนการใหม่ไม่จำเป็นต้องเริ่มต้น แทนที่/bin/echoด้วยechoสำหรับการทดสอบก็เอาbash -c 1.28sนั่นเป็นเรื่องเดียวกัน Hovever evalเร็วขึ้นสำหรับการเรียกใช้โปรแกรมเรียกทำงาน ความแตกต่างที่สำคัญที่นี่คือที่evalไม่ได้เริ่มเปลือกใหม่ (มันดำเนินการคำสั่งในปัจจุบัน) ในขณะที่bash -cเริ่มเปลือกใหม่แล้วดำเนินการคำสั่งในเปลือกใหม่ เริ่มต้นเปลือกใหม่ต้องใช้เวลาและนั่นคือเหตุผลที่ช้ากว่าbash -ceval


ผมคิดว่า OP ต้องการที่จะเปรียบเทียบbash -cกับการไม่ได้eval exec
โจเซฟอาร์

@JosephR อ๊ะ! ฉันจะเปลี่ยนสิ่งนั้น
PlasmaPower

1
@JosephR มันควรได้รับการแก้ไขแล้ว นอกจากนี้ผม redid ทดสอบอีกเล็กน้อยและbash -cไม่ได้ว่าไม่ดี ...
PlasmaPower

3
แม้ว่าสิ่งนี้จะเป็นจริง แต่ก็ยังขาดความแตกต่างพื้นฐานที่คำสั่งจะถูกดำเนินการในสภาพแวดล้อมที่แตกต่างกัน เห็นได้ชัดว่าการเริ่มต้นอินสแตนซ์ใหม่ของ bash จะช้าลงนี่ไม่ใช่การสังเกตที่น่าสนใจ
Gilles 'หยุดความชั่วร้าย'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.