Bash'te hata işleme


240

Bash hatalarıyla başa çıkmak için en sevdiğiniz yöntem hangisidir? Web'de bulduğum hataları ele almanın en iyi örneği William Shotts, Jr tarafından http://www.linuxcommand.org adresinde yazılmıştır .

Bash'te 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 betiklerinde kullandığınız daha iyi bir hata işleme rutininiz var mı?


1
Bu ayrıntılı cevaba bakın: Bash betiğinde hatayı yükseltin .
codeforester

1
Günlüğe kaydetme ve hata işleme uygulamasına buradan bakın: github.com/codeforester/base/blob/master/lib/stdlib.sh
codeforester

Yanıtlar:


154

Bir tuzak kullan!

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

... sonra, her geçici 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 -eaynı şekilde, size ciddi uyarılarla birlikte gelir ve kodun öngörülebilirliğini ve taşınabilirliğini zayıflatır , ancak size hatadan çıkış davranışı verir ).

Tuzağın errorsizi aramasına izin verebilirsiniz (bu durumda 1 varsayılan çıkış kodunu kullanır ve ileti yok) veya kendiniz arayabilir ve açık değerler sağlayabilirsiniz; Örneğin:

error ${LINENO} "the foobar failed" 2

durum 2 ile çıkar ve açık bir mesaj verir.


4
@draemon değişken büyük harf kullanımı kasıtlıdır. Tümü büyük harf yalnızca kabuk yerleşikleri ve ortam değişkenleri için gelenekseldir - diğer her şey için küçük harf kullanılması ad alanı çakışmalarını önler. Ayrıca bkz. Stackoverflow.com/questions/673055/…
Charles Duffy

1
tekrar kırmadan önce değişikliğinizi test edin. Kurallar iyi bir şey, ama işleyen koda ikincil.
Draemon

3
@Demon, aslında katılmıyorum. Açıkça bozuk kod fark edilir ve düzeltilir. Kötü uygulamalar ancak çoğunlukla çalışan kodlar sonsuza dek yaşar (ve yayılır).
Charles Duffy

1
ama fark etmedin. Kırık kod fark olsun çünkü kod işleyen birincil endişe kaynağıdır.
Draemon

5
tam olarak ücretsiz değildir ( stackoverflow.com/a/10927223/26334 ) ve kod zaten POSIX ile uyumsuzsa, işlev anahtar sözcüğünü kaldırmak onu POSIX sh altında daha fazla çalıştıramaz, ancak asıl amacım ve (IMO), set -e'yi kullanma önerisini zayıflatarak cevabı devalüe etti. Stackoverflow "sizin" kodunuzla ilgili değil, en iyi cevapları almakla ilgilidir.
Draemon

123

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

set -e

ilkel bir hata mekanizması olarak. Basit bir komut başarısız olursa komut dosyanızı hemen durduracaktır. Bunun varsayılan davranış olması gerektiğini düşünüyorum: Bu tür hatalar neredeyse her zaman beklenmedik bir şey ifade ettiğinden, aşağıdaki komutları yürütmeye devam etmek gerçekten 'aklı başında' değildir.



3
@CharlesDuffy, bazı gotchas ile üstesinden gelinebilirset -o pipefail
hobs

7
@CharlesDuffy Gotchas'ı işaret ettiğiniz için teşekkür ederiz; genel olarak, yine de set -eyüksek bir fayda-maliyet oranına sahip olduğunu düşünüyorum .
Bruno De Fraine

3
@BrunoDeFraine Kendimi kullanıyorum set -e, ancak irc.freenode.org # bash tavsiyesinde (oldukça güçlü terimlerle) birtakım diğer normaller buna karşı. En azından, söz konusu gotchas iyi anlaşılmalıdır.
Charles Duffy

3
-e -o pipefail -u # ayarlayın ve ne yaptığınızı bilin
Sam Watkins

78

