คำสั่ง eval ใน Bash และการใช้งานทั่วไป


166

หลังจากอ่านหน้าคนทุบตีและเกี่ยวกับการโพสต์นี้

ฉันยังคงมีปัญหาในการเข้าใจสิ่งที่evalคำสั่งทำและสิ่งที่จะเป็นประโยชน์โดยทั่วไป ตัวอย่างเช่นถ้าเราทำ:

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

เกิดอะไรขึ้นที่นี่และเครื่องหมายดอลลาร์และเครื่องหมายแบ็กสแลชเชื่อมโยงกับปัญหาอย่างไร


1
สำหรับบันทึกความพยายามครั้งที่สองใช้งานได้ $($n)ทำงาน$nใน subshell มันพยายามรันคำสั่ง1ที่ไม่มีอยู่
Martin Wickman

1
@MartinWickman แต่ต้องการคือการทำงานในที่สุดไม่ได้echo $1 1ฉันไม่คิดว่ามันจะสามารถทำได้โดยใช้ subshells
Hari Menon

5
คุณควรจะตระหนักถึงผลกระทบต่อการรักษาความปลอดภัยของการใช้ eval
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

1
@ Raze2dust: ฉันไม่เชื่อว่าเขาแนะนำว่ามันสามารถทำงานกับ subshells แต่แทนที่จะอธิบายว่าทำไมคำสั่งที่ 5 ที่รายการ OP ไม่ทำงาน
jedwards

คำตอบ:


196

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

${$n}เป็นข้อผิดพลาดทางไวยากรณ์ในทุบตี ภายในวงเล็บปีกกาคุณสามารถมีชื่อตัวแปรได้พร้อมกับคำนำหน้าและคำต่อท้ายที่เป็นไปได้บางส่วน แต่คุณไม่สามารถมีไวยากรณ์ของ bash โดยพลการและโดยเฉพาะอย่างยิ่งคุณไม่สามารถใช้การขยายตัวแปรได้ มีวิธีการพูดว่า "ค่าของตัวแปรที่มีชื่ออยู่ในตัวแปรนี้" แม้ว่า:

echo ${!n}
one

$(…)เรียกใช้คำสั่งที่ระบุไว้ในวงเล็บใน subshell (เช่นในกระบวนการแยกต่างหากที่สืบทอดการตั้งค่าทั้งหมดเช่นค่าตัวแปรจากเชลล์ปัจจุบัน) และรวบรวมผลลัพธ์ ดังนั้นecho $($n)รัน$nเป็นคำสั่งเชลล์และแสดงเอาต์พุต เนื่องจาก$nประเมิน1, $($n)ความพยายามที่จะเรียกใช้คำสั่ง1ซึ่งไม่ได้อยู่

eval echo \${$n}evalวิ่งพารามิเตอร์ที่ส่งผ่านไปยัง หลังจากที่ขยายตัวพารามิเตอร์และecho ${1}ดังนั้นรันคำสั่งeval echo \${$n}echo ${1}

หมายเหตุว่าส่วนใหญ่ของเวลาที่คุณต้องใช้คำพูดสองรอบแทนตัวแปรและแทนคำสั่ง (เช่นที่ทุกเวลามี):$ ใส่เครื่องหมายอัญประกาศคู่ล้อมรอบตัวแปรและคำสั่งแทนเว้นแต่คุณจะรู้ว่าคุณจำเป็นต้องละทิ้งมัน หากไม่มีเครื่องหมายอัญประกาศคู่เชลล์จะทำการแยกฟิลด์ (เช่นจะแยกค่าของตัวแปรหรือเอาต์พุตจากคำสั่งเป็นคำแยกต่างหาก) จากนั้นถือว่าแต่ละคำเป็นรูปแบบสัญลักษณ์แทน ตัวอย่างเช่น:"$foo", "$(foo)"

$ 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* *

evalไม่ได้ใช้บ่อยมาก ในเชลล์บางตัวการใช้งานทั่วไปส่วนใหญ่คือการรับค่าของตัวแปรที่ไม่ทราบชื่อจนกระทั่งรันไทม์ ในทุบตีนี้ไม่จำเป็นต้องขอบคุณ${!VAR}ไวยากรณ์ evalยังคงมีประโยชน์เมื่อคุณต้องการสร้างคำสั่งอีกต่อไปที่มีตัวดำเนินการคำที่สงวนไว้ ฯลฯ


