มีคำสั่ง TRY CATCH ใน Bash หรือไม่


349

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


1
มันอาจช่วยได้หากคุณสามารถอธิบายรายละเอียดของปัญหาที่คุณพยายามแก้ไขได้ ดูเหมือนว่าคุณไม่ใช่คนใหม่ที่นี่ แต่คุณยังอาจต้องการไปที่ศูนย์ช่วยเหลือและดูความช่วยเหลือเกี่ยวกับวิธีถามคำถามที่ดี
devnull

ที่กล่าวไว้ดูเหมือนว่าคำพูดhelp testอาจช่วยให้คุณค้นหาวิธีการแก้ไขปัญหาของคุณ
devnull

2
try / catch / ในที่สุด block ไม่ใช่คำสั่งมันเป็นโครงสร้าง
Ben

ซ้ำเป็นไปได้ของเปลือก Linux ลองจับในที่สุด
Blong

@LeeProbert: เนื่องจากคุณไม่มีข้อยกเว้นในการทุบตีฉันสงสัยว่าคุณต้องการจับอะไร สิ่งที่ใกล้เคียงที่สุดในทิศทางของการยกเว้นจะเป็นสัญญาณและส่วนใหญ่ (ไม่ใช่ทั้งหมด) คุณสามารถจับได้โดยใช้trapคำสั่ง
user1934428

คำตอบ:


563

มีคำสั่ง TRY CATCH ใน Bash หรือไม่?

เลขที่

Bash ไม่มีสินค้าฟุ่มเฟือยมากเท่าที่จะหาได้ในภาษาการเขียนโปรแกรมจำนวนมาก

ไม่มีการtry/catchทุบตี แต่หนึ่งสามารถบรรลุพฤติกรรมคล้ายกันโดยใช้หรือ&&||

การใช้||:

หากcommand1ล้มเหลวให้command2รันดังนี้

command1 || command2

ในทำนองเดียวกันการใช้&&, command2จะทำงานถ้าcommand1ประสบความสำเร็จ

การประมาณที่ใกล้เคียงที่สุดของtry/catchเป็นดังนี้

{ # try

    command1 &&
    #save your output

} || { # catch
    # save log for exception 
}

ทุบตียังมีกลไกการจัดการข้อผิดพลาดบางอย่างเช่นกัน

set -e

มันหยุดสคริปต์ของคุณหากคำสั่งง่ายๆใด ๆ ล้มเหลว

if...elseและยังทำไมไม่ มันเป็นเพื่อนที่ดีที่สุดของคุณ


18
เมื่อใช้สิ่งนี้คุณต้องระวังว่าโค้ดสำหรับ#save your outputไม่ล้มเหลวมิฉะนั้นบล็อก "catch" จะยังคงทำงาน
chepner

7
มีข้อเสนอแนะเพื่อใช้if...elseโครงสร้าง สิ่งนี้หมายความว่าคำสั่ง bash แก้ไขเป็น "ความจริง" หรือไม่หากคำสั่งเหล่านั้นทำงานได้สำเร็จและ "ผิดพลาด" หากล้มเหลว?
ลุคกริฟฟิ ธ ส์

6
สำหรับผู้อ่านหัวข้อนี้: ดูเหมือนว่าset -eไม่จำเป็นต้องเป็นวิธีที่ดีที่สุดในการทำสิ่งต่าง ๆ นี่คือตัวอย่างข้อโต้แย้ง / กรณีพิเศษ: mywiki.wooledge.org/BashFAQ/105
ลุคเดวิส

2
ฉันขอทราบวิธีบันทึกข้อยกเว้นได้ไหม โดยปกติในโค้ดจาวาเราสามารถใช้ system.out.log (e) แต่ในเชลล์ล่ะ?
Panadol Chong

112

จากคำตอบบางข้อที่ฉันพบที่นี่ฉันทำให้ตัวเองเป็นไฟล์ตัวช่วยเล็ก ๆ เพื่อเป็นแหล่งข้อมูลสำหรับโครงการของฉัน:

trycatch.sh

#!/bin/bash

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function throwErrors()
{
    set -e
}

function ignoreErrors()
{
    set +e
}

นี่คือตัวอย่างลักษณะที่ใช้งาน:

#!/bin/bash
export AnException=100
export AnotherException=101

# start with a try
try
(   # open a subshell !!!
    echo "do something"
    [ someErrorCondition ] && throw $AnException

    echo "do something more"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # automaticatly end the try block, if command-result is non-null
    echo "now on to something completely different"
    executeCommandThatMightFail

    echo "it's a wonder we came so far"
    executeCommandThatFailsForSure || true # ignore a single failing command

    ignoreErrors # ignore failures of commands until further notice
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
    executeCommand3ThatFailsForSure

    echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
    # now you can handle
    case $ex_code in
        $AnException)
            echo "AnException was thrown"
        ;;
        $AnotherException)
            echo "AnotherException was thrown"
        ;;
        *)
            echo "An unexpected exception was thrown"
            throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
        ;;
    esac
}

2
คุณจะช่วยนำเข้าฟังก์ชั่นลองตรวจจับในตัวอย่างอื่นได้อย่างไร (ฉันสมมติว่าพวกเขาอยู่ในแฟ้มที่แยกต่างหาก)
kilianc

1
@ kilianc: ฉันเพิ่งจะชอบมัน: source inc / trycatch.sh
Mathias Henze

2
@MathiasHenze ขอบคุณชายรหัสของคุณยอดเยี่ยม แต่ทำไมคุณต้องมี a ||หลังcatchและก่อนหน้า{}บล็อก? ฉันคิดว่ามันจะเป็น&&
Remy San

(ตอบช้าสำหรับทุกคนที่พบสิ่งนี้) เป็นหลักกรณีข้อผิดพลาดif False or run_if_failed()หมายถึงการลัดวงจรหรือลองใช้คำสั่งแรกที่ไม่ได้กลับมาจริงและตอนนี้ย้ายไปยังคำสั่งต่อไป &&จะไม่ทำงานเพราะคำสั่งแรก ( try) ให้ผลที่ผิดพลาดซึ่งหมายความว่าคำสั่งไม่จำเป็นต้องตามกฎซ้ำซากcatch false&any equals falseมีเพียงไฟฟ้าลัดวงจรและ / หรือที่จะทำงานทั้งสองอย่าง
ldmtwo

