ในสคริปต์ Bash ฉันจะออกจากสคริปต์ทั้งหมดได้อย่างไรหากมีเงื่อนไขบางประการเกิดขึ้น


717

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

มีวิธีที่ฉันสามารถทำได้โดยไม่ต้องห่อทั้งสคริปต์ในขณะที่ห่วงและใช้ตัวแบ่ง? บางสิ่งบางอย่างเหมือนdun dun dun goto?

คำตอบ:


810

ลองคำสั่งนี้:

exit 1

แทนที่1ด้วยรหัสข้อผิดพลาดที่เหมาะสม ดูยังออกจากรหัสที่มีความหมายพิเศษ


4
@CMCDragonkai โดยปกติรหัสใด ๆ ที่ไม่ใช่ศูนย์จะทำงานได้ หากคุณไม่ต้องการอะไรเป็นพิเศษคุณสามารถใช้งานได้1อย่างต่อเนื่อง หากสคริปต์นั้นถูกเรียกใช้โดยสคริปต์อื่นคุณอาจต้องการกำหนดชุดรหัสสถานะของคุณเองด้วยความหมายเฉพาะ ตัวอย่างเช่น1การทดสอบ2== ล้มเหลวการรวบรวม == ล้มเหลว หากสคริปต์เป็นส่วนหนึ่งของอย่างอื่นคุณอาจต้องปรับเปลี่ยนรหัสเพื่อให้ตรงกับแนวทางปฏิบัติที่ใช้ ตัวอย่างเช่นเมื่อส่วนหนึ่งของชุดทดสอบดำเนินการโดย automake รหัส77จะใช้เพื่อทำเครื่องหมายการทดสอบที่ข้าม
MichałGórny

21
ไม่มีสิ่งนี้ปิดหน้าต่างด้วยไม่ใช่แค่ออกจากสคริปต์
Toni Leigh

7
@ToniLeigh ทุบตีไม่ได้มีแนวคิดของ "หน้าต่าง" คุณอาจสับสนเกี่ยวกับการตั้งค่าเฉพาะของคุณ - เช่น terminal emulator - ทำ
Michael Foukarakis

3
@ToniLeigh หากมีการปิด "หน้าต่าง" เป็นไปได้ว่าคุณกำลังวางexit #คำสั่งไว้ในฟังก์ชันไม่ใช่สคริปต์ (ในกรณีนี้ใช้return #แทน)
เจมี่

1
@Sevenearths 0 หมายความว่าทำได้สำเร็จจึงexit 0ออกจากสคริปต์และส่งคืน 0 (บอกสคริปต์อื่น ๆ ที่อาจใช้ผลลัพธ์ของสคริปต์นี้สำเร็จ)
VictorGalisson

689

ใช้set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

สคริปต์จะยุติหลังจากบรรทัดแรกที่ล้มเหลว (ส่งคืนโค้ดออกที่ไม่ใช่ศูนย์) ในกรณีนี้command-that-failed2จะไม่ทำงาน

หากคุณต้องตรวจสอบสถานะการส่งคืนของทุกคำสั่งเดียวสคริปต์ของคุณจะเป็นดังนี้:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

ด้วยชุด -eมันจะมีลักษณะ:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

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


10
ด้วยset -eคุณยังสามารถทำบางอย่างcommand 2>&1 || echo $?ออกจากคำสั่งที่มีข้อผิดพลาดโดยไม่หยุดสคริปต์:
Adobe

6
set -eจะยกเลิกสคริปต์ถ้าไปป์ไลน์หรือโครงสร้างคำสั่งส่งคืนค่าที่ไม่ใช่ศูนย์ ตัวอย่างเช่นfoo || barจะล้มเหลวก็ต่อเมื่อทั้งคู่fooและbarส่งกลับค่าที่ไม่ใช่ศูนย์ โดยปกติแล้วสคริปต์ทุบตีที่เขียนได้ดีจะทำงานหากคุณเพิ่มset -eเมื่อเริ่มต้นและการเพิ่มการทำงานดังกล่าวเป็นการตรวจสอบการมีสติอัตโนมัติ: ยกเลิกสคริปต์หากมีสิ่งผิดปกติเกิดขึ้น
Mikko Rantalainen

6
หากคุณกำลังไพพ์คำสั่งเข้าด้วยกันคุณสามารถล้มเหลวหากคำสั่งใดล้มเหลวด้วยการตั้งค่าset -o pipefailตัวเลือก
Jake Biesinger

18
ที่จริงรหัสสำนวนโดยไม่จะเป็นเพียงset -e make || exit $?
tripleee

4
set -uนอกจากนี้คุณมี ดูโหมดทุบตี boffอย่างไม่เป็นทางการ : set -euo pipefail.
Pablo A

236

ผู้ชาย SysOps เคยสอนเทคนิคกรงเล็บสามนิ้วให้ฉัน:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

ฟังก์ชั่นเหล่านี้คือ * NIX OS และเชลล์มีรสชาติที่แข็งแกร่ง ใส่พวกเขาที่จุดเริ่มต้นของสคริปต์ของคุณ (ทุบตีหรืออย่างอื่น) try()คำสั่งและรหัสของคุณบน

