ออกจาก Shell Script ตามรหัสการออกจากกระบวนการ


378

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


3
ฉันตอบว่าสมมติว่าคุณกำลังใช้ bash แต่ถ้าเป็นเชลล์ที่แตกต่างกันมากคุณสามารถระบุในโพสต์ของคุณได้หรือไม่
Martin W

ฮาร์ดเมธอด: ทดสอบค่าของ$?after ทุกคำสั่ง วิธีการง่าย ๆ : ใส่set -eหรือ#!/bin/bash -eที่ด้านบนของสคริปต์ Bash ของคุณ
mwfearnley

คำตอบ:


494

หลังจากแต่ละคำสั่งรหัสทางออกสามารถพบได้ใน$?ตัวแปรดังนั้นคุณจะมีลักษณะดังนี้:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

คุณต้องระวังคำสั่ง piped เพราะ$?เพียงแค่ให้รหัสส่งคืนขององค์ประกอบสุดท้ายใน pipe ดังนั้นในรหัส:

ls -al file.ext | sed 's/^/xx: /"

จะไม่ส่งคืนรหัสข้อผิดพลาดหากไฟล์นั้นไม่มีอยู่ (เนื่องจากsedส่วนหนึ่งของไปป์ไลน์ใช้งานได้จริงส่งคืน 0)

เปลือกจริงให้อาร์เรย์ที่สามารถให้ความช่วยเหลือในกรณีที่ผู้ที่เป็นอยู่bash PIPESTATUSอาร์เรย์นี้มีองค์ประกอบหนึ่งรายการสำหรับแต่ละส่วนประกอบของไปป์ไลน์ซึ่งคุณสามารถเข้าถึงทีละรายการเช่น${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

โปรดทราบว่านี่จะทำให้คุณได้ผลลัพธ์ของfalseคำสั่งไม่ใช่ไปป์ไลน์ทั้งหมด คุณยังสามารถทำให้รายการทั้งหมดดำเนินการตามที่เห็นสมควร:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

หากคุณต้องการได้รับรหัสข้อผิดพลาดที่ใหญ่ที่สุดจากไปป์ไลน์คุณสามารถใช้สิ่งต่อไปนี้:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

สิ่งนี้จะผ่านแต่ละPIPESTATUSองค์ประกอบในทางกลับกันเก็บไว้ในrcถ้ามันมากกว่าrcค่าก่อนหน้า


39
ฟีเจอร์เดียวกันในโค้ดพกพาเพียงหนึ่งบรรทัด: ls -al file.ext || exit $?([[]] ไม่สามารถพกพาได้)
MarcH

19
MarcH ฉันคิดว่าคุณจะพบว่า[[ ]]เป็นอุปกรณ์พกพาที่สวยbashซึ่งเป็นคำถามที่ติดแท็ก :-) แปลกมากพอlsไม่ทำงานcommand.comดังนั้นมันจึงไม่พกพาได้เช่นกันฉันรู้ดี แต่มันก็เป็นข้อโต้แย้งแบบเดียวกับคุณ นำเสนอ.
paxdiablo

39
ฉันรู้ว่านี่เป็นของโบราณ แต่ควรสังเกตว่าคุณสามารถรับรหัสทางออกของคำสั่งในPIPESTATUS${PIPESTATUS[0]}${PIPESTATUS[1]}${PIPESTATUS[*]}
ไพพ์

11
จะต้องมีการเน้นย้ำว่าการเขียนสคริปต์เชลล์ที่สวยงามและเป็นสำนวนมักไม่ค่อยต้องการตรวจสอบ$?โดยตรง คุณมักต้องการบางสิ่งif ls -al file.ext; then : nothing; else exit $?; fiที่แน่นอนเช่น @MarcH พูดว่าเทียบเท่าls -al file.ext || exit $?แต่ถ้าคำสั่งthenหรือelseประโยคค่อนข้างซับซ้อนกว่า
tripleee

9
[[ $rc != 0 ]]จะทำให้คุณมีข้อผิดพลาด0: not foundหรือ นี้ควรจะเปลี่ยนไป1: not found [ $rc -ne 0 ]นอกจากนี้ก็จะถูกลบออกและใช้เพียงrc=$? [ $? -ne 0 ]
CurtisLeeBolin

223

หากคุณต้องการทำงานกับ $? คุณจะต้องตรวจสอบหลังจากแต่ละคำสั่งตั้งแต่ $? ถูกอัพเดตหลังจากออกแต่ละคำสั่ง ซึ่งหมายความว่าหากคุณดำเนินการไปป์ไลน์คุณจะได้รับรหัสออกจากกระบวนการสุดท้ายในขั้นตอนเท่านั้น

อีกวิธีคือ:

set -e
set -o pipefail

หากคุณวางสิ่งนี้ไว้ที่ด้านบนสุดของเชลล์สคริปต์ดูเหมือนว่าทุบตีจะดูแลสิ่งนี้ให้คุณ ดังที่ผู้โพสต์ก่อนหน้าระบุไว้ "set -e" จะทำให้ bash ออกจากโดยมีข้อผิดพลาดในคำสั่งง่ายๆ "set -o pipefail" จะทำให้ bash ออกโดยมีข้อผิดพลาดในคำสั่งใด ๆ ในไปป์ไลน์เช่นกัน