Bu sayfadaki tüm cevapları okumak bana çok ilham verdi.

İşte ipucu:

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=( "$@" )
    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


Koşu:

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ünde görebileceğiniz gibi, çıktı renklidir ve hata mesajı kullanılan dilde gelir.

resim açıklamasını buraya girin


3
Bu şey harika .. bunun için bir github projesi oluşturmalısınız, böylece insanlar kolayca iyileştirmeler yapabilir ve geri katkıda bulunabilirler. Log4bash ile birleştirdim ve birlikte iyi bash komut dosyaları oluşturmak için güçlü bir env oluşturur.
Dominik Dorn

1
FYI - test ${#g_libs[@]} == 0POSIX uyumlu değil ( =dize karşılaştırmaları veya -eqsayısal karşılaştırmalar için POSIX testi destekler , ancak ==POSIX'te dizilerin eksikliğinden bahsetmiyorum) ve POSIX uyumlu olmaya çalışmıyorsanız , neden dünyasını testbir matematik bağlamından çok mu kullanıyorsunuz? (( ${#g_libs[@]} == 0 ))sonuçta, okunması daha kolaydır.
Charles Duffy

2
@Luca - bu gerçekten harika! Resminiz bana kendi uygulamamı yaratmam için ilham verdi, bu da birkaç adım daha ileri gidiyor. Aşağıdaki cevabımda yayınladım .
niieani

3
Bravo !! Bu, bir komut dosyasında hata ayıklamanın mükemmel bir yoludur. Grazie mille Eklediğim tek şey OS X için böyle bir kontrol oldu: case "$(uname)" in Darwin ) stderr_log="${TMPDIR}stderr.log";; Linux ) stderr_log="/dev/shm/stderr.log";; * ) stderr_log="/dev/shm/stderr.log" ;; esac
SaxDaddy

1
Biraz utanmaz bir kendi kendine fiş, ancak bu snippet'i aldık, temizledik, daha fazla özellik ekledik, çıktı biçimlendirmesini geliştirdik ve daha POSIX uyumlu hale getirdik (hem Linux hem de OSX'te çalışıyor). Github'da Privex ShellCore'un bir parçası olarak yayınlandı: github.com/Privex/shell-core
Someguy123

22

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

set -o errexit

Bayrağın anlamını "-e" den biraz daha açık hale getirir.

Rastgele ekleme: bayrağı geçici olarak devre dışı bırakmak ve varsayılan değere dönmek için (çıkış kodlarından bağımsız olarak devam eden yürütme),

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 uygun hata işlemeyi engeller, ancak hızlı ve etkilidir (tıpkı bash gibi).


1
$(foo)sadece fooyanlış bir çizgi üzerinde kullanmak genellikle yanlış bir şeydir. Neden örnek vererek onu tanıtmalıyız?
Charles Duffy

20

Burada sunulan fikirlerden esinlenerek, bash boilerplate projemdeki bash komut dosyalarındaki hataları işlemek için okunabilir ve kullanışlı bir yol geliştirdim .

Kitaplığı basitçe sağlayarak aşağıdakileri kutudan çıkarırsınız (yani, set -ebir trapon ERRve bazı bash-fu sayesinde sanki herhangi bir hatada yürütmeyi durduracaktır ):

bash-oo-framework hata işleme

Deneme ve yakalama gibi hatalarla başa çıkmaya yardımcı olan veya throw anahtar sözcüğü gibi, geri izlemeyi görmek için bir noktada yürütmeyi kesmenize olanak tanıyan bazı ekstra özellikler vardır . Ayrıca, terminal destekliyorsa, güç hattı emojisini tükürür, çıktının parçalarını mükemmel okunabilirlik için renklendirir ve kod satırı bağlamında istisnanın neden olduğu yöntemin altını çizer.

Olumsuz - taşınabilir değil - kod bash, muhtemelen> = 4 sadece çalışır (ama bash 3 için biraz çaba ile ported olabilir hayal ediyorum).

