วิธีใช้ eval ใน Linux Bash Scripts

เผยแพร่แล้ว: 2022-08-22
แล็ปท็อป Linux แสดง bash prompt
fatmawati achmad zaenuri/Shutterstock.com

จากคำสั่ง Bash ทั้งหมด eval ที่เก่าไม่ดีอาจมีชื่อเสียงที่แย่ที่สุด มีเหตุผลหรือเพียงแค่กดไม่ดี? เราพูดถึงการใช้งานและอันตรายของคำสั่ง Linux ที่ไม่ค่อยมีใครรู้จัก

เราต้องการพูดคุยเกี่ยวกับ eval

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

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

วิธีทำงานกับตัวแปรใน Bash
ที่เกี่ยวข้อง วิธีการทำงานกับตัวแปรใน Bash

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

ปัญหาเกิดขึ้นเมื่อสคริปต์ถูกเขียนขึ้นเพื่อใช้ eval บนสตริงที่ได้รับจากที่อื่น นอก สคริปต์ ผู้ใช้อาจพิมพ์ข้อความนี้ ส่งผ่าน API ติดแท็กในคำขอ HTTPS หรือที่อื่นๆ ภายนอกสคริปต์

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

ก้าวแรกกับ eval

คำสั่ง eval เป็นคำสั่งเชลล์ Bash ในตัว หากมี Bash แสดงว่ามี eval

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

มาสร้างตัวแปรที่เรียกว่า wordcount กัน

 wordcount="wc -w raw-notes.md"

ตัวแปรสตริงประกอบด้วยคำสั่งนับจำนวนคำในไฟล์ชื่อ “raw-notes.md”

เราสามารถใช้ eval เพื่อรันคำสั่งนั้นได้โดยส่ง ค่า ของตัวแปรไปให้

 eval " $wordcount "

การใช้ eval กับตัวแปรสตริงเพื่อนับคำในไฟล์

คำสั่งดำเนินการในเชลล์ปัจจุบัน ไม่ใช่ในเชลล์ย่อย เราสามารถแสดงสิ่งนี้ได้อย่างง่ายดาย เรามีไฟล์ข้อความสั้นชื่อ “variables.txt” ประกอบด้วยสองบรรทัดนี้

 ครั้งแรก=วิธีการ
วินาที = Geek

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

 cat variables.txt
eval "$(ตัวแปร cat.txt)"
echo $first $วินาที 

การเข้าถึงตัวแปรที่กำหนดโดย eval ในเชลล์ปัจจุบัน

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

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

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

ที่เกี่ยวข้อง: วิธีใช้คำสั่ง Linux cat และ tac

การใช้ตัวแปรใน Command String

เราสามารถรวมตัวแปรอื่นๆ ในสตริงคำสั่งได้ เราจะตั้งค่าสองตัวแปรเพื่อเก็บจำนวนเต็ม

 num1=10 
num2=7

เราจะสร้างตัวแปรเพื่อเก็บคำสั่ง expr ซึ่งจะคืนค่าผลรวมของตัวเลขสองตัว ซึ่งหมายความว่าเราจำเป็นต้องเข้าถึงค่าของตัวแปรจำนวนเต็มสองตัวในคำสั่ง สังเกต backticks รอบคำสั่ง expr

 add="`expr $num1 + $num2`"

เราจะสร้างคำสั่งอื่นเพื่อแสดงผลลัพธ์ของคำสั่ง expr

 แสดง = "สะท้อน"

โปรดทราบว่าเราไม่จำเป็นต้องเว้นวรรคที่ส่วนท้ายของสตริง echo หรือที่จุดเริ่มต้นของสตริง expr eval ดูแลสิ่งนั้น

และเพื่อดำเนินการคำสั่งทั้งหมดที่เราใช้:

 ประเมิน $แสดง $add 

การใช้ตัวแปรในสตริงคำสั่ง

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

ที่เกี่ยวข้อง: วิธีทำงานกับตัวแปรใน Bash

การเข้าถึงตัวแปรภายในตัวแปร

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

คัดลอกสคริปต์นี้ไปยังโปรแกรมแก้ไข และบันทึกเป็นไฟล์ชื่อ “assign.sh”

 #!/bin/bash

title="How-To Geek"
หน้าเว็บ=ชื่อเรื่อง
command="echo"
eval $command \${$หน้าเว็บ}

เราต้องทำให้มันสามารถเรียกใช้งานได้ด้วยคำสั่ง chmod

 chmod +x assign.sh 

การใช้ chmod เพื่อทำให้สคริปต์ปฏิบัติการได้

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

เมื่อเราเรียกใช้สคริปต์ เราจะเห็นข้อความจาก title ตัวแปรแม้ว่าคำสั่ง eval จะใช้ webpage ตัวแปร

 ./assign.sh 

การเข้าถึงค่าของตัวแปรจากชื่อที่เก็บไว้ในตัวแปรอื่น

เครื่องหมายดอลลาร์ที่เป็นอักขระหลีก “ $ ” และเครื่องหมายวงเล็บปีกกา “ {} ” ทำให้ eval ดูค่าที่เก็บไว้ภายในตัวแปรที่มีชื่อเก็บอยู่ในตัวแปรของ webpage

การใช้ตัวแปรที่สร้างขึ้นแบบไดนามิก

เราสามารถใช้ eval เพื่อสร้างตัวแปรแบบไดนามิก สคริปต์นี้เรียกว่า "loop.sh"

 #!/bin/bash

รวม=0
label="การวนซ้ำเสร็จสมบูรณ์ ทั้งหมด:"

