ฉันต้องการเพิ่มข้อผิดพลาดในสคริปต์ Bash พร้อมข้อความ "Test cases Failed !!!" วิธีการทำใน Bash?
ตัวอย่างเช่น:
if [ condition ]; then
raise error "Test cases failed !!!"
fi
ฉันต้องการเพิ่มข้อผิดพลาดในสคริปต์ Bash พร้อมข้อความ "Test cases Failed !!!" วิธีการทำใน Bash?
ตัวอย่างเช่น:
if [ condition ]; then
raise error "Test cases failed !!!"
fi
echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress
?
คำตอบ:
ขึ้นอยู่กับตำแหน่งที่คุณต้องการจัดเก็บข้อความแสดงข้อผิดพลาด
คุณสามารถทำสิ่งต่อไปนี้:
echo "Error!" > logfile.log
exit 125
หรือดังต่อไปนี้:
echo "Error!" 1>&2
exit 64
เมื่อคุณเพิ่มข้อยกเว้นคุณจะหยุดการทำงานของโปรแกรม
คุณยังสามารถใช้บางอย่างเช่นรหัสข้อผิดพลาดที่คุณอาจต้องการกลับไปยังระบบปฏิบัติการexit xxx
อยู่ที่ไหนxxx
(จาก 0 ถึง 255) ที่นี่125
และ64
มีรหัสสุ่มเพียงคุณสามารถออกจากกับ เมื่อคุณจำเป็นต้องแสดงให้ระบบปฏิบัติการที่โปรแกรมหยุดผิดปกติ (เช่น. มีข้อผิดพลาดเกิดขึ้น) คุณจะต้องผ่านรหัสทางออกที่ไม่ใช่ศูนย์exit
ที่จะ
ในฐานะที่เป็น @chepner ชี้ให้เห็นคุณสามารถทำได้exit 1
ซึ่งจะหมายถึงข้อผิดพลาดที่ไม่ได้ระบุ
1>&2
จะทำเคล็ดลับ
exit
โดยตัวมันเองใช้สถานะออกของคำสั่งที่เสร็จสมบูรณ์ล่าสุดซึ่งอาจเป็น 0
exit 1
ซึ่งตามแบบแผนหมายถึงข้อผิดพลาดที่ไม่ได้ระบุ
หากผู้ทดสอบกรณีทดสอบของคุณส่งคืนรหัสที่ไม่ใช่ศูนย์สำหรับการทดสอบที่ล้มเหลวคุณสามารถเขียน:
test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
printf '%s\n' "Test case x failed" >&2 # write error message to stderr
exit 1 # or exit $test_result
fi
หรือสั้นกว่านั้น:
if ! test_handler test_case_x; then
printf '%s\n' "Test case x failed" >&2
exit 1
fi
หรือสั้นที่สุด:
test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }
ในการออกด้วยรหัสทางออกของ test_handler:
test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }
หากคุณต้องการใช้แนวทางที่ครอบคลุมมากขึ้นคุณสามารถมีตัวจัดการข้อผิดพลาด:
exit_if_error() {
local exit_code=$1
shift
[[ $exit_code ]] && # do nothing if no error code passed
((exit_code != 0)) && { # do nothing if error code is 0
printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
exit "$exit_code" # we could also check to make sure
# error code is numeric when passed
}
}
จากนั้นเรียกใช้หลังจากเรียกใช้กรณีทดสอบของคุณ:
run_test_case test_case_x
exit_if_error $? "Test case x failed"
หรือ
run_test_case test_case_x || exit_if_error $? "Test case x failed"
ข้อดีของการมีตัวจัดการข้อผิดพลาดexit_if_error
คือ:
if
บล็อกที่ทดสอบรหัสทางออกเพื่อหาข้อผิดพลาดนี่คือการใช้งานการจัดการและการบันทึกข้อผิดพลาดโดยสมบูรณ์:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
__FILE__
, __LINE__
ในทุบตีมีอีกสองสามวิธีที่คุณสามารถแก้ไขปัญหานี้ได้ สมมติว่าความต้องการอย่างหนึ่งของคุณคือการรันเชลล์สคริปต์ / ฟังก์ชันที่มีคำสั่งเชลล์สองสามคำสั่งและตรวจสอบว่าสคริปต์ทำงานสำเร็จหรือไม่และส่งข้อผิดพลาดในกรณีที่เกิดความล้มเหลว
คำสั่งเชลล์โดยทั่วไปจะอาศัยรหัสออกที่ส่งคืนเพื่อแจ้งให้เชลล์ทราบว่าสำเร็จหรือล้มเหลวเนื่องจากเหตุการณ์ที่ไม่คาดคิด
ดังนั้นสิ่งที่คุณต้องการจะทำขึ้นอยู่กับสองประเภทนี้
มีตัวเลือกเชลล์ให้ใช้ขึ้นอยู่กับว่าคุณต้องการทำอะไร สำหรับกรณีแรกที่เปลือกมีตัวเลือกที่มีset -e
และเป็นครั้งที่สองที่คุณสามารถทำtrap
บนEXIT
exit
ในสคริปต์ / ฟังก์ชันของฉันหรือไม่?การใช้exit
โดยทั่วไปจะช่วยเพิ่มความสามารถในการอ่านในกิจวัตรบางอย่างเมื่อคุณทราบคำตอบแล้วคุณต้องการออกจากขั้นตอนการโทรทันที หากรูทีนถูกกำหนดในลักษณะที่ไม่จำเป็นต้องล้างข้อมูลเพิ่มเติมเมื่อตรวจพบข้อผิดพลาดการไม่ออกทันทีหมายความว่าคุณต้องเขียนโค้ดเพิ่มเติม
ดังนั้นในกรณีถ้าคุณจำเป็นต้องกระทำการทำความสะอาดขึ้นในสคริปต์เพื่อให้ยุติการทำความสะอาดสคริปต์ก็เป็นที่ต้องการไม่ได้exit
กับการใช้งาน
set -e
สำหรับข้อผิดพลาดในการออกหรือไม่?ไม่!
set -e
เป็นความพยายามที่จะเพิ่ม "การตรวจหาข้อผิดพลาดอัตโนมัติ" ให้กับเชลล์ เป้าหมายของมันคือการทำให้เชลล์ยกเลิกทุกครั้งที่เกิดข้อผิดพลาด แต่มันมาพร้อมกับข้อผิดพลาดที่อาจเกิดขึ้นมากมายเช่น
คำสั่งที่เป็นส่วนหนึ่งของการทดสอบ if มีภูมิคุ้มกัน ในตัวอย่างหากคุณคาดว่าจะทำลายการtest
ตรวจสอบในไดเร็กทอรีที่ไม่มีอยู่มันจะไม่ผ่านไปยังเงื่อนไขอื่น
set -e
f() { test -d nosuchdir && echo no dir; }
f
echo survived
คำสั่งในไปป์ไลน์นอกเหนือจากคำสั่งสุดท้ายมีภูมิคุ้มกัน ในตัวอย่างด้านล่างเนื่องจากโค้ดออกของคำสั่งที่เรียกใช้งานล่าสุด (ขวาสุด) ถือว่าเป็น ( cat
) และทำได้สำเร็จ สิ่งนี้สามารถหลีกเลี่ยงได้โดยการตั้งค่าโดยset -o pipefail
ตัวเลือก แต่ยังคงเป็นข้อแม้
set -e
somecommand that fails | cat -
echo survived
trap
เมื่อออกคำตัดสินคือถ้าคุณต้องการที่จะสามารถที่จะจัดการกับข้อผิดพลาดแทนที่จะสุ่มสี่สุ่มห้าออกแทนการใช้set -e
ใช้trap
ในERR
สัญญาณหลอก
ERR
ดักไม่ได้ที่จะเรียกใช้รหัสเมื่อเปลือกของตัวเองออกมาด้วยไม่ใช่ศูนย์รหัสข้อผิดพลาด แต่เมื่อใดวิ่งคำสั่งจากเชลล์ที่ที่ไม่ได้เป็นส่วนหนึ่งของสภาพ (เช่นหากcmd
หรือcmd ||
) ออกกับสถานะทางออกที่ไม่ใช่ศูนย์ .
แนวปฏิบัติทั่วไปคือเรากำหนดตัวจัดการกับดักเพื่อให้ข้อมูลการดีบักเพิ่มเติมว่าบรรทัดใดและสาเหตุของการออก จำรหัสออกของคำสั่งสุดท้ายที่ทำให้เกิดERR
สัญญาณจะยังคงมีอยู่ ณ จุดนี้
cleanup() {
exitcode=$?
printf 'error condition hit\n' 1>&2
printf 'exit code returned: %s\n' "$exitcode"
printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
printf 'command present on line: %d' "${BASH_LINENO[0]}"
# Some more clean up code can be added here before exiting
exit $exitcode
}
และเราเพียงแค่ใช้ตัวจัดการนี้ด้านล่างด้านบนของสคริปต์ที่ล้มเหลว
trap cleanup ERR
รวบรวมสิ่งนี้เข้าด้วยกันในสคริปต์ง่ายๆที่มีอยู่false
ในบรรทัดที่ 15 ข้อมูลที่คุณจะได้รับ
error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15
trap
นอกจากนี้ยังมีตัวเลือกโดยไม่คำนึงถึงข้อผิดพลาดในการทำงานเพียงการล้างข้อมูลบนเปลือกเสร็จสิ้น (เช่นออกจากเชลล์สคริปต์ของคุณ) EXIT
กับสัญญาณ คุณยังสามารถดักจับสัญญาณหลายสัญญาณพร้อมกันได้ รายการสัญญาณที่รองรับเพื่อดักจับสามารถพบได้ในหน้าtrap.1p - คู่มือ Linux
สิ่งที่ควรสังเกตอีกประการหนึ่งคือต้องเข้าใจว่าวิธีการใดที่ให้มาไม่ได้ผลหากคุณจัดการกับเชลล์ย่อยที่เกี่ยวข้องในกรณีนี้คุณอาจต้องเพิ่มการจัดการข้อผิดพลาดของคุณเอง
บนเชลล์ย่อยที่ใช้set -e
งานไม่ได้ false
ถูก จำกัด ให้ย่อยเปลือกและไม่เคยได้รับการแพร่กระจายไปยังเปลือกแม่ หากต้องการจัดการข้อผิดพลาดที่นี่ให้เพิ่มตรรกะของคุณเองที่จะทำ(false) || false
set -e
(false)
echo survived
เช่นเดียวกับที่เกิดขึ้นกับtrap
ยัง ตรรกะด้านล่างใช้ไม่ได้ด้วยเหตุผลที่กล่าวมาข้างต้น
trap 'echo error' ERR
(false)
นี่คือกับดักง่ายๆที่พิมพ์อาร์กิวเมนต์สุดท้ายของสิ่งที่ล้มเหลวใน STDERR รายงานบรรทัดที่ล้มเหลวและออกจากสคริปต์โดยมีหมายเลขบรรทัดเป็นรหัสออก โปรดทราบว่าสิ่งเหล่านี้ไม่ใช่แนวคิดที่ดีเสมอไป แต่นี่แสดงให้เห็นถึงแอปพลิเคชันที่สร้างสรรค์บางอย่างที่คุณสามารถสร้างได้
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
ฉันใส่มันลงในสคริปต์ด้วยการวนซ้ำเพื่อทดสอบ ฉันแค่ตรวจสอบการตีตัวเลขสุ่ม คุณอาจใช้การทดสอบจริง ถ้าฉันต้องการประกันตัวฉันเรียกเท็จ (ซึ่งทำให้เกิดกับดัก) พร้อมกับข้อความที่ฉันต้องการโยน
สำหรับฟังก์ชันการทำงานอย่างละเอียดให้ Trap เรียกใช้ฟังก์ชันการประมวลผล คุณสามารถใช้คำสั่ง case กับ arg ($ _) ของคุณได้ตลอดเวลาหากคุณต้องการล้างข้อมูลเพิ่มเติม ฯลฯ กำหนดให้ var สำหรับน้ำตาลที่เป็นประโยค -
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false
while :
do x=$(( $RANDOM % 10 ))
case "$x" in
0) $throw "DIVISION BY ZERO" ;;
3) $raise "MAGIC NUMBER" ;;
*) echo got $x ;;
esac
done
ตัวอย่างผลลัพธ์:
# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6
แน่นอนคุณทำได้
runTest1 "Test1 fails" # message not used if it succeeds
มีพื้นที่มากมายสำหรับการปรับปรุงการออกแบบ
ผลตอบแทนรวมถึงความจริงที่false
ไม่สวย (เช่นน้ำตาล) และสิ่งอื่น ๆ ที่สะดุดกับดักอาจดูโง่เล็กน้อย ยังฉันชอบวิธีนี้
คุณมี 2 ตัวเลือก: เปลี่ยนเส้นทางเอาต์พุตของสคริปต์ไปยังไฟล์แนะนำไฟล์บันทึกในสคริปต์และ
ในที่นี้คุณถือว่าสคริปต์แสดงข้อมูลที่จำเป็นทั้งหมดรวมถึงคำเตือนและข้อความแสดงข้อผิดพลาด จากนั้นคุณสามารถเปลี่ยนทิศทางผลลัพธ์ไปยังไฟล์ที่คุณเลือกได้
./runTests &> output.log
คำสั่งดังกล่าวเปลี่ยนเส้นทางทั้งเอาต์พุตมาตรฐานและเอาต์พุตข้อผิดพลาดไปยังไฟล์บันทึกของคุณ
การใช้วิธีนี้คุณไม่จำเป็นต้องแนะนำไฟล์บันทึกในสคริปต์ดังนั้นตรรกะจึงง่ายขึ้นเล็กน้อย
ในสคริปต์ของคุณให้เพิ่มไฟล์บันทึกโดยการเข้ารหัสอย่างหนัก:
logFile='./path/to/log/file.log'
หรือส่งผ่านพารามิเตอร์:
logFile="${1}" # This assumes the first parameter to the script is the log file
เป็นความคิดที่ดีที่จะเพิ่มการประทับเวลาในขณะดำเนินการลงในไฟล์บันทึกที่ด้านบนของสคริปต์:
date '+%Y%-m%d-%H%M%S' >> "${logFile}"
จากนั้นคุณสามารถเปลี่ยนเส้นทางข้อความแสดงข้อผิดพลาดของคุณไปยังไฟล์บันทึก
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
fi
สิ่งนี้จะเพิ่มข้อผิดพลาดต่อท้ายไฟล์บันทึกและดำเนินการต่อ หากคุณต้องการหยุดการดำเนินการเมื่อเกิดข้อผิดพลาดร้ายแรงคุณสามารถexit
สคริปต์:
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
# Clean up if needed
exit 1;
fi
โปรดสังเกตว่าexit 1
โปรแกรมหยุดดำเนินการเนื่องจากข้อผิดพลาดที่ไม่ได้ระบุ คุณสามารถปรับแต่งสิ่งนี้ได้หากต้องการ
เมื่อใช้วิธีนี้คุณสามารถปรับแต่งบันทึกของคุณและมีไฟล์บันทึกที่แตกต่างกันสำหรับแต่ละองค์ประกอบของสคริปต์ของคุณ
หากคุณมีสคริปต์ที่ค่อนข้างเล็กหรือต้องการเรียกใช้สคริปต์ของผู้อื่นโดยไม่แก้ไขให้เป็นแนวทางแรกจะเหมาะสมกว่า
หากคุณต้องการให้ไฟล์บันทึกอยู่ในตำแหน่งเดียวกันเสมอนี่เป็นตัวเลือกที่ดีกว่าของ 2 นอกจากนี้หากคุณสร้างสคริปต์ขนาดใหญ่ที่มีส่วนประกอบหลายอย่างคุณอาจต้องการบันทึกแต่ละส่วนแตกต่างกันและแนวทางที่สองเป็นของคุณเท่านั้น ตัวเลือก
ฉันมักพบว่ามีประโยชน์ในการเขียนฟังก์ชันเพื่อจัดการข้อความแสดงข้อผิดพลาดเพื่อให้โค้ดนั้นสะอาดขึ้นโดยรวม
# Usage: die [exit_code] [error message]
die() {
local code=$? now=$(date +%T.%N)
if [ "$1" -ge 0 ] 2>/dev/null; then # assume $1 is an error code if numeric
code="$1"
shift
fi
echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
exit $code
}
สิ่งนี้ใช้รหัสข้อผิดพลาดจากคำสั่งก่อนหน้าและใช้เป็นรหัสข้อผิดพลาดเริ่มต้นเมื่อออกจากสคริปต์ทั้งหมด นอกจากนี้ยังบันทึกเวลาด้วยไมโครวินาทีที่รองรับ (วันที่ของ GNU %N
คือนาโนวินาทีซึ่งเราจะตัดทอนเป็นไมโครวินาทีในภายหลัง)
หากตัวเลือกแรกเป็นศูนย์หรือจำนวนเต็มบวกจะกลายเป็นรหัสออกและเราจะลบออกจากรายการตัวเลือก จากนั้นเราจะรายงานข้อความเป็นข้อผิดพลาดมาตรฐานโดยใช้ชื่อของสคริปต์คำว่า "ERROR" และเวลา (เราใช้การขยายพารามิเตอร์เพื่อตัดทอนนาโนวินาทีเป็นไมโครวินาทีหรือสำหรับเวลาที่ไม่ใช่ GNU เพื่อตัดทอนเช่น12:34:56.%N
ถึง12:34:56
) เครื่องหมายจุดคู่และช่องว่างจะถูกเพิ่มหลังคำว่า ERROR แต่เมื่อมีข้อความแสดงข้อผิดพลาดที่ให้มาเท่านั้น สุดท้ายเราออกจากสคริปต์โดยใช้รหัสทางออกที่กำหนดไว้ก่อนหน้านี้ทำให้เกิดกับดักตามปกติ
ตัวอย่างบางส่วน (สมมติว่ารหัสอยู่ในscript.sh
):
if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"
$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"
$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"