Kod, daha iyi kullanım için birden fazla dosyaya ayrılmıştır, ancak Luca Borrione'nin yukarıdaki cevabından geriye dönük fikirden ilham aldım .

Daha fazla okumak veya kaynağa göz atmak için GitHub'a bakın:

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


Bu Bash Nesnesi Odaklı Çerçeve projesi içinde. ... Neyse ki sadece 7.4k LOC ( GLOC'a göre ) var. OOP - Nesneye yönelik ağrı?
ingyhere

@ingyhere son derece modüler (ve silme dostu), bu nedenle istisnalar bölümünü yalnızca sizin için geldiğinizse kullanabilirsiniz;)
niieani

11

Araması gerçekten kolay bir şeyi tercih ederim. Bu yüzden biraz karmaşık görünen ama kullanımı kolay bir şey kullanıyorum. Genellikle aşağıdaki kodu kopyalayıp scriptlerime 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 "$@"
    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 tarafındaki temizleme işlevine bir çağrı koydum, ancak bu komut dosyasından komut dosyasına değişir, bu yüzden onu dışarıda bıraktım. Tuzaklar ortak sonlandırma sinyallerini yakalar ve her şeyin temizlendiğinden emin olur. Takma ad, gerçek büyüyü yapan şeydir. Her şeyi başarısız olup olmadığını kontrol etmeyi seviyorum. Genel olarak programları "if!" tür bildirimi. Satır numarasından 1 çıkartılarak takma ad bana hatanın nerede meydana geldiğini söyleyecektir. Ayrıca aramak basit ve aptal kanıtı. Aşağıda bir örnek bulunmaktadır (/ bin / false yerine arayacağınız her şeyi yazınız).

#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

2
"Açıkça takma adlara izin vermeliyiz" ifadesini genişletebilir misiniz ? Bazı beklenmedik davranışların ortaya çıkmasından endişe ediyorum. Aynı şeyi daha küçük bir etkiyle elde etmenin bir yolu var mı?
blong

İhtiyacım yok $LINENO - 1. Onsuz doğru göster.
kyb

Bash ve zshfalse || die "hello death"
kyb

6

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

mountÇıkış kodları için kullanılan bit vektörü yaklaşımını da 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 betiğinizin birden fazla eşzamanlı hatayı bildirmesini sağlar.


4

Aşağıdaki tuzak kodunu kullanıyorum, ayrıca hataların borular ve 'zaman' komutları ile izlenmesine izin veriyor

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