69

ฉันได้พัฒนาการใช้งานลองและจับได้อย่างไร้ที่ติใน bash ซึ่งช่วยให้คุณเขียนโค้ดเช่น:

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

คุณสามารถวางบล็อกบล็อคที่อยู่ภายในตัวเองได้!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

รหัสเป็นส่วนหนึ่งของฉันทุบตีสำเร็จรูป / กรอบ นอกจากนี้ยังขยายแนวคิดของการลองและจับกับสิ่งต่าง ๆ เช่นการจัดการข้อผิดพลาดด้วย backtrace และข้อยกเว้น (รวมถึงคุณสมบัติที่ดีอื่น ๆ )

นี่คือรหัสที่รับผิดชอบเพียงลองและจับ:

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # BACKWARDS COMPATIBILE WAY:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # so that we may continue with a "catch"
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

รู้สึกอิสระที่จะใช้ส้อมและมีส่วนร่วม - มันบนGitHub


@ erm3nda ดีใจที่ได้ยินเช่นนั้น! ฉันคิดว่าฉันได้ฆ่าแมลงบางตัวหลังจากที่ฉันโพสต์สิ่งนี้ไปแล้วลองดูที่ GitHub เพื่อรับทราบการอัปเดต (คุณจะต้องมี 03_exception.sh และ 04_try_catch.sh) เวอร์ชันปัจจุบันค่อนข้างกันกระสุนเท่าที่ฉันรู้
niieani

ดีมาก! ฉันจะใช้ในโครงการของฉัน ฉันทำงานใน 5 นาทีและ centos ของฉันพร้อมทุบตี 4.2.46
เฟลิเป้

1
มีปัญหาพื้นฐานอยู่ที่นี่: หากคุณเปลี่ยนตัวแปรในบล็อกลองจะไม่สามารถเห็นได้นอกเพราะมันทำงานใน sub-shell
Kan Li

1
@ KanLi ถูกต้อง หากคุณสนใจเกี่ยวกับผลลัพธ์ของการลอง / จับคุณสามารถจับได้เช่น:my_output=$(try { code...; } catch { code...; })
niieani

ในเวอร์ชันล่าสุดดูเหมือนว่า EXCEPTION_LINE ถูกเปลี่ยนชื่อเป็น BACKTRACE_LINE github.com/niieani/bash-oo-framework#using-try--catch
Ben Creasy

19

คุณสามารถใช้trap:

try { block A } catch { block B } finally { block C }

แปลเป็น:

(
  set -Ee
  function _catch {
    block B
    exit 0  # optional; use if you don't want to propagate (rethrow) error to outer shell
  }
  function _finally {
    block C
  }
  trap _catch ERR
  trap _finally EXIT
  block A
)

คุณต้องการ-Eตั้งค่าสถานะฉันคิดว่าดังนั้นกับดักจึงแพร่กระจายไปยังฟังก์ชั่น
Mark K Cowan

17

มีวิธีแก้ไขปัญหาที่คล้ายกันมากมายซึ่งอาจใช้งานได้ ด้านล่างเป็นวิธีที่ง่ายและใช้งานได้เพื่อให้ได้ลอง / จับโดยมีคำอธิบายในความคิดเห็น

#!/bin/bash

function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}