คำอธิบาย

(ขึ้นอยู่กับ ความคิดเห็นที่บินแกะ )

  • yell: พิมพ์ชื่อสคริปต์และอาร์กิวเมนต์ทั้งหมดไปที่stderr:
    • $0 เป็นเส้นทางไปยังสคริปต์
    • $* เป็นข้อโต้แย้งทั้งหมด
    • >&2วิธีการ>เปลี่ยนเส้นทางไปยัง stdout 2ท่อ ท่อ1จะเป็นของstdoutตัวเอง
  • dieทำเช่นเดียวกันyellแต่ออกด้วยสถานะการออกที่ไม่ใช่ 0ซึ่งหมายถึง“ ล้มเหลว”
  • tryใช้||(บูลีนOR) ซึ่งจะประเมินเฉพาะด้านขวาหากด้านซ้ายล้มเหลว
    • $@เป็นข้อโต้แย้งทั้งหมดอีกครั้ง แต่ที่แตกต่างกัน

3
ฉันค่อนข้างใหม่ในการเขียนสคริปต์ยูนิกซ์ คุณช่วยอธิบายวิธีการทำงานของฟังก์ชั่นด้านบนได้ไหม? ฉันเห็นไวยากรณ์ใหม่ที่ฉันไม่คุ้นเคย ขอบคุณ
kaizenCoder

15
ตะโกน : $0เป็นเส้นทางไปยังสคริปต์ $*เป็นข้อโต้แย้งทั้งหมด >&2หมายถึง“ >การเปลี่ยนเส้นทาง stdout เพื่อ&ท่อ2” ท่อ 1 จะ stdout ตัวเอง ดังนั้นตะโกนนำหน้าข้อโต้แย้งทั้งหมดที่มีชื่อสคริปต์และพิมพ์ไปยัง stderr ตายทำเช่นเดียวกับตะโกนแต่ออกด้วยสถานะทางออกที่ไม่ใช่ 0 ซึ่งหมายความว่า "ล้มเหลว" ลองใช้บูลีนหรือ||ซึ่งประเมินเฉพาะด้านขวาหากไฟล์ด้านซ้ายไม่ได้ล้มเหลว $@เป็นข้อโต้แย้งทั้งหมดอีกครั้ง แต่ที่แตกต่างกัน หวังว่าจะอธิบายทุกอย่าง
โบยบิน

1
ผมปรับเปลี่ยนนี้จะเป็นเพื่อให้คุณสามารถส่งข้อความและออกรหัสที่มีdie() { yell "$1"; exit $2; } die "divide by zero" 115
Mark Lakata

3
ฉันสามารถดูวิธีการที่ฉันต้องการใช้และyell dieอย่างไรก็ตามtryไม่มาก คุณสามารถให้ตัวอย่างของการใช้งานได้หรือไม่?
kshenoy

2
อืม แต่คุณใช้มันในสคริปต์ได้อย่างไร ฉันไม่เข้าใจสิ่งที่คุณหมายถึงโดย "ลอง () คำสั่งและรหัสของคุณใน"
TheJavaGuy-Ivan Milosavljević

33

