การยกเลิกเชลล์สคริปต์หากคำสั่งใดส่งคืนค่าที่ไม่ใช่ศูนย์


437

ฉันมีสคริปต์เชลล์ Bash ที่เรียกใช้คำสั่งจำนวนมาก ฉันต้องการให้เชลล์สคริปต์ออกโดยอัตโนมัติด้วยค่าส่งคืน 1 หากคำสั่งใด ๆ ส่งคืนค่าที่ไม่เป็นศูนย์

เป็นไปได้โดยไม่ตรวจสอบผลลัพธ์ของแต่ละคำสั่งอย่างชัดเจนหรือไม่

เช่น

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi

8
นอกจากset -eนี้ยังทำset -u(หรือset -eu) -uหมดสิ้นไปกับพฤติกรรมที่งี่เง่าและซ่อนบั๊กที่คุณสามารถเข้าถึงตัวแปรที่ไม่มีอยู่และมีค่าว่างที่สร้างขึ้นโดยไม่มีการวินิจฉัย
Kaz

คำตอบ:


742

เพิ่มสิ่งนี้ไปยังจุดเริ่มต้นของสคริปต์:

set -e

สิ่งนี้จะทำให้เชลล์ออกจากทันทีหากคำสั่งง่าย ๆ ออกจากด้วยค่าการออกที่ไม่ใช่ศูนย์ คำสั่งง่ายๆคือคำสั่งใด ๆ ที่ไม่ได้เป็นส่วนหนึ่งของคำสั่ง if, while, หรือจนกว่าจะถึงการทดสอบหรือส่วนหนึ่งของ && หรือ || รายการ.

ดูหน้าbash (1) man ที่คำสั่งภายใน "set" สำหรับรายละเอียดเพิ่มเติม

ฉันเริ่มเชลล์สคริปต์เกือบทั้งหมดด้วย "set -e" เป็นเรื่องที่น่ารำคาญจริงๆที่สคริปต์จะดำเนินต่อไปอย่างดื้อรั้นเมื่อมีบางอย่างล้มเหลวตรงกลางและทำลายสมมุติฐานของสคริปต์ที่เหลือ


36
แต่ฉันชอบใช้ "#! / usr / bin / env bash" เพราะฉันมักจะใช้ bash จากที่อื่นที่ไม่ใช่ / bin และ "#! / usr / bin / env bash -e" ไม่ทำงาน นอกจากนี้ยังเป็นเรื่องดีที่มีสถานที่ที่จะแก้ไขเพื่ออ่าน "set -xe" เมื่อฉันต้องการเปิดการสืบค้นกลับสำหรับการดีบัก
Ville Laurikari

48
นอกจากนี้ยังมีธงในบรรทัด shebang bash script.shจะถูกละเว้นถ้าสคริปต์ได้รับการทำงานเป็น
Tom Anderson

27
เพียงแค่ทราบ: หากคุณประกาศฟังก์ชั่นภายในสคริปต์ทุบตีฟังก์ชั่นจะต้องมีการตั้งค่า -e redeclared ภายในร่างกายของฟังก์ชั่นถ้าคุณต้องการที่จะขยายฟังก์ชั่นนี้
Jin Kim

8
นอกจากนี้หากคุณระบุสคริปต์ของคุณบรรทัด shebang จะไม่เกี่ยวข้อง

4
@JinKim นั่นไม่ได้เป็นอย่าง bash 3.2.48 ลองทำสิ่งต่อไปนี้ภายในสคริปต์: set -e; tf() { false; }; tf; echo 'still here'. แม้จะไม่ได้set -eอยู่ในร่างของtf()การดำเนินการถูกยกเลิก บางทีคุณอาจจะพูดแบบนั้นset -eไม่ได้สืบทอดโดยsubshellsซึ่งเป็นเรื่องจริง
mklement0

202

วิธีเพิ่มคำตอบที่ยอมรับ:

จำไว้ว่าset -eบางครั้งก็ไม่พอโดยเฉพาะถ้าคุณมีท่อ

ตัวอย่างเช่นสมมติว่าคุณมีสคริปต์นี้

#!/bin/bash
set -e 
./configure  > configure.log
make

... ซึ่งใช้งานได้ตามที่คาดไว้: มีข้อผิดพลาดในการconfigureยกเลิกการดำเนินการ

พรุ่งนี้คุณจะเปลี่ยนแปลงเล็กน้อยที่ดูเหมือน:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... และตอนนี้มันไม่ทำงาน นี่คือคำอธิบายที่นี่และมีวิธีแก้ปัญหา (Bash เท่านั้น):

#! / bin / ทุบตี
ชุด -e 
set -o pipefail

./configure | tee configure.log
แต่งหน้า

1
ขอบคุณสำหรับการอธิบายถึงความสำคัญของการต้องpipefailไปด้วยset -o!
Malcolm

83

คำสั่ง if ในตัวอย่างของคุณไม่จำเป็น เพียงทำแบบนี้:

dosomething1 || exit 1

