it-swarm-id.com

Kesalahan penanganan di Bash

Apa metode favorit Anda untuk menangani kesalahan di Bash? Contoh terbaik penanganan kesalahan yang saya temukan di web ditulis oleh William Shotts, Jr at http://www.linuxcommand.org .

Dia menyarankan menggunakan fungsi berikut untuk penanganan kesalahan di Bash:

#!/bin/bash

# A slicker error handling routine

# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run.  You can get this
# value from the first item on the command line ($0).

# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>

PROGNAME=$(basename $0)

function error_exit
{

#   ----------------------------------------------------------------
#   Function for exit due to fatal program error
#       Accepts 1 argument:
#           string containing descriptive error message
#   ---------------------------------------------------------------- 

    echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
    exit 1
}

# Example call of the error_exit function.  Note the inclusion
# of the LINENO environment variable.  It contains the current
# line number.

echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."

Apakah Anda memiliki rutinitas penanganan kesalahan yang lebih baik yang Anda gunakan dalam skrip Bash?

215
Noob

Gunakan perangkap!

tempfiles=( )
cleanup() {
  rm -f "${tempfiles[@]}"
}
trap cleanup 0

error() {
  local parent_lineno="$1"
  local message="$2"
  local code="${3:-1}"
  if [[ -n "$message" ]] ; then
    echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
  else
    echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
  fi
  exit "${code}"
}
trap 'error ${LINENO}' ERR

... lalu, setiap kali Anda membuat file sementara:

temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )

