it-swarm-tr.com

Bash'te işleme hatası

Bash'deki hataları işlemek için en sevdiğiniz yöntem nedir? İnternette bulduğum hatalara en iyi örnek, William Shotts, Jr tarafından http://www.linuxcommand.org yazılmıştır.

Bash'de hata işleme için aşağıdaki işlevi kullanmanızı önerir:

#!/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."

Bash komut dosyalarında kullandığınız daha iyi bir hata işleme yönteminiz var mı?

215
Noob

Bir tuzak kullanın!

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

... o zaman, geçici bir dosya oluşturduğunuzda:

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

ve $temp_foo çıkışta silinecek ve geçerli satır numarası yazdırılacaktır. (set -e, aynı şekilde size hatalı çıkış davranışı verecektir, ciddi uyarılar olsa da ve kodun öngörülebilirliğini ve taşınabilirliğini zayıflatır).

Sizin için tuzak çağrısı error _ (bu durumda 1'in varsayılan çıkış kodunu kullanır ve mesaj kullanmaz) ya da kendiniz çağırabilir ve açık değerler sağlayabilirsiniz; Örneğin:

error ${LINENO} "the foobar failed" 2

durum 2 ile çıkacak ve açık bir mesaj verecektir.

148
Charles Duffy

Bu iyi bir çözüm. Sadece eklemek istedim

set -e

ilkel bir hata mekanizması olarak. Basit bir komut başarısız olursa, derhal betiğinizi durduracaktır. Bunun varsayılan davranış olması gerektiğine inanıyorum: bu tür hatalar neredeyse her zaman beklenmeyen bir şeyi işaret ettiğinden, aşağıdaki komutları çalıştırmaya devam etmek gerçekten aklı başında değildir.

115
Bruno De Fraine

Bu sayfadaki tüm cevapları okumak beni çok etkiledi.

İşte benim ipucum:

dosya içeriği: 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



Kullanım örneği:
dosya içeriği: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

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

exit 0


[. .____] Çalıştırma:

bash trap-test.sh

Çıktı:

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!


Aşağıdaki ekran görüntüsünden görebileceğiniz gibi, çıktı renkli ve hata mesajı kullanılan dilde geliyor.

enter image description here

73
Luca Borrione

"Set -e" ye eşdeğer bir alternatif

set -o errexit

Bayrağın anlamını "-e" den biraz daha net hale getiriyor.

Rastgele ekleme: bayrağı geçici olarak devre dışı bırakmak ve varsayılana (çıkış kodlarından bağımsız olarak devam eden yürütmeye devam etmek) dönmek için, yalnızca

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

Bu, diğer yanıtlarda belirtilen doğru hata işlemeyi engeller, ancak hızlı ve etkilidir (bash gibi).

22
Ben Scholbrock

Burada sunulan fikirlerden ilham alarak, bash betiğindeki hataları ele almak için bash kazanı projesi içinde okunaklı ve kullanışlı bir yol geliştirdim.

Kütüphaneyi basitçe kullanarak, aşağıdakileri alırsınız (örn. trap ve bazı bash-f 'deki ERR sayesinde _set -e_ kullanıyorsanız:

bash-oo-framework error handling

dene ve yakala ya da throw anahtar sözcüğü gibi hatalarla başa çıkmaya yardımcı olan bazı ekstra özellikler vardır; Artı, eğer terminal destekliyorsa, powerline emojisini tükürür, çıktının bölümlerini büyük okunabilirlik için renklendirir ve istisnaya neden olan yöntemin kod satırı bağlamında altını çizer.

Olumsuz tarafı - taşınabilir değil - kod bash, muhtemelen> = 4 sadece çalışır (ancak bash 3 için biraz çaba göstererek taşınabileceğini hayal ediyorum).

Kod daha iyi işlem için birden fazla dosyaya ayrıldı, ancak geri bildirim fikrinden ilham aldım yukarıdaki cevap Luca Borrione .

Daha fazla okumak veya kaynağa bakmak için GitHub'a bakın:

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

19
niieani

Araması çok kolay bir şeyi tercih ederim. Bu yüzden biraz karmaşık görünen, ancak kullanımı kolay bir şey kullanıyorum. Genelde sadece aşağıdaki kodu kopyalayıp yapıştırıyorum. Bir açıklama kodu takip eder.

#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 ))`):"'

Genellikle error_exit işlevinin yanındaki temizleme işlevine bir çağrı koyarım, ancak bu komut dosyasından komut dosyasına değişir, bu yüzden dışarıda bıraktım. Tuzaklar ortak sonlandırma sinyallerini alır ve her şeyin temizlendiğinden emin olun. Takma ad, asıl sihiri yapan şeydir. Başarısızlık için her şeyi kontrol etmeyi seviyorum. Bu yüzden genel olarak programları "if!" Olarak adlandırıyorum. type deyimi. Satır numarasından 1 çıkarılarak takma ad, hatanın nerede oluştuğunu söyler. Ayrıca, aramak için basit ve hemen hemen aptal kanıtı öldü. Aşağıda bir örnek var (sadece ne arayacaksanız ile değiştirin/bin/false).

#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

Diğer bir husus, döndürülecek çıkış kodudur. Sadece "1" oldukça standarttır, ancak bir avuç bash'ın kendi kendine kullandığı ayrılmış çıkış kodları vardır ve aynı sayfa kullanıcı tanımlı kodların 64- aralığında olması gerektiğini savunur. 113, C/C++ standartlarına uymak için.

mount öğesinin çıkış kodları için kullandığı bit vektörünü de düşünebilirsiniz:

 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- kodları birlikte kullanmak, komut dosyanızın eşzamanlı olarak birden fazla hatayı işaretlemesini sağlar.

6
yukondude

Aşağıdaki tuzak kodunu kullanıyorum, aynı zamanda hataların borulardan ve 'zaman' komutlarından izlenmesini sağlar

#!/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

Kullandım

die() {
        echo $1
        kill $$
}

önce; Sanırım 'çıkış' nedense benim için başarısız oldu. Yine de yukarıdaki varsayılanlar iyi bir fikir gibi görünüyor.

3
pjz

B bir süredir bana iyi hizmet etti. Hata veya uyarı mesajlarını kırmızı, parametre başına bir satır yazdırır ve isteğe bağlı bir çıkış koduna izin verir.

# 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

Bunun sizin için yararlı olup olmayacağından emin değilim, ancak hata kontrolünü (önceki komuttan çıkış kodu) içerecek şekilde eklemek için önerilen işlevlerden bazılarını değiştirdim. Her "kontrol" de, bir hata olarak günlüğe kaydetme amaçları için neyin "mesajı" parametresi olarak geçiyorum.

#!/bin/bash

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

Şimdi aynı betiğin içinde (ya da export -f error_exit kullanıyorsam başka birinde) çağırmak için, işlevin adını yazdım ve şöyle bir ileti parametresi olarak iletdim, şöyle:

#!/bin/bash

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

rm *
error_exit "Unable to delete all files"

Bunu kullanarak bazı otomatik işlemler için gerçekten sağlam bir bash dosyası oluşturabildim ve hata durumunda duracak ve beni haberdar edecek (log.sh yapacak)

2
Nelson Rodriguez

Bu işlev son zamanlarda bana oldukça iyi hizmet ediyor:

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}
}

