วิธีใช้ 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
การระบุจุดบกพร่องในสคริปต์ตัวอย่างเล็กๆ น้อยๆ เหล่านี้เป็นเรื่องง่าย เมื่อคุณเริ่มเขียนสคริปต์ที่เกี่ยวข้องมากขึ้น ตัวเลือกเหล่านี้จะพิสูจน์คุณค่าของมัน