Perangkap, ERR, dan gema garis kesalahan


30

Saya mencoba membuat beberapa pelaporan kesalahan menggunakan Perangkap untuk memanggil fungsi pada semua kesalahan:

Trap "_func" ERR

Apakah mungkin untuk mendapatkan saluran dari mana sinyal ERR dikirim? Shell itu bash.

Jika saya melakukan itu, saya bisa membaca dan melaporkan perintah apa yang digunakan dan mencatat / melakukan beberapa tindakan.

Atau mungkin saya salah dalam hal ini?

Saya diuji dengan yang berikut:

#!/bin/bash
trap "ECHO $LINENO" ERR

echo hello | grep "asdf"

Dan $LINENOkembali 2. Tidak bekerja.


Anda dapat melihat skrip debugger bash bashdb. Tampaknya argumen pertama untuk trapdapat berisi variabel yang dievaluasi dalam konteks yang diinginkan. Jadi trap 'echo $LINENO' ERR'harusnya bekerja.
Donasi berhasil

hmm baru saja mencoba ini dengan gema yang buruk | perintah grep dan mengembalikan baris pernyataan Trap. Tapi saya akan melihat bashdb
Mechaflash

Saya sangat menyesal ... Saya tidak menentukan dalam pertanyaan asli saya bahwa saya memerlukan solusi asli. Saya mengedit pertanyaan.
Mechaflash

Maaf, saya borked contoh baris: trap 'echo $LINENO' ERR. Argumen pertama trapadalah seluruh echo $LINENOhardquoted. Ini dalam bash.
Donasi berhasil

5
@ Techaflash Itu harus trap 'echo $LINENO' ERR, dengan tanda kutip tunggal, bukan tanda kutip ganda. Dengan perintah yang Anda tulis, $LINENOdiperluas ketika baris 2 diuraikan, jadi jebakannya adalah echo 2(atau lebih tepatnya ECHO 2, yang akan ditampilkan bash: ECHO: command not found).
Gilles 'SANGAT berhenti menjadi jahat'

Jawaban:


61

Seperti yang ditunjukkan dalam komentar, penawaran Anda salah. Anda perlu satu tanda kutip untuk mencegah $LINENOagar tidak diperluas ketika garis perangkap pertama kali diuraikan.

Ini bekerja:

#! /bin/bash

err_report() {
    echo "Error on line $1"
}

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Menjalankannya:

 $ ./test.sh
 Error on line 9

terima kasih untuk contohnya dengan panggilan fungsi. Saya tidak tahu bahwa tanda kutip ganda memperluas variabel dalam kasus ini.
Mechaflash

echo hello | grep foosepertinya tidak menimbulkan kesalahan bagi saya. Apakah saya salah memahami sesuatu?
geotheory

@geotheory Pada sistem saya grepmemiliki status keluar 0 jika ada kecocokan, 1 jika tidak ada kecocokan dan> 1 untuk kesalahan. Anda dapat memeriksa perilaku di sistem Anda denganecho hello | grep foo; echo $?
Patrick

Tidak, Anda benar, ini adalah kesalahan :)
geotheory

Tidakkah Anda perlu menggunakan -e pada baris doa, untuk menyebabkan kesalahan pada kegagalan perintah? Itu adalah: #! / Bin / bash -e?
Tim Bird

14

Anda juga dapat menggunakan bash builtin 'pemanggil':

#!/bin/bash

err_report() {
  echo "errexit on line $(caller)" >&2
}

trap err_report ERR

echo hello | grep foo

itu mencetak nama file juga:

$ ./test.sh
errexit on line 9 ./test.sh

7

Saya sangat suka jawaban yang diberikan oleh @Mat di atas. Membangun ini, saya menulis sedikit pembantu yang memberikan sedikit lebih banyak konteks untuk kesalahan:

Kita dapat memeriksa skrip untuk baris yang menyebabkan kegagalan:

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

Ini dia dalam skrip tes kecil:

#!/bin/bash

set -e

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

echo one
echo two
echo three
echo four
false
echo five
echo six
echo seven
echo eight

Saat kami menjalankannya, kami dapat:

$ /tmp/test.sh
one
two
three
four
Error occurred:
12      echo two
13      echo three
14      echo four
15   >>>false
16      echo five
17      echo six
18      echo seven

Ini akan lebih baik menggunakan $(caller)data untuk memberikan konteks bahkan jika kegagalan tidak ada dalam skrip saat ini tetapi salah satu dari impornya. Sangat bagus!
tricasse

2

Terinspirasi oleh jawaban lain, berikut ini adalah penangan kesalahan kontekstual yang lebih sederhana:

trap '>&2 echo Command failed: $(tail -n+$LINENO $0 | head -n1)' ERR

Anda juga dapat menggunakan awk sebagai ganti tail & head jika diperlukan.


1
ada alasan jawaban yang lain menyediakan konteks dengan 3 baris di atas dan 3 baris di bawah garis menyinggung - bagaimana jika kesalahan berasal dari garis kelanjutan?
iruvar

@iruvar ini dimengerti, tapi saya tidak butuh konteks ekstra itu; satu baris konteks sesederhana yang didapatnya, dan
secukup yang

Oke teman saya, +1
iruvar

1