5
functionAnahtar kelime ivazsız POSIX uyumlu değildir. Sadece deklarasyon yapma düşünün error() {hiçbir ile functionondan önce.
Charles Duffy

5
${$?}sadece olmalı $?veya ${?}gereksiz parantez kullanmakta ısrar ediyorsanız; iç $yanlış.
Charles Duffy

3
@CharlesDuffy şimdiye kadar POSIX, nezaketle GNU / Linux ile uyumlu değil (yine de,
puanını alıyorum

3

Kullandım

die() {
        echo $1
        kill $$
}

önce; Bence 'çıkış' bir sebepten dolayı benim için başarısız oluyordu. Yine de yukarıdaki varsayılanlar iyi bir fikir gibi görünüyor.


Hata mesajı STDERR'a gönderilseniz iyi olur, değil mi?
ankostis

3

Bu bir süredir bana iyi hizmet etti. Hata veya uyarı mesajlarını parametre başına bir satır kırmızı olarak 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' "$@" >&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=( "$@" )

    # 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

Bunun size yardımcı olup olmayacağından emin değilim, ancak burada hata kontrolünü (önceki komuttan çıkış kodu) dahil etmek için burada önerilen işlevlerden bazılarını değiştirdim. Her bir "kontrol" de ben de günlükleme için hatanın ne olduğunu "mesaj" bir parametre olarak geçirir.

#!/bin/bash

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

Şimdi aynı komut dosyası içinde (veya kullanırsam başka bir komut dosyası) çağırmak için export -f error_exitsadece fonksiyonun adını yazıyorum ve parametre olarak bir mesaj iletiyorum:

#!/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 bana log.shbildirecek ( bunu yapacak)


2
İşlevleri tanımlamak için POSIX sözdizimini kullanın function; yalnızca anahtar kelime yok error_exit() {.
Charles Duffy

2
sadece yapmamanın bir nedeni var cd /home/myuser/afolder || error_exit "Unable to switch to folder"mı?
Pierre-Olivier Vares

@ Pierre-OlivierVares || kullanmamanın özel bir nedeni yok. Bu sadece mevcut bir kod bir alıntı oldu ve ben sadece ilgili hata satır sonra "hata işleme" satırları ekledi. Bazıları çok uzun ve ayrı (acil) bir hatta sahip olmak daha
Nelson Rodriguez

Temiz bir çözüm gibi görünüyor, ancak kabuk kontrolü şikayet ediyor: github.com/koalaman/shellcheck/wiki/SC2181
mhulse

1

Bu hile eksik komutlar veya işlevler için kullanışlıdır. Eksik işlevin (veya yürütülebilir dosyanın) adı $ _ olarak aktarılacaktır

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

İşlevde $_olduğu gibi kullanılamaz $?mı? İşlevde birini kullanmak için herhangi bir neden olduğundan emin değilim ama diğerinde değil.
ingyhere

1

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
    "$@"
    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 \""$@"\" 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 bloğun sonuna iletilir. Önceki komutların başarısız olması durumunda sonraki komutların yürütülmesini istemediğinizde, ancak komut dosyasının hemen çıkmasını istemediğinizde (örneğin, bir döngü içinde) yararlı buluyorum.


0

Tuzak kullanmak her zaman bir seçenek değildir. Örneğin, hata işlemeye ihtiyaç duyan ve herhangi bir komut dosyasından çağrılabilen (dosyayı yardımcı işlevlerle kaynakladıktan sonra) bir tür yeniden kullanılabilir işlev yazıyorsanız, bu işlev dış komut dosyasının çıkış süresi hakkında hiçbir şey kabul edemez, bu da tuzakları kullanmayı çok zorlaştırıyor. Tuzakları kullanmanın bir başka dezavantajı, arayan zincirinde daha önce ayarlanmış olabilecek önceki tuzağın üzerine yazma riski taşıdığından, kötü bir şekilde oluşturulabilirliktir.

Tuzaklar olmadan doğru hata işlemeyi yapmak için kullanılabilecek küçük bir numara var. Zaten diğer cevaplardan da bildiğiniz gibi , bir alt kabukta çalıştırsanız bile, onlardan sonra operatör set -ekullanı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 ||temizlemeden önce dış fonksiyondan geri dönmesini önlemek için operatöre ihtiyaç vardır. Hile, iç komutu arka planda çalıştırmak ve hemen beklemek. waitYerleşik iç komutun çıkış kodu döndürür ve şimdi kullandığınız ||sonra waitöylesine değil, iç fonksiyonu, set -eikincisi içeride düzgün çalışır:

#!/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 inşa edilen genel fonksiyon. Kaldırmak eğer tüm POSIX uyumlu kabuklarda çalışmalıdır localanahtar kelimeleri yani hepsini yerine, local x=ysadece birlikte 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" "$@" &

  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" "$@"
    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: $@"
  CLEANUP=cleanup run inner "$@"
  echo "<-- main"
}


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


cleanup() {
  echo "--> cleanup: $@"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "$@"

Örneği çalıştırma:

$ ./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, ilettiğiniz komuttan yapılan Shell değişkenlerinin tüm değişikliklerinin runçağrı işlevine yayılmamasıdır, çünkü komut bir alt kabukta çalışır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.