Çalıştırılacak komutun adına 0 veya son dönüş değeri ekleyerek çağırırsınız, böylece hata değerlerini kontrol etmek zorunda kalmadan komutları zincirleyebilirsiniz. Bununla, bu ifade bloğu:

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

Bu olur:

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>>>

Komutlardan herhangi biri başarısız olursa, hata kodu basitçe bloğun sonuna iletilir. Daha önceki bir komutun başarısız olması durumunda müteakip komutların yürütülmesini istemediğinizde, ancak komut dosyasının hemen çıkmasını da istemiyorsanız (örneğin, bir döngü içinde) faydalı buluyorum.

1
xarxziux

Bu numara eksik komutlar veya işlevler için kullanışlıdır. Eksik işlevin (veya çalıştırılabilir) adı $ _

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

Tuzak kullanmak her zaman bir seçenek değildir. Örneğin, hata işlemesi gerektiren ve herhangi bir komut dosyasından çağrılabilen (dosyayı yardımcı işlevlerle sağladıktan sonra) bir çeşit yeniden kullanılabilir işlev yazıyorsanız, bu işlev dış komut dosyasının çıkış zamanı hakkında bir şey üstlenemez. bu tuzakları kullanmayı çok zorlaştırıyor. Tuzakları kullanmanın diğer bir dezavantajı, daha önce arayanlar zincirinde kurulabilecek tuzakların üzerine yazma riskini taşıdığınız için kötü uyuşmazlıktır.

Tuzaklar olmadan uygun hata işleme yapmak için kullanılabilecek küçük bir hile var. Zaten başka cevaplardan da bildiğiniz gibi, set -e komutları, alt kabukta çalıştırsanız bile, onlardan sonra || operatörünü kullanırsanız, komutların içinde çalışmaz; örneğin bu işe yaramaz:

#!/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

Ancak, temizleme işleminden önce dış işlevden geri dönmeyi önlemek için || operatörü gerekir. İşin püf noktası, iç komutu arka planda çalıştırmak ve ardından hemen beklemek. wait builtin, iç komutun çıkış kodunu döndürür ve şimdi || kullandıktan sonra wait öğesini kullanırsınız, iç işlevi değil, set -e içinde doğru çalışır ikincisi:

#!/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

İşte bu fikir üzerine kurulu genel fonksiyon. local anahtar kelimelerini kaldırırsanız, yani tüm local x=y ile sadece x=y ile değiştirirseniz, POSIX uyumlu tüm kabuklarda çalışması gerekir:

# [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
}

Kullanım örneği:

#!/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]"

Örnek çalıştırılıyor:

$ ./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

Bu yöntemi kullanırken bilmeniz gereken tek şey, run komutuna geçirdiğiniz komuttan yapılan tüm Shell değişkenlerinin, komutun bir alt kabukta çalıştığından çağıran fonksiyona yayılmamasıdır.

0
skozin