หากคุณจะเรียกใช้สคริปต์ด้วยsourceคุณสามารถใช้return <x>ตำแหน่งที่<x>จะเป็นสถานะการออกจากสคริปต์ (ใช้ค่าที่ไม่เป็นศูนย์สำหรับข้อผิดพลาดหรือเท็จ) แต่ถ้าคุณเรียกใช้สคริปต์ที่เรียกใช้งานได้ (เช่นโดยตรงกับชื่อไฟล์) คำสั่ง return จะส่งผลให้เกิดการบ่น (ข้อความแสดงข้อผิดพลาด "return: สามารถส่งคืน` เท่านั้นจากฟังก์ชั่นหรือสคริปต์ที่มา ')

หากexit <x>ใช้แทนเมื่อสคริปต์ถูกเรียกใช้sourceจะส่งผลให้ออกจากเชลล์ที่เริ่มต้นสคริปต์ แต่สคริปต์ที่ปฏิบัติการได้จะสิ้นสุดลงตามที่คาดไว้

หากต้องการจัดการกับทั้งสองกรณีในสคริปต์เดียวกันคุณสามารถใช้

return <x> 2> /dev/null || exit <x>

วิธีนี้จะจัดการกับการเรียกใช้ที่เหมาะสม นั่นคือสมมติว่าคุณจะใช้คำสั่งนี้ที่ระดับบนสุดของสคริปต์ ฉันจะแนะนำไม่ให้ออกจากสคริปต์โดยตรงจากภายในฟังก์ชัน

หมายเหตุ: <x>ควรเป็นเพียงตัวเลข


ใช้งานไม่ได้สำหรับฉันในสคริปต์ที่มีการส่งคืน / ออกภายในฟังก์ชั่นนั่นคือมันเป็นไปได้ที่จะออกจากภายในฟังก์ชั่นที่ไม่มีเชลล์อยู่ แต่ไม่ทำให้ผู้เรียกฟังก์ชั่นดูแลการตรวจสอบรหัสส่งคืนของฟังก์ชันที่ถูกต้อง ?
ม.ค.

@jan สิ่งที่ผู้โทรทำ (หรือไม่ได้ทำ) ด้วยค่าส่งคืนเป็น orthogonal อย่างสมบูรณ์ (เช่นเป็นอิสระจาก) วิธีที่คุณกลับมาจากฟังก์ชั่น (... w / o ออกจากเชลล์ไม่ว่าสิ่งที่ขอร้อง) มันส่วนใหญ่ขึ้นอยู่กับรหัสโทรซึ่งไม่ได้เป็นส่วนหนึ่งของ Q & A นี้ คุณสามารถปรับแต่งค่าที่ส่งคืนของฟังก์ชันให้ตรงกับความต้องการของผู้โทร แต่คำตอบนี้ไม่ได้จำกัด ว่าค่าที่ส่งคืนนี้จะเป็น ...
kavadias

11

ฉันมักจะมีฟังก์ชั่นที่เรียกว่า run () เพื่อจัดการข้อผิดพลาด ทุกการโทรที่ฉันต้องการจะถูกส่งผ่านไปยังฟังก์ชันนี้ดังนั้นสคริปต์ทั้งหมดจึงออกเมื่อมีการล้มเหลว ข้อได้เปรียบของสิ่งนี้เหนือโซลูชัน set -e คือสคริปต์ไม่ได้ออกจากโหมดเงียบเมื่อสายล้มเหลวและสามารถบอกคุณได้ว่าปัญหาคืออะไร ในตัวอย่างต่อไปนี้บรรทัดที่ 3 จะไม่ถูกเรียกใช้งานเนื่องจากสคริปต์ออกจากการเรียกใช้เป็นเท็จ

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

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

ฉันจำเหตุผลที่ใช้ eval ไม่ได้ฟังก์ชั่นทำงานได้ดีกับ cmd_output = $ ($ 1)
Joseph Sheedy

ฉันเพิ่งใช้สิ่งนี้เป็นส่วนหนึ่งของกระบวนการปรับใช้ที่ซับซ้อนและใช้งานได้ยอดเยี่ยม ขอบคุณและนี่คือความคิดเห็นและ upvote
fuzzygroup

ผลงานที่น่าทึ่งอย่างแท้จริง! นี่เป็นวิธีที่ง่ายและสะอาดที่สุดที่ทำงานได้ดี set -e optionสำหรับฉันฉันเพิ่มนี้ก่อนที่คำสั่งในการวนลูปตั้งแต่จะไม่ได้รับ runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'แล้วคำสั่งเพราะมันมีข้อโต้แย้งผมใช้ราคาเดียวเพื่อหลีกเลี่ยงปัญหาทุบตีต้องการเพื่อ: หมายเหตุฉันเปลี่ยนชื่อของฟังก์ชันเป็น runTry
Tony-Caffe

1
evalอาจเป็นอันตรายได้หากคุณยอมรับอินพุตโดยพลการ แต่สิ่งนี้ดูดีทีเดียว
dragon788

5

แทนที่จะifสร้างคุณสามารถใช้การประเมินการลัดวงจร :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

จดบันทึกวงเล็บซึ่งจำเป็นเนื่องจากลำดับความสำคัญของโอเปอเรเตอร์สำรอง $?เป็นตัวแปรพิเศษที่กำหนดให้ออกจากรหัสของคำสั่งที่เรียกว่าล่าสุด


1
ถ้าฉันใช้command -that --fails || exit $?มันได้โดยไม่ต้องใส่วงเล็บอะไรecho $[4/0]ที่ทำให้เราต้องการมัน?
Anentropic

2
@Anentropic @skalee วงเล็บไม่เกี่ยวข้องกับลำดับความสำคัญ แต่เป็นการจัดการข้อยกเว้น หารโดยศูนย์จะทำให้เกิดทางออกทันทีจากเปลือกที่มีรหัส 1. โดยไม่ต้องวงเล็บ (เช่นง่ายecho $[4/0] || exit $?) ทุบตีจะไม่ดำเนินการให้อยู่คนเดียวเชื่อฟังecho ||
bobbogo

1

ฉันมีคำถามเดียวกัน แต่ไม่สามารถถามได้เพราะมันจะซ้ำกัน

คำตอบที่ยอมรับซึ่งใช้ exit ไม่ทำงานเมื่อสคริปต์มีความซับซ้อนเพิ่มขึ้นเล็กน้อย หากคุณใช้กระบวนการพื้นหลังเพื่อตรวจสอบเงื่อนไขให้ออกจากกระบวนการนั้นเท่านั้นเนื่องจากจะทำงานในเชลล์ย่อย ในการฆ่าสคริปต์คุณต้องฆ่ามันอย่างชัดเจน (อย่างน้อยนั่นก็เป็นวิธีเดียวที่ฉันรู้)

นี่เป็นสคริปต์เล็กน้อยในการทำ:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

นี่เป็นคำตอบที่ดีกว่า แต่ก็ยังไม่สมบูรณ์ a จริงๆฉันไม่รู้วิธีกำจัดส่วนที่บูม

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