ในเรื่องที่เกี่ยวกับความคิดเห็นของฉันข้างต้น Eval ทำอะไรได้บ้าง?
kstratis

@ Konos5 ความคิดเห็นอะไร? evalรับสตริง (ซึ่งอาจเป็นผลมาจากการวิเคราะห์และประเมินผล) และตีความว่าเป็นข้อมูลโค้ด
Gilles 'หยุดความชั่วร้าย'

ภายใต้คำตอบของ Raze2dust ฉันได้แสดงความคิดเห็น ตอนนี้ฉันมีแนวโน้มที่จะเชื่อว่า eval ส่วนใหญ่จะใช้เพื่อวัตถุประสงค์ในการปฏิเสธ ถ้าฉันพิมพ์ Eval echo \ $ {$ n} ฉันจะได้รับ อย่างไรก็ตามถ้าฉันพิมพ์ echo \ $ {$ n} ฉันได้รับ \ $ {1} ฉันเชื่อว่าสิ่งนี้เกิดขึ้นเนื่องจากการแยกวิเคราะห์ "สองรอบ" ของ Eval ตอนนี้ฉันกำลังสงสัยว่าจะเกิดอะไรขึ้นถ้าฉันต้องการการอ้างสิทธิ์สามเท่าโดยใช้การประกาศ i = n พิเศษ ในกรณีนี้ตาม Raze2dust ฉันแค่ต้องเพิ่ม eval พิเศษ แต่ผมเชื่อว่าควรจะมีวิธีที่ดีกว่า ... (มันจะได้รับรกง่าย)
kstratis

@ Konos5 eval evalฉันจะไม่ใช้ ฉันจำไม่ได้ว่าเคยรู้สึกต้องการ หากคุณต้องสองจริงๆอ้อมใช้ตัวแปรชั่วคราวมันจะง่ายต่อการแก้ปัญหา:eval eval tmp="\${$i}"; eval x="\${$tmp}"
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

1
@ Konos5 "แยกวิเคราะห์สองครั้ง" ทำให้เข้าใจผิดเล็กน้อย บางคนอาจถูกชักนำให้เชื่อเช่นนี้เนื่องจากความยากลำบากในการระบุอาร์กิวเมนต์สตริงตัวอักษรใน Bash ที่ได้รับการปกป้องจากการขยายที่หลากหลาย evalใช้โค้ดในสตริงและประเมินตามกฎปกติ ในทางเทคนิคแล้วมันไม่ถูกต้องเพราะมีบางกรณีที่ Bash ปรับเปลี่ยนการแยกวิเคราะห์เพื่อไม่ทำการขยายการโต้แย้งของ eval - แต่นั่นเป็นเรื่องอาหารปิดบังไม่ชัดเจนที่ฉันสงสัยว่าใครรู้
ormaaj

39

เพียงแค่คิดว่า eval เป็น "การประเมินการแสดงออกของคุณอีกครั้งก่อนที่จะดำเนินการ"

eval echo \${$n}กลายเป็นecho $1หลังจากการประเมินรอบแรก การเปลี่ยนแปลงที่สังเกตเห็นได้สามประการ:

  • The \$กลายเป็น$(จำเป็นต้องใช้แบ็กสแลชมิฉะนั้นจะพยายามประเมิน${$n}ซึ่งหมายถึงตัวแปรที่มีชื่อ{$n}ซึ่งไม่ได้รับอนุญาต)
  • $n ถูกประเมินเพื่อ 1
  • ที่evalหายไป

ในรอบที่สองมันเป็นพื้นecho $1ซึ่งสามารถดำเนินการโดยตรง

ดังนั้นeval <some command>ก่อนจะประเมิน<some command>(โดยประเมินที่นี่ฉันหมายถึงตัวแปรทดแทน, แทนที่อักขระที่หลบหนีด้วยคนที่ถูกต้อง ฯลฯ ) แล้วเรียกใช้นิพจน์ผลลัพธ์อีกครั้ง