สำหรับ n ใน {1..10}
ทำ
  ค่า x$n=$n
  echo "วนซ้ำ" $x$n
  ((รวม+=$x$n))
เสร็จแล้ว

เสียงสะท้อน $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10

echo $label $total

มันสร้างตัวแปรที่เรียกว่า total ซึ่งเก็บผลรวมของค่าของตัวแปรที่เราสร้าง จากนั้นจะสร้างตัวแปรสตริงที่เรียกว่า label นี่เป็นสตริงข้อความง่ายๆ

เราจะวนซ้ำ 10 ครั้งและสร้าง 10 ตัวแปรที่เรียกว่า x1 up to x10 คำสั่ง eval ในเนื้อความของลูปให้ "x" และใช้ค่าของตัวนับลูป $n เพื่อสร้างชื่อตัวแปร ในเวลาเดียวกัน มันตั้งค่าตัวแปรใหม่เป็นค่าของตัวนับลูป $n

9 ตัวอย่างลูปใน Linux Bash Scripts
ที่เกี่ยวข้อง 9 ตัวอย่าง for Loops ใน Linux Bash Scripts

มันพิมพ์ตัวแปรใหม่ไปที่หน้าต่างเทอร์มินัลแล้วเพิ่มตัวแปร total ด้วยค่าของตัวแปรใหม่

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

สุดท้าย เราพิมพ์ค่าของตัวแปร total

 ./loop.sh 

การใช้ eval เพื่อสร้างตัวแปรแบบไดนามิก

ที่เกี่ยวข้อง: ไพรเมอร์: Bash Loops: สำหรับในขณะที่และจนถึง

การใช้ eval กับ Arrays

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

คุณไม่ต้องการให้มันเป็นเพียงแค่ rm *.log คุณเพียงแค่ต้องการให้มันลบไฟล์บันทึกที่สร้างขึ้นเท่านั้น สคริปต์นี้จำลองการทำงานนั้น นี่คือ "clear-logs.sh"

 #!/bin/bash

ประกาศ -a logfiles

จำนวนไฟล์=0 
rm_string="ก้อง"

ฟังก์ชัน create_logfile() {
  ((++นับไฟล์))
  filename=$(วันที่ +"%Y-%m-%d_%H-%M-%S") .log
  logfiles[$filecount]=$filename
  echo $filecount "สร้างแล้ว" ${logfiles[$filecount]}
}

# เนื้อหาของสคริปต์ การประมวลผลบางอย่างทำที่นี่ว่า
# สร้างไฟล์บันทึกเป็นระยะ เราจะจำลองว่า
create_logfile
นอน3
create_logfile
นอน3
create_logfile
นอน3
create_logfile

#มีไฟล์ใดบ้างที่จะลบ?
สำหรับ ((file=1; file<=$filecount; file++))
ทำ
  # ลบไฟล์บันทึก
  eval $rm_string ${logfiles[$file]} "ถูกลบ..."
  ไฟล์บันทึก[$file]=""
เสร็จแล้ว

สคริปต์ประกาศอาร์เรย์ที่เรียกว่า logfiles ซึ่งจะเก็บชื่อของไฟล์บันทึกที่สร้างโดยสคริปต์ มันประกาศตัวแปรที่เรียกว่า filecount ซึ่งจะเก็บจำนวนไฟล์บันทึกที่สร้างขึ้น

นอกจากนี้ยังประกาศสตริงที่เรียกว่า rm_string ในสคริปต์ในโลกแห่งความเป็นจริง คำสั่งนี้จะมีคำสั่ง rm แต่เรากำลังใช้ echo เพื่อให้เราสามารถแสดงหลักการในลักษณะที่ไม่ทำลายล้าง

ฟังก์ชัน create_logfile() คือตำแหน่งที่แต่ละล็อกไฟล์ถูกตั้งชื่อ และตำแหน่งที่จะเปิด เรากำลังสร้างเฉพาะ ชื่อไฟล์ และแสร้งทำเป็นว่ามันถูกสร้างขึ้นในระบบไฟล์

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

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

วิธีหยุดสคริปต์ทุบตีชั่วคราวด้วยคำสั่ง Linux Sleep
ที่เกี่ยวข้อง วิธีหยุดสคริปต์ทุบตีชั่วคราวด้วยคำสั่ง Linux Sleep

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

ในที่สุดก็มีลูปที่ลบไฟล์บันทึก ไฟล์ตัวนับลูปถูกตั้งค่าเป็นหนึ่ง นับได้ถึงและรวมถึงค่าของ filecount ซึ่งเก็บจำนวนไฟล์ที่สร้างขึ้น

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

ภายในลูป เราใช้ eval กับ rm_string แบบไม่ทำลายและชื่อไฟล์ที่ดึงมาจากอาร์เรย์ จากนั้นเราตั้งค่าองค์ประกอบอาร์เรย์เป็นสตริงว่าง

นี่คือสิ่งที่เราเห็นเมื่อเราเรียกใช้สคริปต์

 ./clear-logs.sh 

การลบไฟล์ที่มีชื่อเก็บไว้ในอาร์เรย์

มันไม่ได้แย่ไปซะหมด

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

หากคุณแน่ใจว่าสตริงที่ใช้งานได้นั้นถูกสร้างขึ้นภายในและไม่ถูกดักจับจากมนุษย์, API หรือสิ่งต่างๆ เช่น คำขอ HTTPS คุณจะหลีกเลี่ยงข้อผิดพลาดที่สำคัญได้

ที่เกี่ยวข้อง: วิธีแสดงวันที่และเวลาใน Linux Terminal (และใช้ใน Bash Scripts)