ดูที่นี่หรือที่นี่สำหรับการสนทนาเพิ่มเติมเล็กน้อยเกี่ยวกับปัญหานี้ นี่คือส่วนทุบตีด้วยตนเองในชุด builtin


6
นี่ควรเป็นคำตอบที่ดีที่สุด: ทำได้ง่ายกว่าการใช้PIPESTATUSและตรวจสอบรหัสออกทุกที่
candu

2
#!/bin/bash -eเป็นวิธีเดียวที่จะเริ่มเชลล์สคริปต์ คุณสามารถใช้สิ่งต่าง ๆ เช่นfoo || handle_error $?ถ้าคุณต้องการตรวจสอบสถานะการออกจริง
Davis Herring

53

" set -e" น่าจะเป็นวิธีที่ง่ายที่สุดในการทำเช่นนี้ เพียงแค่ใส่คำสั่งก่อนหน้าในโปรแกรมของคุณ


6
@SwaroopCH set -eสคริปต์ของคุณจะยกเลิกหากคำสั่งใด ๆ ในการออกสคริปต์ของคุณพร้อมสถานะข้อผิดพลาดและคุณไม่ได้จัดการข้อผิดพลาดนี้
แอนดรู

2
set -eเทียบเท่ากับ 100% set -o errexitซึ่งไม่เหมือนกับการค้นหาแบบเดิม ค้นหา opengroup + errexit เพื่อรับเอกสารอย่างเป็นทางการ
MarcH

30

ถ้าคุณเพิ่งเรียก exit ใน bash โดยไม่มีพารามิเตอร์มันจะคืนค่า exit code ของคำสั่งสุดท้าย รวมกับหรือทุบตีควรเรียกออกเท่านั้นถ้าคำสั่งก่อนหน้านี้ล้มเหลว แต่ฉันไม่ได้ทดสอบสิ่งนี้

command1 || ออกจาก;
command2 || ออกจาก;

Bash จะเก็บรหัสทางออกของคำสั่งสุดท้ายในตัวแปร $?



21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. ฉันจะรับรหัสทางออกของได้อย่างไร cmd1ในcmd1|cmd2

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

    cmd | head -1

    คุณอาจสังเกตเห็นสถานะการออก 141 (หรือ 269 ที่มี ksh93) cmd1แต่เป็นเพราะcmdสัญญาณ SIGPIPE ถูกขัดจังหวะเมื่อ head -1ยกเลิกหลังจากอ่านหนึ่งบรรทัด

    หากต้องการทราบสถานะทางออกขององค์ประกอบของไปป์ไลน์ cmd1 | cmd2 | cmd3

    ด้วย zsh:

    รหัสทางออกมีอยู่ในอาร์เรย์พิเศษ pipestatus cmd1รหัสการออกอยู่ใน$pipestatus[1], cmd3รหัสการออก $pipestatus[3]เพื่อให้$?เป็นเช่นเดียวกับ $pipestatus[-1]เสมอ

    ข ด้วยทุบตี:

    รหัสทางออกมีอยู่ในPIPESTATUSอาร์เรย์พิเศษ cmd1รหัสการออกอยู่ใน${PIPESTATUS[0]}, cmd3รหัสการออก ${PIPESTATUS[2]}เพื่อให้$?เป็นเช่นเดียวกับเสมอ ${PIPESTATUS: -1}เสมอ

    ...

    ดูรายละเอียดเพิ่มเติมดังต่อไปนี้การเชื่อมโยง


19

สำหรับทุบตี:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

19
อาจไม่ควร "ออกจาก 0" เนื่องจากนั่นบ่งชี้ความสำเร็จ
ดี

3
exit_code = $ ?; echo "สคริปต์ถูกยกเลิกเนื่องจากข้อผิดพลาด"; ออกจาก $ exit_code
RaSergiy

1
@ HAL9001 คุณมีหลักฐานการนี้หรือไม่? เอกสารไอบีเอ็มกล่าวว่าเป็นอย่างอื่น
Patrick James McDougle

11

ในการทุบตีสิ่งนี้เป็นเรื่องง่ายเพียงผูกเข้าด้วยกันด้วย &&:

command1 && command2 && command3

คุณยังสามารถใช้การซ้อนถ้าสร้าง:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi

+1 นี่เป็นทางออกที่ง่ายที่สุดที่ฉันต้องการ นอกจากนี้คุณยังสามารถเขียนได้if (! command)หากคุณต้องการรหัสข้อผิดพลาดที่ไม่ใช่ศูนย์
Berci

สิ่งนี้สำหรับคำสั่งแบบต่อเนื่อง .. จะเกิดอะไรขึ้นถ้าฉันต้องการเปิดทั้งสามแบบขนานและฆ่าทุกคนหากมีข้อใดข้อหนึ่งล้มเหลว
Vasile Surdu

4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit

2
ไม่มีเหตุผลที่จะใช้$*; ใช้"$@"แทนเพื่อรักษาช่องว่างและสัญลักษณ์แทน
Davis Herring
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.