evalใช้เมื่อคุณต้องการสร้างตัวแปรแบบไดนามิกหรืออ่านเอาต์พุตจากโปรแกรมที่ออกแบบมาเป็นพิเศษเพื่อให้อ่านเช่นนี้ ดูhttp://mywiki.wooledge.org/BashFAQ/048สำหรับตัวอย่าง ลิงค์นี้ยังมีวิธีการทั่วไปที่evalใช้และความเสี่ยงที่เกี่ยวข้อง


3
ขณะที่ทราบสำหรับกระสุนแรกที่${VAR}ไวยากรณ์จะได้รับอนุญาตและแนะนำเมื่อมีความเคลือบแคลงใด ๆ (ไม่$VAR == $Vตามมาด้วยARหรือ$VAR == $VAตามมาด้วยR) เทียบเท่ากับ${VAR} $VARในความเป็นจริงมันเป็นชื่อตัวแปร$nที่ไม่ได้รับอนุญาต
jedwards

2
eval eval echo \\\${\${$i}}จะทำ dereference สามเท่า ฉันไม่แน่ใจว่ามีวิธีที่ง่ายกว่านี้หรือไม่ นอกจากนี้ยัง\${$n}ทำงานได้ดี (ภาพพิมพ์one) ในเครื่องของฉัน ..
Hari น้อน

