วิธีใช้ set และ pipefail ใน Bash Scripts บน Linux
เผยแพร่แล้ว: 2022-06-25
คำสั่ง Linux set และ pipefail กำหนดว่าจะเกิดอะไรขึ้นเมื่อเกิดความล้มเหลวในสคริปต์ Bash มีอะไรให้คิดมากกว่าที่ควรจะหยุดหรือควรดำเนินต่อไป
ที่เกี่ยวข้อง: คู่มือเริ่มต้นสำหรับการเขียนสคริปต์เชลล์: พื้นฐาน
สคริปต์ทุบตีและเงื่อนไขข้อผิดพลาด
สคริปต์เชลล์ทุบตีนั้นยอดเยี่ยม เขียนได้เร็วและไม่ต้องคอมไพล์ การดำเนินการซ้ำๆ หรือหลายขั้นตอนที่คุณต้องดำเนินการ สามารถรวมไว้ในสคริปต์ที่สะดวกได้ และเนื่องจากสคริปต์สามารถเรียกใช้ยูทิลิตี Linux มาตรฐานใดๆ ก็ได้ คุณจึงไม่จำกัดเฉพาะความสามารถของภาษาเชลล์เอง
แต่ปัญหาอาจเกิดขึ้นเมื่อคุณเรียกใช้ยูทิลิตี้หรือโปรแกรมภายนอก หากไม่สำเร็จ ยูทิลิตี้ภายนอกจะปิดลงและส่งรหัสส่งคืนไปยังเชลล์ และอาจพิมพ์ข้อความแสดงข้อผิดพลาดไปยังเทอร์มินัล แต่สคริปต์ของคุณจะดำเนินการต่อไป บางทีนั่นอาจไม่ใช่สิ่งที่คุณต้องการ หากเกิดข้อผิดพลาดขึ้นในช่วงต้นของการดำเนินการของสคริปต์ อาจทำให้เกิดปัญหาที่แย่ลงหากสคริปต์ที่เหลือได้รับอนุญาตให้เรียกใช้
คุณสามารถตรวจสอบโค้ดส่งคืนจากแต่ละกระบวนการภายนอกเมื่อเสร็จสิ้น แต่จะกลายเป็นเรื่องยากเมื่อกระบวนการถูกไพพ์ไปยังกระบวนการอื่น รหัสส่งคืนจะมาจากกระบวนการที่ส่วนท้ายของไพพ์ ไม่ใช่รหัสที่อยู่ตรงกลางที่ล้มเหลว แน่นอน ข้อผิดพลาดสามารถเกิดขึ้นได้ภายในสคริปต์ของคุณเช่นกัน เช่น การพยายามเข้าถึงตัวแปรที่ยังไม่ได้กำหนดค่า
คำสั่ง set และ pipefile ให้คุณตัดสินใจว่าจะเกิดอะไรขึ้นเมื่อมีข้อผิดพลาดเช่นนี้เกิดขึ้น พวกเขายังช่วยให้คุณตรวจจับข้อผิดพลาดได้แม้ในขณะที่เกิดขึ้นตรงกลางของห่วงโซ่ท่อ
วิธีใช้งานมีดังนี้
แสดงให้เห็นถึงปัญหา
นี่คือสคริปต์ทุบตีเล็กน้อย มันสะท้อนข้อความสองบรรทัดไปยังเทอร์มินัล คุณสามารถเรียกใช้สคริปต์นี้ได้หากคุณคัดลอกข้อความลงในโปรแกรมแก้ไขและบันทึกเป็น "script-1.sh"
#!/bin/bash echo สิ่งนี้จะเกิดขึ้นก่อน echo สิ่งนี้จะเกิดขึ้นที่สอง
เพื่อให้ปฏิบัติการได้ คุณจะต้องใช้ chmod :
chmod +x script-1.sh
คุณจะต้องเรียกใช้คำสั่งนั้นในแต่ละสคริปต์หากต้องการเรียกใช้บนคอมพิวเตอร์ของคุณ มารันสคริปต์กันเถอะ:
./script-1.sh

ข้อความสองบรรทัดจะถูกส่งไปยังหน้าต่างเทอร์มินัลตามที่คาดไว้
มาปรับเปลี่ยนสคริปต์กันเล็กน้อย เราจะขอให้ ls แสดงรายการรายละเอียดของไฟล์ที่ไม่มีอยู่ สิ่งนี้จะล้มเหลว เราบันทึกสิ่งนี้เป็น “script-2.sh” และทำให้สามารถเรียกใช้งานได้
#!/bin/bash echo สิ่งนี้จะเกิดขึ้นก่อน ls ชื่อไฟล์จินตภาพ echo สิ่งนี้จะเกิดขึ้นที่สอง
เมื่อเราเรียกใช้สคริปต์นี้ เราจะเห็นข้อความแสดงข้อผิดพลาดจาก ls
./script-2.sh

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