Ini versi lain, terinspirasi oleh @sanmai dan @unpythonic. Ini menunjukkan garis skrip di sekitar kesalahan, dengan nomor baris, dan status keluar - menggunakan tail & head karena tampaknya lebih sederhana daripada solusi awk.

Menampilkan ini sebagai dua baris di sini agar mudah dibaca - Anda dapat menggabungkannya menjadi satu jika Anda lebih suka (menjaga ;):

trap 'echo >&2 "Error - exited with status $? at line $LINENO:"; 
         pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR

Ini bekerja cukup baik dengan set -euo pipefail( mode ketat tidak resmi ) - setiap kesalahan variabel yang tidak terdefinisi memberikan nomor baris tanpa menembakkan ERRsinyal semu, tetapi kasus lain menunjukkan konteks.

Contoh output:

myscript.sh: line 27: blah: command not found
Error - exited with status 127 at line 27:
   24   # Do something
   25   lines=$(wc -l /etc/passwd)
   26   # More stuff
   27   blah
   28   
   29   # Check time
   30   time=$(date)

0

Apakah mungkin untuk mendapatkan saluran dari mana sinyal ERR dikirim?

Ya, LINENOdan BASH_LINENOvariabel sangat berguna untuk mendapatkan garis kegagalan dan garis yang mengarah ke sana.

Atau mungkin saya salah dalam hal ini?

Tidak, hanya -qopsi yang hilang dengan ...

echo hello | grep -q "asdf"

... Dengan -qopsi grepakan kembali 0untuk truedan 1untuk false. Dan di Bash itu trapbukan Trap...

trap "_func" ERR

... Saya butuh solusi asli ...

Ini adalah penjebak yang mungkin berguna untuk men-debug hal-hal yang memiliki kompleksitas siklomatik sedikit lebih ...

failure.sh

## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
##    trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
    local -n _lineno="${1:-LINENO}"
    local -n _bash_lineno="${2:-BASH_LINENO}"
    local _last_command="${3:-${BASH_COMMAND}}"
    local _code="${4:-0}"

    ## Workaround for read EOF combo tripping traps
    if ! ((_code)); then
        return "${_code}"
    fi

    local _last_command_height="$(wc -l <<<"${_last_command}")"

    local -a _output_array=()
    _output_array+=(
        '---'
        "lines_history: [${_lineno} ${_bash_lineno[*]}]"
        "function_trace: [${FUNCNAME[*]}]"
        "exit_code: ${_code}"
    )

    if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
        _output_array+=('source_trace:')
        for _item in "${BASH_SOURCE[@]}"; do
            _output_array+=("  - ${_item}")
        done
    else
        _output_array+=("source_trace: [${BASH_SOURCE[*]}]")
    fi

    if [[ "${_last_command_height}" -gt '1' ]]; then
        _output_array+=(
            'last_command: ->'
            "${_last_command}"
        )
    else
        _output_array+=("last_command: ${_last_command}")
    fi

    _output_array+=('---')
    printf '%s\n' "${_output_array[@]}" >&2
    exit ${_code}
}

... dan contoh skrip penggunaan untuk mengungkap perbedaan halus dalam cara mengatur jebakan di atas untuk penelusuran fungsi juga ...

example_usage.sh

#!/usr/bin/env bash

set -E -o functrace

## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"


## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR


something_functional() {
    _req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
    _opt_arg_one="${2:-SPAM}"
    _opt_arg_two="${3:0}"
    printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
    ## Generate an error by calling nothing
    "${__DIR__}/nothing.sh"
}


## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
    printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi


## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'

Di atas di mana diuji pada Bash versi 4+, jadi tinggalkan komentar jika sesuatu untuk versi sebelum empat diperlukan, atau Buka Masalah jika gagal menjebak kegagalan pada sistem dengan versi minimum empat.

Takeaways utama adalah ...

set -E -o functrace
  • -Emenyebabkan kesalahan dalam fungsi muncul

  • -o functrace sebab memungkinkan lebih banyak verbositas ketika sesuatu dalam suatu fungsi gagal

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Kutipan tunggal digunakan di sekitar pemanggilan fungsi dan kutipan ganda di sekitar argumen individual

  • Referensi ke LINENOdan BASH_LINENOditeruskan alih-alih nilai saat ini, meskipun ini mungkin disingkat di versi yang lebih baru terkait dengan perangkap, sehingga garis kegagalan akhir membuatnya menjadi output

  • Nilai BASH_COMMANDdan status keluar ( $?) diteruskan, pertama untuk mendapatkan perintah yang mengembalikan kesalahan, dan kedua untuk memastikan bahwa jebakan tidak memicu status non-kesalahan

Dan sementara yang lain mungkin tidak setuju saya merasa lebih mudah untuk membangun array output dan menggunakan printf untuk mencetak setiap elemen array pada baris itu sendiri ...

printf '%s\n' "${_output_array[@]}" >&2

... juga >&2sedikit di akhir menyebabkan kesalahan untuk pergi ke mana seharusnya (kesalahan standar), dan memungkinkan untuk menangkap kesalahan saja ...

## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log

## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null

Seperti yang ditunjukkan oleh ini dan contoh-contoh lain pada Stack Overflow, ada banyak cara untuk membangun bantuan debugging menggunakan utilitas bawaan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.