dan $temp_foo akan dihapus saat keluar, dan nomor baris saat ini akan dicetak. (set -e juga akan memberi Anda perilaku exit-on-error, meskipun disertai dengan peringatan serius dan melemahkan prediktabilitas dan portabilitas kode.

Anda bisa membiarkan jebakan memanggil error untuk Anda (dalam hal ini ia menggunakan kode keluar default 1 dan tidak ada pesan) atau menyebutnya sendiri dan memberikan nilai eksplisit; misalnya:

error ${LINENO} "the foobar failed" 2

akan keluar dengan status 2, dan memberikan pesan eksplisit.

148
Charles Duffy

Itu solusi yang bagus. Saya hanya ingin menambahkan

set -e

sebagai mekanisme kesalahan yang belum sempurna. Ini akan segera menghentikan skrip Anda jika perintah sederhana gagal. Saya pikir ini seharusnya menjadi perilaku default: karena kesalahan seperti itu hampir selalu menandakan sesuatu yang tidak terduga, itu tidak benar-benar 'waras' untuk terus menjalankan perintah berikut.

115
Bruno De Fraine

Membaca semua jawaban di halaman ini sangat menginspirasi saya.

Jadi, inilah petunjuk saya:

konten file: lib.trap.sh

lib_name='trap'
lib_version=20121026

stderr_log="/dev/shm/stderr.log"

#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

if test "${g_libs[$lib_name]+_}"; then
    return 0
else
    if test ${#g_libs[@]} == 0; then
        declare -A g_libs
    fi
    g_libs[$lib_name]=$lib_version
fi


#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

exec 2>"$stderr_log"


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function exit_handler ()
{
    local error_code="$?"

    test $error_code == 0 && return;

    #
    # LOCAL VARIABLES:
    # ------------------------------------------------------------------
    #    
    local i=0
    local regex=''
    local mem=''

    local error_file=''
    local error_lineno=''
    local error_message='unknown'

    local lineno=''


    #
    # PRINT THE HEADER:
    # ------------------------------------------------------------------
    #
    # Color the output if it's an interactive terminal
    test -t 1 && tput bold; tput setf 4                                 ## red bold
    echo -e "\n(!) EXIT HANDLER:\n"


    #
    # GETTING LAST ERROR OCCURRED:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    #
    # Read last file from the error log
    # ------------------------------------------------------------------
    #
    if test -f "$stderr_log"
        then
            stderr=$( tail -n 1 "$stderr_log" )
            rm "$stderr_log"
    fi

    #
    # Managing the line to extract information:
    # ------------------------------------------------------------------
    #

    if test -n "$stderr"
        then        
            # Exploding stderr on :
            mem="$IFS"
            local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
            IFS=':'
            local stderr_parts=( $shrunk_stderr )
            IFS="$mem"

            # Storing information on the error
            error_file="${stderr_parts[0]}"
            error_lineno="${stderr_parts[1]}"
            error_message=""

            for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
                do
                    error_message="$error_message "${stderr_parts[$i-1]}": "
            done

            # Removing last ':' (colon character)
            error_message="${error_message%:*}"

            # Trim
            error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
    fi

    #
    # GETTING BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
    _backtrace=$( backtrace 2 )


    #
    # MANAGING THE OUTPUT:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    local lineno=""
    regex='^([a-z]{1,}) ([0-9]{1,})$'

    if [[ $error_lineno =~ $regex ]]

        # The error line was found on the log
        # (e.g. type 'ff' without quotes wherever)
        # --------------------------------------------------------------
        then
            local row="${BASH_REMATCH[1]}"
            lineno="${BASH_REMATCH[2]}"

            echo -e "FILE:\t\t${error_file}"
            echo -e "${row^^}:\t\t${lineno}\n"

            echo -e "ERROR CODE:\t${error_code}"             
            test -t 1 && tput setf 6                                    ## white yellow
            echo -e "ERROR MESSAGE:\n$error_message"


        else
            regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
            if [[ "$_backtrace" =~ $regex ]]

                # The file was found on the log but not the error line
                # (could not reproduce this case so far)
                # ------------------------------------------------------
                then
                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    echo -e "ERROR MESSAGE:\n${stderr}"

                # Neither the error line nor the error file was found on the log
                # (e.g. type 'cp ffd fdf' without quotes wherever)
                # ------------------------------------------------------
                else
                    #
                    # The error file is the first on backtrace list:

                    # Exploding backtrace on newlines
                    mem=$IFS
                    IFS='
                    '
                    #
                    # Substring: I keep only the carriage return
                    # (others needed only for tabbing purpose)
                    IFS=${IFS:0:1}
                    local lines=( $_backtrace )

                    IFS=$mem

                    error_file=""

                    if test -n "${lines[1]}"
                        then
                            array=( ${lines[1]} )

                            for (( i=2; i<${#array[@]}; i++ ))
                                do
                                    error_file="$error_file ${array[$i]}"
                            done

                            # Trim
                            error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
                    fi

                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    if test -n "${stderr}"
                        then
                            echo -e "ERROR MESSAGE:\n${stderr}"
                        else
                            echo -e "ERROR MESSAGE:\n${error_message}"
                    fi
            fi
    fi

    #
    # PRINTING THE BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 7                                            ## white bold
    echo -e "\n$_backtrace\n"

    #
    # EXITING:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 4                                            ## red bold
    echo "Exiting!"

    test -t 1 && tput sgr0 # Reset terminal

    exit "$error_code"
}
trap exit_handler EXIT                                                  # ! ! ! TRAP EXIT ! ! !
trap exit ERR                                                           # ! ! ! TRAP ERR ! ! !


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function backtrace
{
    local _start_from_=0

    local params=( "[email protected]" )
    if (( "${#params[@]}" >= "1" ))
        then
            _start_from_="$1"
    fi

    local i=0
    local first=false
    while caller $i > /dev/null
    do
        if test -n "$_start_from_" && (( "$i" + 1   >= "$_start_from_" ))
            then
                if test "$first" == false
                    then
                        echo "BACKTRACE IS:"
                        first=true
                fi
                caller $i
        fi
        let "i=i+1"
    done
}

return 0



Contoh penggunaan:
konten file: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

echo "doing something wrong now .."
echo "$foo"

exit 0


Berlari:

bash trap-test.sh

Keluaran:

doing something wrong now ..

(!) EXIT HANDLER:

FILE:       trap-test.sh
LINE:       6

ERROR CODE: 1
ERROR MESSAGE:
foo:   unassigned variable

BACKTRACE IS:
1 main trap-test.sh

Exiting!


Seperti yang Anda lihat dari tangkapan layar di bawah, outputnya berwarna dan pesan kesalahannya muncul dalam bahasa yang digunakan.

enter image description here

73
Luca Borrione

Alternatif yang setara dengan "set -e" adalah

set -o errexit

Itu membuat arti bendera agak lebih jelas daripada hanya "-e".

Tambahan acak: untuk menonaktifkan sementara flag, dan kembali ke default (melanjutkan eksekusi terlepas dari kode keluar), cukup gunakan

set +e
echo "commands run here returning non-zero exit codes will not cause the entire script to fail"
echo "false returns 1 as an exit code"
false
set -e

Ini mencegah penanganan kesalahan yang tepat yang disebutkan dalam respons lain, tetapi cepat & efektif (seperti halnya bash).

22
Ben Scholbrock

Terinspirasi oleh ide-ide yang disajikan di sini, saya telah mengembangkan cara yang mudah dibaca dan nyaman untuk menangani kesalahan dalam skrip bash di proyek bash boilerplate .

Dengan hanya sumber pustaka, Anda mendapatkan berikut ini dari kotak (yaitu akan menghentikan eksekusi pada setiap kesalahan, seolah-olah menggunakan set -e terima kasih kepada trap pada ERR dan beberapa bash-fu ):

bash-oo-framework error handling

Ada beberapa fitur tambahan yang membantu menangani kesalahan, seperti mencoba dan menangkap , atau lemparan kata kunci, yang memungkinkan Anda untuk menghentikan eksekusi pada suatu titik untuk melihat jejak balik. Plus, jika terminal mendukungnya, ia mengeluarkan emoji powerline, mewarnai bagian-bagian output agar mudah dibaca, dan menggarisbawahi metode yang menyebabkan pengecualian dalam konteks garis kode.

Kelemahannya adalah - ini tidak portabel - kode bekerja di bash, mungkin> = 4 saja (tapi saya bayangkan itu bisa diangkut dengan beberapa upaya untuk bash 3).

Kode dipisahkan menjadi beberapa file untuk penanganan yang lebih baik, tetapi saya terinspirasi oleh ide backtrace dari jawaban di atas oleh Luca Borrione .

Untuk membaca lebih lanjut atau melihat sumbernya, lihat GitHub:

https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw

19
niieani

Saya lebih suka sesuatu yang sangat mudah dipanggil. Jadi saya menggunakan sesuatu yang terlihat sedikit rumit, tetapi mudah digunakan. Saya biasanya hanya menyalin dan menempelkan kode di bawah ini ke dalam skrip saya. Penjelasan mengikuti kode.

#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
    echo
    echo "[email protected]"
    exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM

#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'

Saya biasanya melakukan panggilan ke fungsi pembersihan di samping fungsi error_exit, tetapi ini bervariasi dari satu skrip ke skrip yang lain sehingga saya mengabaikannya. Perangkap menangkap sinyal terminasi umum dan memastikan semuanya dibersihkan. Alias ​​adalah apa yang melakukan sihir yang sebenarnya. Saya suka memeriksa semuanya untuk kegagalan. Jadi secara umum saya memanggil program dalam "jika!" ketikkan pernyataan. Dengan mengurangi 1 dari nomor baris alias akan memberi tahu saya di mana kegagalan terjadi. Itu juga sangat sederhana untuk dipanggil, dan cukup banyak bukti bodoh. Di bawah ini adalah contoh (cukup ganti/bin/false dengan apa pun yang akan Anda panggil).

#This is an example useage, it will print out
#Error prog-name (@1): Who knew false is false.
if ! /bin/false ; then
    die "Who knew false is false."
fi
11
Michael Nooner

Pertimbangan lain adalah kode keluar untuk kembali. Hanya "1" cukup standar, meskipun ada beberapa kode keluar yang disediakan oleh bash sendiri , dan halaman yang sama berpendapat bahwa kode yang ditentukan pengguna harus dalam kisaran 64- 113 agar sesuai dengan standar C/C++.

Anda mungkin juga mempertimbangkan pendekatan vektor bit yang mount gunakan untuk kode keluarnya:

 0  success
 1  incorrect invocation or permissions
 2  system error (out of memory, cannot fork, no more loop devices)
 4  internal mount bug or missing nfs support in mount
 8  user interrupt
16  problems writing or locking /etc/mtab
32  mount failure
64  some mount succeeded

OR- kode bersama memungkinkan skrip Anda memberi sinyal beberapa kesalahan simultan.

6
yukondude

Saya menggunakan kode jebakan berikut, ini juga memungkinkan kesalahan dilacak melalui pipa dan perintah 'waktu'

#!/bin/bash
set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
function error() {
    JOB="$0"              # job name
    LASTLINE="$1"         # line of error occurrence
    LASTERR="$2"          # error code
    echo "ERROR in ${JOB} : line ${LASTLINE} with exit code ${LASTERR}"
    exit 1
}
trap 'error ${LINENO} ${?}' ERR
4
Olivier Delrieu

Saya sudah menggunakan

die() {
        echo $1
        kill $$
}

sebelum; Saya pikir karena 'exit' gagal untuk saya karena beberapa alasan. Default di atas sepertinya ide yang bagus.

3
pjz

Ini telah membantu saya dengan baik untuk sementara waktu sekarang. Ini mencetak pesan kesalahan atau peringatan dengan warna merah, satu baris per parameter, dan memungkinkan kode keluar opsional.

# Custom errors
EX_UNKNOWN=1

warning()
{
    # Output warning messages
    # Color the output red if it's an interactive terminal
    # @param $1...: Messages

    test -t 1 && tput setf 4

    printf '%s\n' "[email protected]" >&2

    test -t 1 && tput sgr0 # Reset terminal
    true
}

error()
{
    # Output error messages with optional exit code
    # @param $1...: Messages
    # @param $N: Exit code (optional)

    messages=( "[email protected]" )

    # If the last parameter is a number, it's not part of the messages
    last_parameter="${messages[@]: -1}"
    if [[ "$last_parameter" =~ ^[0-9]*$ ]]
    then
        exit_code=$last_parameter
        unset messages[$((${#messages[@]} - 1))]
    fi

    warning "${messages[@]}"

    exit ${exit_code:-$EX_UNKNOWN}
}
3
l0b0

Tidak yakin apakah ini akan membantu Anda, tetapi saya memodifikasi beberapa fungsi yang disarankan di sini untuk memasukkan pemeriksaan kesalahan (kode keluar dari perintah sebelumnya) di dalamnya. Pada setiap "periksa", saya juga menyampaikan sebagai "pesan" parameter tentang kesalahan tersebut untuk tujuan pencatatan.

#!/bin/bash

error_exit()
{
    if [ "$?" != "0" ]; then
        log.sh "$1"
        exit 1
    fi
}

Sekarang untuk memanggilnya dalam skrip yang sama (atau yang lain jika saya menggunakan export -f error_exit) Saya cukup menulis nama fungsi dan meneruskan pesan sebagai parameter, seperti ini:

#!/bin/bash

cd /home/myuser/afolder
error_exit "Unable to switch to folder"

rm *
error_exit "Unable to delete all files"

Dengan menggunakan ini saya dapat membuat file bash yang sangat tangguh untuk beberapa proses otomatis dan itu akan berhenti jika terjadi kesalahan dan memberi tahu saya (log.sh akan melakukan itu)

2
Nelson Rodriguez

Fungsi ini baru saja melayani saya dengan cukup baik:

action () {
    # Test if the first parameter is non-zero
    # and return straight away if so
    if test $1 -ne 0
    then
        return $1
    fi

    # Discard the control parameter
    # and execute the rest
    shift 1
    "[email protected]"
    local status=$?

    # Test the exit status of the command run
    # and display an error message on failure
    if test ${status} -ne 0
    then
        echo Command \""[email protected]"\" failed >&2
    fi

    return ${status}
}

Anda menyebutnya dengan menambahkan 0 atau nilai pengembalian terakhir ke nama perintah yang akan dijalankan, sehingga Anda dapat mengaitkan perintah tanpa harus memeriksa nilai kesalahan. Dengan ini, pernyataan ini memblokir:

command1 param1 param2 param3...
command2 param1 param2 param3...
command3 param1 param2 param3...
command4 param1 param2 param3...
command5 param1 param2 param3...
command6 param1 param2 param3...

Menjadi ini:

action 0 command1 param1 param2 param3...
action $? command2 param1 param2 param3...
action $? command3 param1 param2 param3...
action $? command4 param1 param2 param3...
action $? command5 param1 param2 param3...
action $? command6 param1 param2 param3...

<<<Error-handling code here>>>

Jika salah satu perintah gagal, kode kesalahan hanya diteruskan ke akhir blok. Saya menemukan ini berguna ketika Anda tidak ingin menjalankan perintah berikutnya jika yang sebelumnya gagal, tetapi Anda juga tidak ingin skrip keluar langsung (misalnya, di dalam loop).

1
xarxziux

Trik ini berguna untuk perintah atau fungsi yang hilang. Nama fungsi yang hilang (atau dapat dieksekusi) akan diberikan dalam $ _

function handle_error {
    status=$?
    last_call=$1

    # 127 is 'command not found'
    (( status != 127 )) && return

    echo "you tried to call $last_call"
    return
}

# Trap errors.
trap 'handle_error "$_"' ERR
0
Orwellophile

Menggunakan perangkap tidak selalu merupakan opsi. Misalnya, jika Anda sedang menulis semacam fungsi yang dapat digunakan kembali yang membutuhkan penanganan kesalahan dan yang dapat dipanggil dari skrip apa pun (setelah sumber file dengan fungsi pembantu), fungsi itu tidak dapat mengasumsikan apa pun tentang waktu keluar skrip luar, yang membuat menggunakan perangkap sangat sulit. Kerugian lain dari menggunakan jebakan adalah kompabilitas yang buruk, karena Anda berisiko menimpa jebakan sebelumnya yang mungkin ditetapkan sebelumnya dalam rantai penelepon.

Ada sedikit trik yang dapat digunakan untuk melakukan penanganan kesalahan yang tepat tanpa jebakan. Seperti yang mungkin sudah Anda ketahui dari jawaban lain, set -e tidak bekerja di dalam perintah jika Anda menggunakan || operator setelahnya, bahkan jika Anda menjalankannya dalam subkulit; mis., ini tidak akan berfungsi:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer

set -e

outer() {
  echo '--> outer'
  (inner) || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

Tetapi || operator diperlukan untuk mencegah kembali dari fungsi luar sebelum pembersihan. Caranya adalah dengan menjalankan perintah dalam di latar belakang, dan kemudian segera tunggu. wait builtin akan mengembalikan kode keluar dari perintah dalam, dan sekarang Anda menggunakan || setelah wait, bukan fungsi bagian dalam, jadi set -e bekerja dengan baik di dalam yang terakhir:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup

set -e

outer() {
  echo '--> outer'
  inner &
  wait $! || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

Inilah fungsi generik yang dibangun di atas gagasan ini. Ini harus bekerja di semua shell yang kompatibel dengan POSIX jika Anda menghapus kata kunci local, mis. Ganti semua local x=y hanya dengan x=y:

# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
  local cmd="$1"; shift
  local exit_code=0

  local e_was_set=1; if ! is_Shell_attribute_set e; then
    set -e
    e_was_set=0
  fi

  "$cmd" "[email protected]" &

  wait $! || {
    exit_code=$?
  }

  if [ "$e_was_set" = 0 ] && is_Shell_attribute_set e; then
    set +e
  fi

  if [ -n "$CLEANUP" ]; then
    RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "[email protected]"
    return $?
  fi

  return $exit_code
}


is_Shell_attribute_set() { # attribute, like "x"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}

Contoh penggunaan:

#!/bin/sh
set -e

# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh


main() {
  echo "--> main: [email protected]"
  CLEANUP=cleanup run inner "[email protected]"
  echo "<-- main"
}


inner() {
  echo "--> inner: [email protected]"
  sleep 0.5; if [ "$1" = 'fail' ]; then
    oh_my_god_look_at_this
  fi
  echo "<-- inner"
}


cleanup() {
  echo "--> cleanup: [email protected]"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "[email protected]"

Menjalankan contoh:

$ ./so_3 fail; echo "exit code: $?"

--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127

$ ./so_3 pass; echo "exit code: $?"

--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0

Satu-satunya hal yang perlu Anda perhatikan ketika menggunakan metode ini adalah bahwa semua modifikasi variabel Shell yang dilakukan dari perintah yang Anda berikan ke run tidak akan merambat ke fungsi pemanggilan, karena perintah dijalankan dalam subkulit.

0
skozin