ศูนย์ที่ได้รับรายงานคือโค้ดส่งคืนจาก echo ที่สองในสคริปต์ ดังนั้นจึงมีสองประเด็นในสถานการณ์สมมตินี้ อย่างแรกคือสคริปต์มีข้อผิดพลาด แต่ยังคงทำงานต่อไป ที่อาจนำไปสู่ปัญหาอื่น ๆ หากสคริปต์ที่เหลือคาดหวังหรือขึ้นอยู่กับการดำเนินการที่ล้มเหลวจริงสำเร็จ และอย่างที่สองก็คือ ถ้าสคริปต์หรือกระบวนการอื่นจำเป็นต้องตรวจสอบความสำเร็จหรือความล้มเหลวของสคริปต์นี้ สคริปต์นั้นจะได้รับการอ่านผิดพลาด
ชุด -e ตัวเลือก
อ็อพชัน set -e (exit) ทำให้สคริปต์ออกหากกระบวนการใดๆ ที่เรียกใช้สร้างโค้ดส่งคืนที่ไม่ใช่ศูนย์ สิ่งใดที่ไม่ใช่ศูนย์ถือเป็นความล้มเหลว
โดยการเพิ่มตัวเลือก set -e ที่จุดเริ่มต้นของสคริปต์ เราสามารถเปลี่ยนลักษณะการทำงานได้ นี่คือ “script-3.sh”
#!/bin/bash ตั้ง -e echo สิ่งนี้จะเกิดขึ้นก่อน ls ชื่อไฟล์จินตภาพ echo สิ่งนี้จะเกิดขึ้นที่สอง
หากเราเรียกใช้สคริปต์นี้ เราจะเห็นผลของ set -e
./script-3.sh
เสียงสะท้อน $?

สคริปต์หยุดทำงานและโค้ดส่งคืนที่ส่งไปยังเชลล์เป็นค่าที่ไม่ใช่ศูนย์
การจัดการกับความล้มเหลวในท่อ
การวางท่อช่วยเพิ่มความซับซ้อนให้กับปัญหา โค้ดส่งคืนที่มาจากลำดับไพพ์ของคำสั่งคือโค้ดส่งคืนจากคำสั่งสุดท้ายในเชน หากเกิดความล้มเหลวกับคำสั่งที่อยู่ตรงกลางของห่วงโซ่ เราจะกลับไปที่ช่องที่หนึ่ง รหัสส่งคืนนั้นหายไปและสคริปต์จะดำเนินการต่อไป
เราสามารถเห็นผลของคำสั่ง piping ที่มีโค้ดส่งคืนที่แตกต่างกันโดยใช้เชลล์ที่มี true และ false คำสั่งทั้งสองนี้ทำไม่เกินการสร้างโค้ดส่งคืนศูนย์หรือหนึ่งตามลำดับ
จริง
เสียงสะท้อน $?
เท็จ
เสียงสะท้อน $?

หากเราไพพ์ค่า false ให้เป็น true — โดยค่า false แสดงถึงกระบวนการที่ล้มเหลว— เราจะได้โค้ดส่งคืนของ true เป็นศูนย์
เท็จ | จริง
เสียงสะท้อน $?


Bash มีตัวแปรอาร์เรย์ที่เรียกว่า PIPESTATUS และสิ่งนี้จะรวบรวมรหัสส่งคืนทั้งหมดจากแต่ละโปรแกรมในไปป์เชน
เท็จ | จริง | เท็จ | จริง
เสียงสะท้อน "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}" 
PIPESTATUS จะเก็บเฉพาะรหัสส่งคืนจนกว่าโปรแกรมถัดไปจะทำงาน และพยายามกำหนดว่าโค้ดส่งคืนใดที่โปรแกรมใดอาจทำงานยุ่งได้อย่างรวดเร็ว
นี่คือที่มาของ set -o (ตัวเลือก) และ pipefail นี่คือ "script-4.sh" สิ่งนี้จะพยายามไพพ์เนื้อหาของไฟล์ที่ไม่มีอยู่ใน wc
#!/bin/bash ตั้ง -e echo สิ่งนี้จะเกิดขึ้นก่อน cat script-99.sh | wc -l echo สิ่งนี้จะเกิดขึ้นที่สอง
สิ่งนี้ล้มเหลวอย่างที่เราคาดหวัง
./script-4.sh
เสียงสะท้อน $?

ศูนย์แรกคือผลลัพธ์จาก wc โดยบอกเราว่าไม่ได้อ่านบรรทัดใด ๆ สำหรับไฟล์ที่หายไป ศูนย์ที่สองคือรหัสส่งคืนจากคำสั่ง echo ที่สอง
เราจะเพิ่มใน -o pipefail บันทึกเป็น “script-5.sh” และทำให้ปฏิบัติการได้
#!/bin/bash set -eo pipefail echo สิ่งนี้จะเกิดขึ้นก่อน cat script-99.sh | wc -l echo สิ่งนี้จะเกิดขึ้นที่สอง
เรียกใช้และตรวจสอบรหัสส่งคืน
./script-5.sh
เสียงสะท้อน $?