หากคุณทำตามคำแนะนำของ Ville Laurikari และใช้set -eสำหรับคำสั่งบางอย่างคุณอาจต้องใช้สิ่งนี้:

dosomething || true

|| trueจะทำให้ท่อส่งคำสั่งมีtrueค่าตอบแทนแม้ว่าคำสั่งล้มเหลวเพื่อให้ได้-eตัวเลือกที่จะไม่ฆ่าสคริปต์


1
ฉันชอบสิ่งนี้. โดยเฉพาะอย่างยิ่งเพราะคำตอบยอดนิยมคือ bash-centric (ไม่ชัดเจนเลยสำหรับฉันว่า / ใช้กับ zsh scripting ในระดับใด) และฉันสามารถค้นหามันได้ แต่คุณก็ชัดเจนขึ้นเพราะตรรกะ
g33kz0r

set -eไม่ bash-centric - รองรับแม้บน Bourne Shell ดั้งเดิม
Marcos Vives Del Sol

27

หากคุณมีการล้างข้อมูลที่คุณต้องทำเมื่อออกจากระบบคุณสามารถใช้ 'กับดัก' กับ ERR สัญญาณหลอกได้ วิธีนี้ทำงานในลักษณะเดียวกันกับการดัก INT หรือสัญญาณอื่น ๆ bash โยน ERR หากคำสั่งใด ๆ ออกจากด้วยค่าที่ไม่ใช่ศูนย์:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

หรือโดยเฉพาะอย่างยิ่งหากคุณใช้ "set -e" คุณสามารถดัก EXIT; กับดักของคุณจะถูกดำเนินการเมื่อสคริปต์ออกด้วยเหตุผลใด ๆ รวมถึงการสิ้นสุดปกติการขัดจังหวะการออกที่เกิดจากตัวเลือก -e ฯลฯ


12

$?ตัวแปรไม่ค่อยจำเป็น หลอกสำนวนควรจะเขียนเป็นcommand; if [ $? -eq 0 ]; then X; fiif command; then X; fi

เคสที่$?จำเป็นต้องมีคือเมื่อต้องการตรวจสอบกับค่าหลายค่า:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

หรือเมื่อ$?ต้องการนำกลับมาใช้ใหม่หรือถูกจัดการ:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi

3
ทำไม "ควรเขียนเป็น" เสมอ ฉันหมายความว่าทำไม " ควร " เป็นเช่นนั้น? เมื่อคำสั่งยาว (คิดว่าการเรียกใช้ GCC ด้วยตัวเลือกโหล) จะสามารถอ่านคำสั่งได้ก่อนที่จะตรวจสอบสถานะการส่งคืน
ysap

หากคำสั่งยาวเกินไปคุณสามารถแยกมันได้โดยตั้งชื่อมัน (กำหนดฟังก์ชั่นเชลล์)
Mark Edgar

12

เรียกใช้ด้วย-eหรือset -eที่ด้านบน

set -uนอกจากนี้ยังมองไปที่


34
ในการบันทึกผู้อื่นที่จำเป็นต้องอ่านhelp set: ปฏิบัติการ-uอ้างอิงถึงตัวแปรที่ไม่ได้ตั้งค่าเป็นข้อผิดพลาด
mklement0

1
ดังนั้นมันเป็นอย่างใดอย่างหนึ่งset -uหรือset -eไม่ทั้งสองอย่าง? @lumpynose
ericn

1
@eric ฉันออกเมื่อหลายปีก่อน แม้ว่าฉันจะรักงานของฉัน แต่สมองผู้สูงอายุของฉันก็ลืมทุกอย่างไปแล้ว Offhand ฉันเดาว่าคุณสามารถใช้ทั้งคู่ได้ ถ้อยคำที่ไม่ดีในส่วนของฉัน; ฉันควรจะพูดว่า "และ / หรือ"
lumpynose

3

การแสดงออกเช่น

dosomething1 && dosomething2 && dosomething3

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

cat nosuchfile && echo "done"
echo $?
1


-2

เพียงแค่โยนเอกสารอ้างอิงอีกอันหนึ่งเนื่องจากมีคำถามเพิ่มเติมเกี่ยวกับการป้อนข้อมูลของ Mark Edgars และนี่คือตัวอย่างเพิ่มเติมและสัมผัสกับหัวข้อโดยรวม:

[[ `cmd` ]] && echo success_else_silence

ซึ่งเหมือนกับที่cmd || exit errcodeแสดงให้เห็น

เช่น. ฉันต้องการตรวจสอบให้แน่ใจว่ามีการยกเลิกการต่อเชื่อมพาร์ติชันหากติดตั้งแล้ว:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 

5
ไม่[[ cmd`]] `ไม่เหมือนกัน เป็นเท็จถ้าเอาต์พุตของคำสั่งว่างเปล่าและเป็นจริงไม่เช่นนั้นโดยไม่คำนึงถึงสถานะการออกของคำสั่ง
Gilles 'หยุดชั่วร้าย'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.