2
@ Konos5 พิมพ์echo \\\${\${$i}} เทียบเท่ากับ$ {1} eval eval echo \\\ $ {\ $ {$ i}} `เทียบเท่าและพิมพ์ออกมา \${${n}}eval echo \\\${\${$i}}echo \${${n}}`` and prints . eval echo ${1}one
Gilles 'ดังนั้นหยุดความชั่วร้าย'

2
@ Konos5 ลองคิดดูตามบรรทัดเดิม - อันแรก` escapes the second one, and the third `หนีออกมา$หลังจากนั้น ดังนั้น\${${n}}หลังจากรอบการประเมินหนึ่งรอบ
Hari Menon

2
@ Konos5 จากซ้ายไปขวาเป็นวิธีที่เหมาะสมในการคิดราคาสำหรับการแยกวิเคราะห์และเครื่องหมายแบ็กสแลช เริ่มแรก\\ ให้แบ็กสแลชหนึ่งอัน จากนั้น\$ให้ผลหนึ่งดอลลาร์ และอื่น ๆ
Gilles 'SO- หยุดความชั่วร้าย'

26

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

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

หากไม่มี eval คุณจะต้องเปลี่ยนเส้นทาง stdout ไปยังไฟล์ temp, ให้ไฟล์ temp แล้วลบทิ้ง ด้วย eval คุณสามารถ:

eval "$(script-or-program)"

หมายเหตุราคามีความสำคัญ ใช้ตัวอย่าง (วางแผน) นี้:

# 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!

มีตัวอย่างของเครื่องมือทั่วไปที่ทำสิ่งนี้หรือไม่ เครื่องมือมีวิธีการสร้างชุดของคำสั่งเชลล์ที่สามารถส่งผ่านไปยัง eval?
Joakim Erdfelt

@ โจอาคิมฉันไม่รู้เครื่องมือโอเพนซอร์สใด ๆ ที่ทำ แต่มันถูกใช้ในสคริปต์ส่วนตัวของ บริษัท ที่ฉันทำงานอยู่ ฉันเพิ่งเริ่มใช้เทคนิคนี้อีกครั้งด้วยตัวเองกับ xampp ไฟล์ Apache .conf ${varname}ขยายตัวแปรสภาพแวดล้อมที่เป็นลายลักษณ์อักษร ฉันพบว่ามันสะดวกที่จะใช้ไฟล์. conf เหมือนกันบนเซิร์ฟเวอร์ที่แตกต่างกันหลายอย่างโดยมีตัวแปรบางอย่างที่แปรสภาพโดยตัวแปรสภาพแวดล้อม ฉันแก้ไข / opt / lampp / xampp (ซึ่งเริ่ม apache) เพื่อทำ eval ประเภทนี้ด้วยสคริปต์ที่โผล่รอบระบบและแสดงexportคำสั่งbash เพื่อกำหนดตัวแปรสำหรับไฟล์. conf
sootsnoot

@Joakim อีกทางเลือกหนึ่งคือการมีสคริปต์เพื่อสร้างไฟล์. conf แต่ละไฟล์ที่ได้รับผลกระทบจากเทมเพลต สิ่งหนึ่งที่ฉันชอบมากกว่าเกี่ยวกับวิธีการของฉันคือการเริ่มต้น apache โดยไม่ต้องผ่าน / opt / lampp / xampp ไม่ได้ใช้สคริปต์เอาต์พุตเก่า แต่มันล้มเหลวในการเริ่มต้นเนื่องจากตัวแปรสภาพแวดล้อมขยายไปสู่สิ่งใด
sootsnoot

@Anthony Sottile ฉันเห็นคุณแก้ไขคำตอบเพื่อเพิ่มราคาประมาณ $ (สคริปต์หรือโปรแกรม) บอกว่าพวกเขามีความสำคัญเมื่อใช้หลายคำสั่ง คุณช่วยยกตัวอย่างได้ไหม - คำสั่งต่อไปนี้ใช้ได้กับคำสั่งที่คั่นด้วยเครื่องหมายอัฒภาคใน stdout ของ 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) สิ่งนี้จะสร้าง abc เมื่อ stdout การรัน. /foo.sh ให้ผล: echo -na; echo -nb; echo -nc
sootsnoot

1
สำหรับตัวอย่างของเครื่องมือทั่วไปที่ใช้ eval ให้ดูpyenv pyenv ช่วยให้คุณสลับระหว่าง Python หลายเวอร์ชันได้อย่างง่ายดาย คุณใส่eval "$(pyenv init -)"ลงใน.bash_profileไฟล์ config ของเชลล์ (หรือคล้ายกัน) ที่สร้างเชลล์สคริปต์เล็กน้อยจากนั้นประเมินมันในเชลล์ปัจจุบัน
Jerry101

10

คำสั่ง 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 >

4

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


evalคำสั่งbash อนุญาตให้คุณรันบรรทัดของโค้ดที่คำนวณหรือได้มาโดยสคริปต์ bash ของคุณ

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

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

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

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

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

  3. เมื่อทดสอบคุณสามารถแทนที่evalคำสั่งด้วยechoและดูสิ่งที่จะปรากฏ หากเป็นรหัสที่ถูกต้องในบริบทปัจจุบันการเรียกใช้ผ่านevalจะทำงานได้


ตัวอย่างต่อไปนี้อาจช่วยให้ชัดเจนว่า eval ทำงานอย่างไร ...

ตัวอย่างที่ 1:

eval คำสั่งหน้ารหัส 'ปกติ' คือ NOP

$ eval a=b
$ eval echo $a
b

ในตัวอย่างข้างต้นevalคำสั่งแรกไม่มีวัตถุประสงค์และสามารถตัดออกได้ evalไม่มีจุดหมายในบรรทัดแรกเนื่องจากไม่มีการกำหนดโค้ดแบบไดนามิกนั่นคือมันแยกวิเคราะห์แล้วในบรรทัดสุดท้ายของรหัสทุบตีดังนั้นมันจะเหมือนกับคำสั่งปกติของรหัสในสคริปต์ทุบตี ลำดับที่ 2 evalนั้นไม่มีจุดหมายเช่นกันเพราะถึงแม้ว่าจะมีขั้นตอนการแยกวิเคราะห์เป็น$aสตริงตัวอักษรที่เทียบเท่ากัน แต่ก็ไม่มีทางอ้อม (เช่นไม่มีการอ้างอิงผ่านค่าสตริงของคำนาม bash จริงหรือตัวแปรสคริปต์ที่ถือ bash) ดังนั้นมันจะทำงานเหมือนกัน เป็นบรรทัดของรหัสโดยไม่มีevalคำนำหน้า



ตัวอย่างที่ 2:

ดำเนินการกำหนด var โดยใช้ชื่อ var ที่ส่งผ่านเป็นค่าสตริง

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

หากคุณต้องการecho $key=$valผลลัพธ์จะเป็น:

mykey=myval

นั่นคือผลลัพธ์สุดท้ายของการแยกสตริงคือสิ่งที่จะถูกประมวลผลโดย eval ดังนั้นผลลัพธ์ของคำสั่ง echo ในตอนท้าย ...



ตัวอย่างที่ 3:

การเพิ่มทางอ้อมให้กับตัวอย่างที่ 2

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

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

 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

หากคำสั่งการแยกวิเคราะห์ที่สันนิษฐานไม่ได้อธิบายว่า eval ทำอะไรได้พอตัวอย่างที่สามอาจอธิบายการแยกวิเคราะห์อย่างละเอียดเพื่อช่วยอธิบายสิ่งที่เกิดขึ้น



ตัวอย่างที่ 4:

ค้นพบว่า vars ซึ่งมีชื่ออยู่ในสตริงมีค่าสตริงหรือไม่

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

ในการทำซ้ำครั้งแรก:

varname="myvarname_a"

Bash วิเคราะห์ข้อโต้แย้งevalและevalเห็นอย่างนี้เมื่อรันไทม์:

eval varval=\$$myvarname_a

ต่อไปนี้pseudocodeความพยายามที่จะแสดงให้เห็นถึงวิธีการตีความทุบตีบรรทัดข้างต้นของจริงevalรหัสที่จะมาถึงค่าสุดท้ายที่ดำเนินการโดย (บรรทัดอธิบายต่อไปนี้ไม่ใช่รหัสทุบตีที่แน่นอน):

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)

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

varval="User-provided"

รหัสที่เหลืออยู่ในตัวอย่างด้านบนจะทำการทดสอบเพื่อดูว่าค่าที่กำหนดให้กับ $ varval เป็นโมฆะหรือไม่และหากเป็นเช่นนั้นจะแจ้งให้ผู้ใช้ระบุค่า


3

ตอนแรกฉันตั้งใจไม่เคยเรียนรู้วิธีการใช้ eval เพราะคนส่วนใหญ่จะแนะนำให้อยู่ห่างจากมันเหมือนโรคระบาด อย่างไรก็ตามฉันเพิ่งค้นพบกรณีการใช้งานที่ทำให้ฉันหน้าไม่ได้จำมันได้เร็ว

หากคุณมีงาน cron ที่คุณต้องการเรียกใช้แบบโต้ตอบเพื่อทดสอบคุณอาจดูเนื้อหาของไฟล์ด้วย cat และคัดลอกและวางงาน cron เพื่อเรียกใช้ น่าเสียดายที่มันเกี่ยวข้องกับการสัมผัสเมาส์ซึ่งเป็นบาปในหนังสือของฉัน

ให้บอกว่าคุณมีงาน cron ที่ /etc/cron.d/repeatme พร้อมเนื้อหา:

*/10 * * * * root program arg1 arg2

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

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

คำสั่ง cut พิมพ์เฉพาะฟิลด์ที่ 6 ของไฟล์คั่นด้วยช่องว่าง Eval จะดำเนินการคำสั่งนั้น

ฉันใช้งาน cron ที่นี่เป็นตัวอย่าง แต่แนวคิดคือการจัดรูปแบบข้อความจาก stdout แล้วประเมินข้อความนั้น

การใช้ eval ในกรณีนี้ไม่ปลอดภัยเพราะเรารู้แน่ชัดว่าเราจะประเมินอะไรก่อนถึงมือ


2

เมื่อเร็ว ๆ นี้ฉันต้องใช้evalเพื่อบังคับให้มีการประเมินผลการจัดฟันหลายครั้งตามลำดับที่ฉันต้องการ Bash ทำการค้ำยันหลายครั้งจากซ้ายไปขวา

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

ขยายเป็น

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

แต่ฉันต้องการการขยายรั้งที่สองเสร็จก่อนยอมทำ

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

สิ่งที่ดีที่สุดที่ฉันสามารถทำได้คือ

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

งานนี้เพราะราคาเดียวปกป้องชุดแรกของการจัดฟันจากการขยายตัวระหว่างการแยกวิเคราะห์ของevalบรรทัดคำสั่งออกจากพวกเขาจะได้รับการขยายตัว subshell evalอุทธรณ์โดย

อาจมีรูปแบบที่น่าสนใจเกี่ยวกับการขยายรั้งแบบซ้อนที่อนุญาตให้สิ่งนี้เกิดขึ้นในขั้นตอนเดียว แต่ถ้ามีฉันแก่เกินไปและโง่เกินกว่าที่จะดู


1

คุณถามถึงการใช้งานทั่วไป

หนึ่งข้อร้องเรียนทั่วไปเกี่ยวกับเชลล์สคริปต์คือคุณ (ที่ถูกกล่าวหา) ไม่สามารถผ่านการอ้างอิงเพื่อรับค่ากลับมาจากฟังก์ชั่น

แต่ที่จริงแล้วผ่าน "eval" คุณสามารถผ่านการอ้างอิงได้ ผู้ติดตามสามารถส่งรายการการกำหนดตัวแปรที่จะประเมินกลับโดยผู้โทร มันผ่านการอ้างอิงเพราะผู้โทรสามารถได้รับอนุญาตให้ระบุชื่อของตัวแปรผลลัพธ์ - ดูตัวอย่างด้านล่าง ผลลัพธ์ข้อผิดพลาดสามารถส่งคืนชื่อมาตรฐานเช่น errno และ errstr

นี่คือตัวอย่างของการผ่านโดยอ้างอิงในทุบตี:

#!/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)"

ผลลัพธ์มีลักษณะดังนี้:

[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)

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


บอกว่ามี "ความเป็นไปได้มากกว่า" เป็นเรื่องขี้เล่นเล็กน้อยและซ้ำซ้อนที่จะพูดน้อย
dotbit

0

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

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

ผลลัพธ์ที่อยากรู้อยากเห็นในตัวเลือกที่ 2 คือเราจะได้ผ่านพารามิเตอร์ 2 ดังนี้:

  • พารามิเตอร์แรก: "value
  • พารามิเตอร์ที่สอง: content"

นั่นเป็นวิธีที่เคาน์เตอร์ง่าย? เพิ่มเติมevalจะแก้ไขปัญหานั้น

ดัดแปลงจากhttps://stackoverflow.com/a/40646371/744133


0

ในคำถาม:

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

ข้อผิดพลาดเอาต์พุตที่อ้างว่าไม่มีไฟล์ a และ tty ฉันเข้าใจสิ่งนี้เพื่อหมายความว่า tty ไม่ได้ถูกตีความก่อนทำการประมวลผลของ grep แต่ bash นั้นส่งผ่าน tty เป็นพารามิเตอร์ไปยัง grep ซึ่งตีความว่าเป็นชื่อไฟล์

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

ฉันมีเฉพาะกับ grep และระบุไฟล์เป็นพารามิเตอร์แทนที่จะใช้ไพพ์ ฉันยังทำให้คำสั่งพื้นฐานง่ายขึ้นผ่านการส่งออกจากคำสั่งเป็นไฟล์เพื่อให้การไพพ์ของ i / o ไม่ซ้อนกัน:

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

ทำได้ดี.

who | grep $(echo pts/3)

ไม่ได้ต้องการจริงๆ แต่กำจัดท่อที่ซ้อนกันและทำงานได้ดี

โดยสรุปแล้วทุบตีดูเหมือนจะไม่ชอบปิ๊ง สิ่งสำคัญคือต้องเข้าใจว่า bash ไม่ใช่โปรแกรมคลื่นลูกใหม่ที่เขียนในลักษณะวนซ้ำ แต่ทุบตีเป็นโปรแกรมเก่า 1,2,3 ซึ่งได้รับการต่อท้ายด้วยคุณสมบัติ สำหรับวัตถุประสงค์ของความมั่นใจในความเข้ากันได้ย้อนหลังวิธีการตีความเริ่มต้นไม่เคยถูกแก้ไข หาก bash ถูกเขียนใหม่เพื่อจับคู่วงเล็บครั้งแรกจะมีจำนวนข้อบกพร่องเท่าใดในโปรแกรม bash จำนวนเท่าใด? โปรแกรมเมอร์หลายคนชอบที่จะเป็นความลับ

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