สคริปต์หยุดทำงานและคำสั่ง echo ที่สองไม่ทำงาน รหัสส่งคืนที่ส่งไปยังเชลล์เป็นรหัสเดียว ซึ่งระบุถึงความล้มเหลวได้อย่างถูกต้อง
ที่เกี่ยวข้อง: วิธีใช้คำสั่ง Echo บน Linux
การจับตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น
ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นอาจมองเห็นได้ยากในสคริปต์ในโลกแห่งความเป็นจริง หากเราพยายาม echo ค่าของตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น echo จะพิมพ์บรรทัดว่าง มันไม่ขึ้นข้อความแสดงข้อผิดพลาด สคริปต์ที่เหลือจะดำเนินการต่อไป
นี่คือ script-6.sh
#!/bin/bash set -eo pipefail เสียงสะท้อน "$notset" echo "คำสั่ง echo อื่น"
เราจะเรียกใช้และสังเกตพฤติกรรมของมัน
./script-6.sh
เสียงสะท้อน $?

สคริปต์จะข้ามตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น และดำเนินการต่อไป รหัสส่งคืนเป็นศูนย์ การพยายามค้นหาข้อผิดพลาดเช่นนี้ในสคริปต์ที่ยาวและซับซ้อนอาจเป็นเรื่องยากมาก
เราสามารถดักจับข้อผิดพลาดประเภทนี้ได้โดยใช้ตัวเลือก set -u (unset) เราจะเพิ่มสิ่งนั้นลงในคอลเลกชั่นชุดตัวเลือกที่เพิ่มขึ้นเรื่อยๆ ที่ด้านบนสุดของสคริปต์ บันทึกเป็น “script-7.sh” และทำให้ปฏิบัติการได้
#!/bin/bash ชุด -eou pipefail เสียงสะท้อน "$notset" echo "คำสั่ง echo อื่น"
มารันสคริปต์กันเถอะ:
./script-7.sh
เสียงสะท้อน $?

ตรวจพบตัวแปรที่ยังไม่ได้กำหนดค่า สคริปต์หยุดทำงาน และโค้ดส่งคืนถูกตั้งค่าเป็นตัวแปรเดียว
อ็อพชัน -u (unset) นั้นฉลาดพอที่ จะไม่ถูกทริกเกอร์ โดยสถานการณ์ที่คุณสามารถโต้ตอบกับตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นได้อย่างถูกต้อง
ใน "script-8.sh" สคริปต์จะตรวจสอบว่าตัวแปร New_Var ได้รับการเตรียมใช้งานหรือไม่ คุณไม่ต้องการให้สคริปต์หยุดที่นี่ ในสคริปต์จริง คุณจะต้องดำเนินการเพิ่มเติมและจัดการกับสถานการณ์ด้วยตนเอง
โปรดทราบว่าเราได้เพิ่มตัวเลือก -u เป็นตัวเลือกที่ สอง ในคำสั่ง set -o pipefail ต้องอยู่ท้ายสุด
#!/bin/bash
ชุด -euo pipefail
ถ้า [ -z "${New_Var:-}" ]; แล้ว
echo "New_Var ไม่ได้กำหนดค่าให้กับมัน"
fiใน “script-9.sh” ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นจะได้รับการทดสอบ และหากไม่ได้กำหนดค่าเริ่มต้น จะมีการจัดเตรียมค่าเริ่มต้นไว้แทน
#!/bin/bash
ชุด -euo pipefail
default_value=484
ค่า=${New_Var:-$default_value}
echo "New_Var=$Value"สคริปต์ได้รับอนุญาตให้ทำงานจนเสร็จสิ้น
./script-8.sh
./script-9.sh

ปิดผนึกด้วยขวาน
อีกตัวเลือกหนึ่งที่สะดวกในการใช้งานคือตัวเลือก set -x (ดำเนินการและพิมพ์) เมื่อคุณเขียนสคริปต์ สิ่งนี้สามารถช่วยชีวิตได้ มันพิมพ์คำสั่งและพารามิเตอร์ของพวกเขาในขณะที่ดำเนินการ
มันให้รูปแบบการติดตามการดำเนินการ "คร่าวๆ และพร้อม" อย่างรวดเร็ว การแยกข้อบกพร่องทางตรรกะและการจำจุดบกพร่องจะง่ายขึ้นมาก
เราจะเพิ่มตัวเลือก set -x ให้กับ “script-8.sh” บันทึกเป็น “script-10.sh” และทำให้ปฏิบัติการได้
#!/bin/bash
ชุด -euxo pipefail
ถ้า [ -z "${New_Var:-}" ]; แล้ว
echo "New_Var ไม่ได้กำหนดค่าให้กับมัน"
fiเรียกใช้เพื่อดูเส้นการติดตาม
./script-10.sh

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


