วิธีใช้ set และ pipefail ใน Bash Scripts บน Linux

เผยแพร่แล้ว: 2022-06-25
เทอร์มินัล Linux บนหน้าจอแล็ปท็อปบนพื้นหลังสีน้ำเงิน
fatmawati achmad zaenuri/Shutterstock.com

คำสั่ง Linux set และ pipefail กำหนดว่าจะเกิดอะไรขึ้นเมื่อเกิดความล้มเหลวในสคริปต์ Bash มีอะไรให้คิดมากกว่าที่ควรจะหยุดหรือควรดำเนินต่อไป

ที่เกี่ยวข้อง: คู่มือเริ่มต้นสำหรับการเขียนสคริปต์เชลล์: พื้นฐาน

สคริปต์ทุบตีและเงื่อนไขข้อผิดพลาด

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

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

Bash Shell คืออะไรและเหตุใดจึงสำคัญสำหรับ Linux
ที่เกี่ยวข้อง Bash Shell คืออะไร และเหตุใดจึงสำคัญสำหรับ 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 เพื่อดูโค้ดส่งคืนของโปรแกรมทั้งหมดในไพพ์เชน

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
 เสียงสะท้อน $? 

เรียกใช้สคริปต์ที่ไม่จับตัวแปรที่ยังไม่ได้กำหนดค่า

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

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

เราสามารถดักจับข้อผิดพลาดประเภทนี้ได้โดยใช้ตัวเลือก 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 

การรันสองสคริปต์โดยที่ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นได้รับการจัดการภายใน และตัวเลือก -u จะไม่ทริกเกอร์

ปิดผนึกด้วยขวาน

อีกตัวเลือกหนึ่งที่สะดวกในการใช้งานคือตัวเลือก set -x (ดำเนินการและพิมพ์) เมื่อคุณเขียนสคริปต์ สิ่งนี้สามารถช่วยชีวิตได้ มันพิมพ์คำสั่งและพารามิเตอร์ของพวกเขาในขณะที่ดำเนินการ

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

เราจะเพิ่มตัวเลือก set -x ให้กับ “script-8.sh” บันทึกเป็น “script-10.sh” และทำให้ปฏิบัติการได้

 #!/bin/bash
ชุด -euxo pipefail

ถ้า [ -z "${New_Var:-}" ]; แล้ว
  echo "New_Var ไม่ได้กำหนดค่าให้กับมัน"
fi

เรียกใช้เพื่อดูเส้นการติดตาม

 ./script-10.sh 

การรันสคริปต์ด้วยบรรทัดการติดตาม -x ที่เขียนไปยังเทอร์มินัล

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