# this subshell is a scope of try
# try
(
  # this flag will make to exit from current subshell on any error
  # inside it (all functions run inside will also break on any error)
  set -e
  a
  b
  # do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
  echo "We have an error"
  # We exit the all script with the same error, if you don't want to
  # exit it and continue, just delete this line.
  exit $errorCode
fi

15

bashไม่ยกเลิกการดำเนินการที่กำลังรันอยู่ในกรณีที่บางอย่างตรวจพบสถานะข้อผิดพลาด (เว้นแต่คุณจะตั้งค่า-eสถานะ) ภาษาการเขียนโปรแกรมที่เสนอtry/catchทำเช่นนี้เพื่อยับยั้ง "การประกันตัว" เนื่องจากสถานการณ์พิเศษนี้ (โดยทั่วไปเรียกว่า "ข้อยกเว้น")

ในbash, เฉพาะคำสั่งที่เป็นปัญหาเท่านั้นที่จะออกด้วยรหัสการออกที่มากกว่า 0, ซึ่งระบุสถานะข้อผิดพลาดนั้น คุณสามารถตรวจสอบสิ่งนั้นได้ แต่เนื่องจากไม่มีการประกันตัวโดยอัตโนมัติจากสิ่งใดการลอง / จับไม่สมเหตุสมผล มันแค่ขาดบริบทนั้น

อย่างไรก็ตามคุณสามารถจำลองการประกันตัวโดยใช้เชลล์ย่อยซึ่งสามารถยุติได้ในจุดที่คุณตัดสินใจ:

(
  echo "Do one thing"
  echo "Do another thing"
  if some_condition
  then
    exit 3  # <-- this is our simulated bailing out
  fi
  echo "Do yet another thing"
  echo "And do a last thing"
)   # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
  echo "Bail out detected"
fi

แทนการที่some_conditionกับifคุณยังสามารถลองสั่งและในกรณีที่ล้มเหลว (มีมากขึ้นกว่ารหัสทางออก 0), ประกันตัวออก:

(
  echo "Do one thing"
  echo "Do another thing"
  some_command || exit 3
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

น่าเสียดายที่การใช้เทคนิคนี้คุณถูก จำกัด ให้ใช้รหัสทางออกที่แตกต่างกัน 255 รายการ (1..255) และไม่สามารถใช้วัตถุข้อยกเว้นที่เหมาะสมได้

หากคุณต้องการข้อมูลเพิ่มเติมเพื่อผ่านไปพร้อมกับข้อยกเว้นที่จำลองขึ้นของคุณคุณสามารถใช้ stdout ของ subshells ได้ แต่นั่นค่อนข้างซับซ้อนและอาจเป็นคำถามอีกข้อหนึ่ง ;-)

การใช้-eแฟล็กที่กล่าวถึงข้างบนกับเชลล์คุณยังสามารถตัดexitคำสั่งที่ชัดเจนได้

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

1
นี่ควรเป็นคำตอบที่ได้รับการยอมรับเพราะใกล้เคียงกับลอง / จับตรรกะมากที่สุดเท่าที่คุณจะทำได้ด้วยเชลล์
เทรนต์

13

ตามที่ทุกคนพูดว่าทุบตีไม่มีไวยากรณ์ลอง / จับที่รองรับภาษาที่เหมาะสม คุณสามารถเปิดใช้งาน bash ด้วย-eอาร์กิวเมนต์หรือใช้set -eภายในสคริปต์เพื่อยกเลิกกระบวนการ bash ทั้งหมดหากคำสั่งใด ๆ มีรหัสออกที่ไม่ใช่ศูนย์ (คุณสามารถset +eอนุญาตคำสั่งที่ล้มเหลวได้ชั่วคราว)

ดังนั้นหนึ่งเทคนิคในการจำลอง try / catch block คือการเรียกใช้กระบวนการย่อยเพื่อทำงานกับการ-eเปิดใช้งาน จากนั้นในกระบวนการหลักให้ตรวจสอบรหัสส่งคืนของกระบวนการย่อย

Bash รองรับสตริง heredoc ดังนั้นคุณไม่ต้องเขียนไฟล์สองไฟล์แยกกันเพื่อจัดการสิ่งนี้ ในตัวอย่างด้านล่าง heryoc TRY จะทำงานในอินสแตนซ์ bash ที่แยกต่างหากโดย-eเปิดใช้งานดังนั้นกระบวนการย่อยจะล้มเหลวหากคำสั่งใด ๆ ส่งคืนโค้ดออกที่ไม่ใช่ศูนย์ จากนั้นย้อนกลับไปในกระบวนการหลักเราสามารถตรวจสอบรหัสส่งคืนเพื่อจัดการ catch block

#!/bin/bash

set +e
bash -e <<TRY
  echo hello
  cd /does/not/exist
  echo world
TRY
if [ $? -ne 0 ]; then
  echo caught exception
fi

ไม่ใช่บล็อกลอง / catch ที่รองรับภาษา แต่อาจทำให้เป็นรอยคล้าย ๆ กับคุณ



4

และคุณมีกับดักhttp://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.htmlซึ่งไม่เหมือนกัน แต่เทคนิคอื่น ๆ ที่คุณสามารถใช้เพื่อจุดประสงค์นี้


สัญญาณมีความเกี่ยวข้องโดยเฉพาะกับเธรดที่บางมากกับแนวคิดของข้อยกเว้นและลอง / จับเนื่องจากไม่ได้เป็นส่วนหนึ่งของโฟลว์การควบคุมปกติของโปรแกรม แต่มันก็โอเคที่จะพูดถึงที่นี่
Alfe

0

สิ่งที่ฉันใช้ง่ายมาก:

try() {
    "$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}

1
เนื่องจากด้านขวามือของ||อยู่ใน()มันจะทำงานใน subshell และออกโดยไม่ทำให้เปลือกหลักเพื่อออก ใช้{ }การจัดกลุ่มแทน
codeforester

0

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

ChangeAlias old_alias [new_alias]

สคริปต์ที่สมบูรณ์ได้รับด้านล่าง

common.GetAlias() {
    local "oldname=${1:-0}"
    if [[ $oldname =~ ^[0-9]+$ && oldname+1 -lt ${#FUNCNAME[@]} ]]; then
        oldname="${FUNCNAME[oldname + 1]}"
    fi
    name="common_${oldname#common.}"
    echo "${!name:-$oldname}"
}

common.Alias() {
    if [[ $# -ne 2 || -z $1 || -z $2 ]]; then
        echo "$(common.GetAlias): The must be only two parameters of nonzero length" >&2
        return 1;
    fi
    eval "alias $1='$2'"
    local "f=${2##*common.}"
    f="${f%%;*}"
    local "v=common_$f"
    f="common.$f"
    if [[ -n ${!v:-} ]]; then
        echo "$(common.GetAlias): $1: Function \`$f' already paired with name \`${!v}'" >&2
        return 1;
    fi
    shopt -s expand_aliases
    eval "$v=\"$1\""
}

common.ChangeAlias() {
    if [[ $# -lt 1 || $# -gt 2 ]]; then
        echo "usage: $(common.GetAlias) old_name [new_name]" >&2
        return "1"
    elif ! alias "$1" &>"/dev/null"; then
        echo "$(common.GetAlias): $1: Name not found" >&2
        return 1;
    fi
    local "s=$(alias "$1")" 
    s="${s#alias $1=\'}"
    s="${s%\'}"
    local "f=${s##*common.}"
    f="${f%%;*}"
    local "v=common_$f"
    f="common.$f"
    if [[ ${!v:-} != "$1" ]]; then
        echo "$(common.GetAlias): $1: Name not paired with a function \`$f'" >&2
        return 1;
    elif [[ $# -gt 1 ]]; then
        eval "alias $2='$s'"
        eval "$v=\"$2\""
    else
        unset "$v"
    fi
    unalias "$1"
}

common.Alias exception             'common.Exception'
common.Alias throw                 'common.Throw'
common.Alias try                   '{ if common.Try; then'
common.Alias yrt                   'common.EchoExitStatus; fi; common.yrT; }'
common.Alias catch                 '{ while common.Catch'
common.Alias hctac                 'common.hctaC -r; done; common.hctaC; }'
common.Alias finally               '{ if common.Finally; then'
common.Alias yllanif               'fi; common.yllaniF; }'
common.Alias caught                'common.Caught'
common.Alias EchoExitStatus        'common.EchoExitStatus'
common.Alias EnableThrowOnError    'common.EnableThrowOnError'
common.Alias DisableThrowOnError   'common.DisableThrowOnError'
common.Alias GetStatus             'common.GetStatus'
common.Alias SetStatus             'common.SetStatus'
common.Alias GetMessage            'common.GetMessage'
common.Alias MessageCount          'common.MessageCount'
common.Alias CopyMessages          'common.CopyMessages'
common.Alias TryCatchFinally       'common.TryCatchFinally'
common.Alias DefaultErrHandler     'common.DefaultErrHandler'
common.Alias DefaultUnhandled      'common.DefaultUnhandled'
common.Alias CallStack             'common.CallStack'
common.Alias ChangeAlias           'common.ChangeAlias'
common.Alias TryCatchFinallyAlias  'common.Alias'

common.CallStack() {
    local -i "i" "j" "k" "subshell=${2:-0}" "wi" "wl" "wn"
    local "format= %*s  %*s  %-*s  %s\n" "name"
    eval local "lineno=('' ${BASH_LINENO[@]})"
    for (( i=${1:-0},j=wi=wl=wn=0; i<${#FUNCNAME[@]}; ++i,++j )); do  
        name="$(common.GetAlias "$i")"
        let "wi = ${#j} > wi ? wi = ${#j} : wi"
        let "wl = ${#lineno[i]} > wl ? wl = ${#lineno[i]} : wl"
        let "wn = ${#name} > wn ? wn = ${#name} : wn"
    done
    for (( i=${1:-0},j=0; i<${#FUNCNAME[@]}; ++i,++j )); do
        ! let "k = ${#FUNCNAME[@]} - i - 1"
        name="$(common.GetAlias "$i")"
        printf "$format" "$wi" "$j" "$wl" "${lineno[i]}" "$wn" "$name" "${BASH_SOURCE[i]}"
    done
}

common.Echo() {
    [[ $common_options != *d* ]] || echo "$@" >"$common_file"
}

common.DefaultErrHandler() {
    echo "Orginal Status: $common_status"
    echo "Exception Type: ERR"
}

common.Exception() {
    common.TryCatchFinallyVerify || return
    if [[ $# -eq 0 ]]; then
        echo "$(common.GetAlias): At least one parameter is required" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    let "common_status = 10#$1"
    shift
    common_messages=()
    for message in "$@"; do
        common_messages+=("$message")
    done
    if [[ $common_options == *c* ]]; then
        echo "Call Stack:" >"$common_fifo"
        common.CallStack "2" >"$common_fifo"
    fi
}

common.Throw() {
    common.TryCatchFinallyVerify || return
    local "message"
    if ! common.TryCatchFinallyExists; then
        echo "$(common.GetAlias): No Try-Catch-Finally exists" >&2
        return "1"        
    elif [[ $# -eq 0 && common_status -eq 0 ]]; then
        echo "$(common.GetAlias): No previous unhandled exception" >&2 
        return "1"
    elif [[ $# -gt 0 && ( ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ) ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    common.Echo -n "In Throw ?=$common_status "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL #=$#"
    if [[ $common_options == *k* ]]; then
        common.CallStack "2" >"$common_file"
    fi
    if [[ $# -gt 0 ]]; then
        let "common_status = 10#$1"
        shift
        for message in "$@"; do
            echo "$message" >"$common_fifo"
        done
        if [[ $common_options == *c* ]]; then
            echo "Call Stack:" >"$common_fifo"
            common.CallStack "2" >"$common_fifo"
        fi
    elif [[ ${#common_messages[@]} -gt 0 ]]; then
        for message in "${common_messages[@]}"; do
            echo "$message" >"$common_fifo"
        done
    fi
    chmod "0400" "$common_fifo"
    common.Echo "Still in Throw $=$common_status subshell=$BASH_SUBSHELL #=$# -=$-"
    exit "$common_status"
}

common.ErrHandler() {
    common_status=$?
    trap ERR
    common.Echo -n "In ErrHandler ?=$common_status debug=$common_options "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL order=$common_order"
    if [[ -w "$common_fifo" ]]; then
        if [[ $common_options != *e* ]]; then
            common.Echo "ErrHandler is ignoring"
            common_status="0"
            return "$common_status" # value is ignored
        fi
        if [[ $common_options == *k* ]]; then
            common.CallStack "2" >"$common_file"
        fi
        common.Echo "Calling ${common_errHandler:-}"
        eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
        if [[ $common_options == *c* ]]; then
            echo "Call Stack:" >"$common_fifo"
            common.CallStack "2" >"$common_fifo"
        fi
        chmod "0400" "$common_fifo"
    fi
    common.Echo "Still in ErrHandler $=$common_status subshell=$BASH_SUBSHELL -=$-"
    if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
        return "$common_status" # value is ignored   
    else
        exit "$common_status"
    fi
}

common.Token() {
    local "name"
    case $1 in
    b) name="before";;
    t) name="$common_Try";;
    y) name="$common_yrT";;
    c) name="$common_Catch";;
    h) name="$common_hctaC";;
    f) name="$common_yllaniF";;
    l) name="$common_Finally";;
    *) name="unknown";;
    esac
    echo "$name"
}

common.TryCatchFinallyNext() {
    common.ShellInit
    local "previous=$common_order" "errmsg"
    common_order="$2"
    if [[ $previous != $1 ]]; then
        errmsg="${BASH_SOURCE[2]}: line ${BASH_LINENO[1]}: syntax error_near unexpected token \`$(common.Token "$2")'"
        echo "$errmsg" >&2
        [[ /dev/fd/2 -ef $common_file ]] || echo "$errmsg" >"$common_file"
        kill -s INT 0
        return "1"        
    fi
}

common.ShellInit() {
    if [[ common_initSubshell -ne BASH_SUBSHELL ]]; then
        common_initSubshell="$BASH_SUBSHELL"
        common_order="b"
    fi
}

common.Try() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[byhl]" "t" || return 
    common_status="0"
    common_subshell="$common_trySubshell"
    common_trySubshell="$BASH_SUBSHELL"
    common_messages=()
    common.Echo "-------------> Setting try=$common_trySubshell at subshell=$BASH_SUBSHELL"
}

common.yrT() {
    local "status=$?"
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[t]" "y" || return 
    common.Echo -n "Entered yrT ?=$status status=$common_status "
    common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL"
    if [[ common_status -ne 0 ]]; then    

        common.Echo "Build message array. ?=$common_status, subshell=$BASH_SUBSHELL"
        local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
        chmod "0600" "$common_fifo"
        echo "$eof" >"$common_fifo"
        common_messages=()
        while read "message"; do

            common.Echo "----> $message"

            [[ $message != *$eof ]] || break
            common_messages+=("$message")
        done <"$common_fifo"
    fi

    common.Echo "In ytT status=$common_status"
    common_trySubshell="$common_subshell"
}

common.Catch() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[yh]" "c" || return 
    [[ common_status -ne 0 ]] || return "1"
    local "parameter" "pattern" "value"
    local "toggle=true" "compare=p" "options=$-"
    local -i "i=-1" "status=0"
    set -f
    for parameter in "$@"; do
        if "$toggle"; then
            toggle="false"
            if [[ $parameter =~ ^-[notepr]$ ]]; then
                compare="${parameter#-}"
                continue 
            fi
        fi
        toggle="true"
        while "true"; do
            eval local "patterns=($parameter)"
            if [[ ${#patterns[@]} -gt 0 ]]; then
                for pattern in "${patterns[@]}"; do
                    [[ i -lt ${#common_messages[@]} ]] || break
                    if [[ i -lt 0 ]]; then
                        value="$common_status"
                    else
                        value="${common_messages[i]}"
                    fi
                    case $compare in
                    [ne]) [[ ! $value == "$pattern" ]] || break 2;;
                    [op]) [[ ! $value == $pattern ]] || break 2;;
                    [tr]) [[ ! $value =~ $pattern ]] || break 2;;
                    esac
                done
            fi
            if [[ $compare == [not] ]]; then
                let "++i,1"
                continue 2
            else
                status="1"
                break 2
            fi
        done
        if [[ $compare == [not] ]]; then
            status="1"
            break
        else
            let "++i,1"
        fi
    done
    [[ $options == *f* ]] || set +f
    return "$status"
} 

common.hctaC() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[c]" "h" || return 
    [[ $# -ne 1 || $1 != -r ]] || common_status="0"
}

common.Finally() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[ych]" "f" || return 
}

common.yllaniF() {
    common.TryCatchFinallyVerify || return
    common.TryCatchFinallyNext "[f]" "l" || return 
    [[ common_status -eq 0 ]] || common.Throw
}

common.Caught() {
    common.TryCatchFinallyVerify || return
    [[ common_status -eq 0 ]] || return 1
}

common.EchoExitStatus() {
    return "${1:-$?}"
}

common.EnableThrowOnError() {
    common.TryCatchFinallyVerify || return
    [[ $common_options == *e* ]] || common_options+="e"
}

common.DisableThrowOnError() {
    common.TryCatchFinallyVerify || return
    common_options="${common_options/e}"
}

common.GetStatus() {
    common.TryCatchFinallyVerify || return
    echo "$common_status"
}

common.SetStatus() {
    common.TryCatchFinallyVerify || return
    if [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
        echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
        return "1"
    fi
    let "common_status = 10#$1"
}

common.GetMessage() {
    common.TryCatchFinallyVerify || return
    local "upper=${#common_messages[@]}"
    if [[ upper -eq 0 ]]; then
        echo "$(common.GetAlias): $1: There are no messages" >&2
        return "1"
    elif [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -ge upper ]]; then
        echo "$(common.GetAlias): $1: First parameter was an invalid index" >&2
        return "1"
    fi
    echo "${common_messages[$1]}"
}

common.MessageCount() {
    common.TryCatchFinallyVerify || return
    echo "${#common_messages[@]}"
}

common.CopyMessages() {
    common.TryCatchFinallyVerify || return
    if [[ $# -ne 1 ]]; then
        echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
        return "1"         
    elif [[ ${#common_messages} -gt 0 ]]; then
        eval "$1=(\"\${common_messages[@]}\")"
    else
        eval "$1=()"
    fi
}

common.TryCatchFinallyExists() {
    [[ ${common_fifo:-u} != u ]]
}

common.TryCatchFinallyVerify() {
    local "name"
    if ! common.TryCatchFinallyExists; then
        echo "$(common.GetAlias "1"): No Try-Catch-Finally exists" >&2
        return "2"        
    fi
}

common.GetOptions() {
    local "opt"
    local "name=$(common.GetAlias "1")"
    if common.TryCatchFinallyExists; then
        echo "$name: A Try-Catch-Finally already exists" >&2
        return "1"        
    fi
    let "OPTIND = 1"
    let "OPTERR = 0"
    while getopts ":cdeh:ko:u:v:" opt "$@"; do
        case $opt in
        c)  [[ $common_options == *c* ]] || common_options+="c";;
        d)  [[ $common_options == *d* ]] || common_options+="d";;
        e)  [[ $common_options == *e* ]] || common_options+="e";;
        h)  common_errHandler="$OPTARG";;
        k)  [[ $common_options == *k* ]] || common_options+="k";;
        o)  common_file="$OPTARG";;
        u)  common_unhandled="$OPTARG";;
        v)  common_command="$OPTARG";;
        \?) #echo "Invalid option: -$OPTARG" >&2
            echo "$name: Illegal option: $OPTARG" >&2
            return "1";;
        :)  echo "$name: Option requires an argument: $OPTARG" >&2
            return "1";;
        *)  echo "$name: An error occurred while parsing options." >&2
            return "1";;
        esac
    done

    shift "$((OPTIND - 1))"
    if [[ $# -lt 1 ]]; then
        echo "$name: The fifo_file parameter is missing" >&2
        return "1"
    fi
    common_fifo="$1"
    if [[ ! -p $common_fifo ]]; then
        echo "$name: $1: The fifo_file is not an open FIFO" >&2
        return "1"  
    fi

    shift
    if [[ $# -lt 1 ]]; then
        echo "$name: The function parameter is missing" >&2
        return "1"
    fi
    common_function="$1"
    if ! chmod "0600" "$common_fifo"; then
        echo "$name: $common_fifo: Can not change file mode to 0600" >&2
        return "1"
    fi

    local "message=" "eof=TRY_CATCH_FINALLY_END_OF_FILE_$RANDOM"
    { echo "$eof" >"$common_fifo"; } 2>"/dev/null"
    if [[ $? -ne 0 ]]; then
        echo "$name: $common_fifo: Can not write" >&2
        return "1"
    fi   
    { while [[ $message != *$eof ]]; do
        read "message"
    done <"$common_fifo"; } 2>"/dev/null"
    if [[ $? -ne 0 ]]; then
        echo "$name: $common_fifo: Can not read" >&2
        return "1"
    fi   

    return "0"
}

common.DefaultUnhandled() {
    local -i "i"
    echo "-------------------------------------------------"
    echo "$(common.GetAlias "common.TryCatchFinally"): Unhandeled exception occurred"
    echo "Status: $(GetStatus)"
    echo "Messages:"
    for ((i=0; i<$(MessageCount); i++)); do
        echo "$(GetMessage "$i")"
    done
    echo "-------------------------------------------------"
}

common.TryCatchFinally() {
    local "common_file=/dev/fd/2"
    local "common_errHandler=common.DefaultErrHandler"
    local "common_unhandled=common.DefaultUnhandled"
    local "common_options="
    local "common_fifo="
    local "common_function="
    local "common_flags=$-"
    local "common_trySubshell=-1"
    local "common_initSubshell=-1"
    local "common_subshell"
    local "common_status=0"
    local "common_order=b"
    local "common_command="
    local "common_messages=()"
    local "common_handler=$(trap -p ERR)"
    [[ -n $common_handler ]] || common_handler="trap ERR"

    common.GetOptions "$@" || return "$?"
    shift "$((OPTIND + 1))"

    [[ -z $common_command ]] || common_command+="=$"
    common_command+='("$common_function" "$@")'

    set -E
    set +e
    trap "common.ErrHandler" ERR
    if true; then
        common.Try 
        eval "$common_command"
        common.EchoExitStatus
        common.yrT
    fi
    while common.Catch; do
        "$common_unhandled" >&2
        break
        common.hctaC -r
    done
    common.hctaC
    [[ $common_flags == *E* ]] || set +E
    [[ $common_flags != *e* ]] || set -e
    [[ $common_flags != *f* || $- == *f* ]] || set -f
    [[ $common_flags == *f* || $- != *f* ]] || set +f
    eval "$common_handler"
    return "$((common_status?2:0))"
}

0

ด้านล่างเป็นตัวอย่างของสคริปต์ที่ใช้try/catch/finallyในการทุบตี

เช่นเดียวกับคำตอบอื่น ๆ สำหรับคำถามนี้จะต้องตรวจจับข้อยกเว้นหลังจากออกจากกระบวนการย่อย

ตัวอย่างสคริปต์เริ่มต้นด้วยการสร้าง fifo แบบไม่ระบุชื่อซึ่งใช้เพื่อส่งข้อความสตริงจากcommand exceptionหรือthrowไปยังจุดสิ้นสุดของtryบล็อกที่ใกล้เคียงที่สุด ที่นี่ข้อความจะถูกลบออกจาก Fifo และวางไว้ในตัวแปรอาร์เรย์ สถานะจะถูกส่งกลับผ่านreturnและexitคำสั่งและวางไว้ในตัวแปรที่แตกต่างกัน ในการเข้าสู่catchบล็อกสถานะนี้ต้องไม่เป็นศูนย์ ข้อกำหนดอื่น ๆ เพื่อเข้าสู่catchบล็อกจะถูกส่งเป็นพารามิเตอร์ หากถึงจุดสิ้นสุดของcatchบล็อกแล้วสถานะจะถูกตั้งค่าเป็นศูนย์ หากถึงจุดสิ้นสุดของfinallyบล็อกและสถานะยังคงเป็นศูนย์ดังนั้นการโยนโดยนัยที่มีข้อความและสถานะจะถูกดำเนินการ สคริปต์ต้องการการเรียกใช้ฟังก์ชันtrycatchfinallyที่มีตัวจัดการข้อยกเว้นที่ไม่สามารถจัดการได้

ไวยากรณ์สำหรับtrycatchfinallyคำสั่งได้รับด้านล่าง

trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function

-cตัวเลือกเพิ่มสาย stack กับข้อความข้อยกเว้น ตัวเลือกที่ช่วยให้การแก้ปัญหาส่งออก ตัวเลือกที่ช่วยให้ข้อยกเว้นคำสั่ง ตัวเลือกที่ช่วยให้ผู้ใช้เพื่อทดแทนการจัดการข้อยกเว้นคำสั่งของตัวเอง ตัวเลือกเพิ่มสแต็คการเรียกร้องให้แก้ปัญหาการส่งออก ตัวเลือกแทนที่ไฟล์ที่ส่งออกเริ่มต้นซึ่งเป็น ตัวเลือกที่ช่วยให้ผู้ใช้เพื่อทดแทนของตัวเองที่ไม่สามารถจัดการจัดการข้อยกเว้นของพวกเขา ตัวเลือกที่ช่วยให้ผู้ใช้สามารถเลือกที่จะส่งค่ากลับมาแม้ว่าการใช้คำสั่งเปลี่ยนตัว เป็นชื่อไฟล์ของ FIFO ฟังก์ชั่นถูกเรียกโดยเป็นกระบวนการย่อย
-d
-e
-h
-k
-o/dev/fd/2
-u
-v
fifo
functiontrycatchfinally

หมายเหตุ: cdkoตัวเลือกจะถูกลบออกเพื่อทำให้สคริปต์ง่ายขึ้น

ไวยากรณ์สำหรับcatchคำสั่งได้รับด้านล่าง

catch [[-enoprt] list ...] ...

ตัวเลือกมีการกำหนดไว้ด้านล่าง ค่าสำหรับรายการแรกคือสถานะ ค่า Subsquent เป็นข้อความ หากมีข้อความมากกว่ารายการแสดงว่าข้อความที่เหลือจะถูกละเว้น

-eหมายถึง[[ $value == "$string" ]](ค่าต้องจับคู่อย่างน้อยหนึ่งสตริงในรายการ)
-nหมายถึง[[ $value != "$string" ]](ค่าไม่สามารถจับคู่สตริงใด ๆ ในรายการ)
-oหมายถึง[[ $value != $pattern ]](ค่าไม่สามารถจับคู่รูปแบบใด ๆ ในรายการ)
-pหมายถึง[[ $value == $pattern ]](ค่ามี จับคู่รูปแบบในรายการอย่างน้อยหนึ่งรายการ)
-rหมายถึง[[ $value =~ $regex ]](ค่าต้องตรงกับนิพจน์ปกติที่ขยายอย่างน้อยหนึ่งรายการ)
-tหมายถึง[[ ! $value =~ $regex ]](ค่าไม่สามารถจับคู่นิพจน์ทั่วไปที่ขยายในรายการใด ๆ )

try/catch/finallyสคริปต์ได้รับด้านล่าง เพื่อให้สคริปต์ง่ายขึ้นสำหรับคำตอบนี้การตรวจสอบข้อผิดพลาดส่วนใหญ่จึงถูกลบ ทำให้ขนาดลดลง 64% สำเนาที่สมบูรณ์ของสคริปต์นี้สามารถพบได้ที่ฉันคำตอบอื่น

shopt -s expand_aliases
alias try='{ common.Try'
alias yrt='EchoExitStatus; common.yrT; }'
alias catch='{ while common.Catch'
alias hctac='common.hctaC; done; }'
alias finally='{ common.Finally'
alias yllanif='common.yllaniF; }'

DefaultErrHandler() {
    echo "Orginal Status: $common_status"
    echo "Exception Type: ERR"
}

exception() {
    let "common_status = 10#$1"
    shift
    common_messages=()
    for message in "$@"; do
        common_messages+=("$message")
    done
}

throw() {
    local "message"
    if [[ $# -gt 0 ]]; then
        let "common_status = 10#$1"
        shift
        for message in "$@"; do
            echo "$message" >"$common_fifo"
        done
    elif [[ ${#common_messages[@]} -gt 0 ]]; then
        for message in "${common_messages[@]}"; do
            echo "$message" >"$common_fifo"
        done
    fi
    chmod "0400" "$common_fifo"
    exit "$common_status"
}

common.ErrHandler() {
    common_status=$?
    trap ERR
    if [[ -w "$common_fifo" ]]; then
        if [[ $common_options != *e* ]]; then
            common_status="0"
            return
        fi
        eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
        chmod "0400" "$common_fifo"
    fi
    if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
        return   
    else
        exit "$common_status"
    fi
}

common.Try() {
    common_status="0"
    common_subshell="$common_trySubshell"
    common_trySubshell="$BASH_SUBSHELL"
    common_messages=()
}

common.yrT() {
    local "status=$?"
    if [[ common_status -ne 0 ]]; then    
        local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
        chmod "0600" "$common_fifo"
        echo "$eof" >"$common_fifo"
        common_messages=()
        while read "message"; do
            [[ $message != *$eof ]] || break
            common_messages+=("$message")
        done <"$common_fifo"
    fi
    common_trySubshell="$common_subshell"
}

common.Catch() {
    [[ common_status -ne 0 ]] || return "1"
    local "parameter" "pattern" "value"
    local "toggle=true" "compare=p" "options=$-"
    local -i "i=-1" "status=0"
    set -f
    for parameter in "$@"; do
        if "$toggle"; then
            toggle="false"
            if [[ $parameter =~ ^-[notepr]$ ]]; then
                compare="${parameter#-}"
                continue 
            fi
        fi
        toggle="true"
        while "true"; do
            eval local "patterns=($parameter)"
            if [[ ${#patterns[@]} -gt 0 ]]; then
                for pattern in "${patterns[@]}"; do
                    [[ i -lt ${#common_messages[@]} ]] || break
                    if [[ i -lt 0 ]]; then
                        value="$common_status"
                    else
                        value="${common_messages[i]}"
                    fi
                    case $compare in
                    [ne]) [[ ! $value == "$pattern" ]] || break 2;;
                    [op]) [[ ! $value == $pattern ]] || break 2;;
                    [tr]) [[ ! $value =~ $pattern ]] || break 2;;
                    esac
                done
            fi
            if [[ $compare == [not] ]]; then
                let "++i,1"
                continue 2
            else
                status="1"
                break 2
            fi
        done
        if [[ $compare == [not] ]]; then
            status="1"
            break
        else
            let "++i,1"
        fi
    done
    [[ $options == *f* ]] || set +f
    return "$status"
} 

common.hctaC() {
    common_status="0"
}

common.Finally() {
    :
}

common.yllaniF() {
    [[ common_status -eq 0 ]] || throw
}

caught() {
    [[ common_status -eq 0 ]] || return 1
}

EchoExitStatus() {
    return "${1:-$?}"
}

EnableThrowOnError() {
    [[ $common_options == *e* ]] || common_options+="e"
}

DisableThrowOnError() {
    common_options="${common_options/e}"
}

GetStatus() {
    echo "$common_status"
}

SetStatus() {
    let "common_status = 10#$1"
}

GetMessage() {
    echo "${common_messages[$1]}"
}

MessageCount() {
    echo "${#common_messages[@]}"
}

CopyMessages() {
    if [[ ${#common_messages} -gt 0 ]]; then
        eval "$1=(\"\${common_messages[@]}\")"
    else
        eval "$1=()"
    fi
}

common.GetOptions() {
    local "opt"
    let "OPTIND = 1"
    let "OPTERR = 0"
    while getopts ":cdeh:ko:u:v:" opt "$@"; do
        case $opt in
        e)  [[ $common_options == *e* ]] || common_options+="e";;
        h)  common_errHandler="$OPTARG";;
        u)  common_unhandled="$OPTARG";;
        v)  common_command="$OPTARG";;
        esac
    done
    shift "$((OPTIND - 1))"
    common_fifo="$1"
    shift
    common_function="$1"
    chmod "0600" "$common_fifo"
}

DefaultUnhandled() {
    local -i "i"
    echo "-------------------------------------------------"
    echo "TryCatchFinally: Unhandeled exception occurred"
    echo "Status: $(GetStatus)"
    echo "Messages:"
    for ((i=0; i<$(MessageCount); i++)); do
        echo "$(GetMessage "$i")"
    done
    echo "-------------------------------------------------"
}

TryCatchFinally() {
    local "common_errHandler=DefaultErrHandler"
    local "common_unhandled=DefaultUnhandled"
    local "common_options="
    local "common_fifo="
    local "common_function="
    local "common_flags=$-"
    local "common_trySubshell=-1"
    local "common_subshell"
    local "common_status=0"
    local "common_command="
    local "common_messages=()"
    local "common_handler=$(trap -p ERR)"
    [[ -n $common_handler ]] || common_handler="trap ERR"
    common.GetOptions "$@"
    shift "$((OPTIND + 1))"
    [[ -z $common_command ]] || common_command+="=$"
    common_command+='("$common_function" "$@")'
    set -E
    set +e
    trap "common.ErrHandler" ERR
    try
        eval "$common_command"
    yrt
    catch; do
        "$common_unhandled" >&2
    hctac
    [[ $common_flags == *E* ]] || set +E
    [[ $common_flags != *e* ]] || set -e
    [[ $common_flags != *f* || $- == *f* ]] || set -f
    [[ $common_flags == *f* || $- != *f* ]] || set +f
    eval "$common_handler"
}

simpleด้านล่างนี้เป็นตัวอย่างซึ่งถือว่าสคริปต์ดังกล่าวข้างต้นจะถูกเก็บไว้ในไฟล์ที่ชื่อว่า makefifoไฟล์ประกอบด้วยสคริปต์ที่อธิบายไว้ในคำตอบนี้ มีการสันนิษฐานว่าไฟล์ชื่อ4444kkkkkไม่มีอยู่จึงทำให้เกิดข้อยกเว้นเกิดขึ้น ข้อความแสดงข้อผิดพลาดจากls 4444kkkkkคำสั่งจะถูกระงับโดยอัตโนมัติจนกว่าจะอยู่ในcatchบล็อกที่เหมาะสม

#!/bin/bash
#

if [[ $0 != ${BASH_SOURCE[0]} ]]; then
    bash "${BASH_SOURCE[0]}" "$@"
    return
fi

source simple
source makefifo

MyFunction3() {
    echo "entered MyFunction3" >&4
    echo "This is from MyFunction3"
    ls 4444kkkkk
    echo "leaving MyFunction3" >&4
}

MyFunction2() {
    echo "entered MyFunction2" >&4
    value="$(MyFunction3)"
    echo "leaving MyFunction2" >&4
}

MyFunction1() {
    echo "entered MyFunction1" >&4
    local "flag=false"
    try 
    (
        echo "start of try" >&4
        MyFunction2
        echo "end of try" >&4
    )
    yrt
    catch "[1-3]" "*" "Exception\ Type:\ ERR"; do
        echo 'start of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
        local -i "i"
        echo "-------------------------------------------------"
        echo "Status: $(GetStatus)"
        echo "Messages:"
        for ((i=0; i<$(MessageCount); i++)); do
            echo "$(GetMessage "$i")"
        done
        echo "-------------------------------------------------"
        break
        echo 'end of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
    hctac >&4
    catch "1 3 5" "*" -n "Exception\ Type:\ ERR"; do
        echo 'start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
        echo "-------------------------------------------------"
        echo "Status: $(GetStatus)"
        [[ $(MessageCount) -le 1 ]] || echo "$(GetMessage "1")"
        echo "-------------------------------------------------"
        break
        echo 'end of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
    hctac >&4
    catch; do
        echo 'start of catch' >&4
        echo "failure"
        flag="true"
        echo 'end of catch' >&4
    hctac
    finally
        echo "in finally"
    yllanif >&4
    "$flag" || echo "success"
    echo "leaving MyFunction1" >&4
} 2>&6

ErrHandler() {
    echo "EOF"
    DefaultErrHandler "$@"
    echo "Function: $3"
    while read; do
        [[ $REPLY != *EOF ]] || break
        echo "$REPLY"
    done
}

set -u
echo "starting" >&2
MakeFIFO "6"
TryCatchFinally -e -h ErrHandler -o /dev/fd/4 -v result /dev/fd/6 MyFunction1 4>&2
echo "result=$result"
exec >&6-

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)สคริปต์ดังกล่าวข้างต้นได้รับการทดสอบโดยใช้ เอาต์พุตจากการรันสคริปต์นี้แสดงอยู่ด้านล่าง

starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "[1-3]" "*" "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 1
Messages:
Orginal Status: 1
Exception Type: ERR
Function: MyFunction3
ls: 4444kkkkk: No such file or directory
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure

อีกตัวอย่างที่ใช้ a throwสามารถสร้างได้โดยแทนที่ฟังก์ชั่นMyFunction3ด้วยสคริปต์ที่แสดงด้านล่าง

MyFunction3() {
    echo "entered MyFunction3" >&4
    echo "This is from MyFunction3"
    throw "3" "Orginal Status: 3" "Exception Type: throw"
    echo "leaving MyFunction3" >&4
}

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

throw [status] [message ...]

เอาต์พุตจากการเรียกใช้สคริปต์ที่แก้ไขจะแสดงอยู่ด้านล่าง

starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 3
Exception Type: throw
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.