Meningkatkan kesalahan dalam skrip Bash

103

Saya ingin memunculkan kesalahan dalam skrip Bash dengan pesan "Kasus uji Gagal !!!". Bagaimana melakukan ini di Bash?

Sebagai contoh:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
Naveen Kumar
sumber
1
Apa yang Anda inginkan terjadi pada kesalahan ini? Bagaimana skrip Anda disebut? Apakah hanya satu skrip atau banyak skrip? Seperti apa kegunaan skrip Anda nantinya?
Etan Reisner
hanya satu skrip. Saya menyebutnya menggunakan terminal ubuntu seperti ./script/test.sh
Naveen Kumar
unix.stackexchange.com
Seth McClaine
Tidak ada cinta untuk echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
diinfiks

Jawaban:

121

Ini tergantung di mana Anda ingin pesan kesalahan disimpan.

Anda dapat melakukan hal berikut:

echo "Error!" > logfile.log
exit 125

Atau berikut ini:

echo "Error!" 1>&2
exit 64

Ketika Anda mengajukan pengecualian, Anda menghentikan eksekusi program.

Anda juga dapat menggunakan sesuatu seperti di exit xxxmana xxxkode kesalahan yang mungkin ingin Anda kembalikan ke sistem operasi (dari 0 hingga 255). Ini 125dan 64hanya kode acak yang dapat Anda gunakan untuk keluar. Saat Anda perlu menunjukkan ke OS bahwa program berhenti secara tidak normal (mis. Terjadi kesalahan), Anda harus meneruskan kode keluar bukan nol ke exit.

Seperti yang ditunjukkan oleh @chepner , Anda bisa melakukannya exit 1, yang berarti kesalahan yang tidak ditentukan .

ForceBru
sumber
12
atau Anda dapat mengirimkannya ke stderr, di mana kesalahan seharusnya terjadi.
bagaimana cara mengirimnya ke stderr?
Naveen Kumar
2
@ user3078630, saya baru saja mengedit jawaban saya. 1>&2akan melakukan trik
ForceBru
Jika ini adalah kesalahan, Anda juga harus keluar dengan status keluar bukan nol. exitdengan sendirinya menggunakan status keluar dari perintah yang terakhir diselesaikan, yang mungkin 0.
chepner
3
Kecuali Anda memiliki arti tertentu dalam pikiran, Anda harus menggunakan exit 1, yang menurut konvensi berarti kesalahan yang tidak ditentukan.
chepner
36

Penanganan kesalahan dasar

Jika runner kasus pengujian Anda mengembalikan kode bukan nol untuk pengujian yang gagal, Anda cukup menulis:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Atau bahkan lebih pendek:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Atau yang terpendek:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Untuk keluar dengan kode keluar test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Penanganan kesalahan lanjutan

Jika Anda ingin mengambil pendekatan yang lebih komprehensif, Anda dapat memiliki penangan kesalahan:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

lalu panggil setelah menjalankan kasus uji Anda:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

atau

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Keuntungan memiliki penangan kesalahan exit_if_erroradalah:

  • kami dapat menstandarkan semua logika penanganan kesalahan seperti logging , mencetak jejak tumpukan , notifikasi, melakukan pembersihan, dll., di satu tempat
  • dengan membuat penangan kesalahan mendapatkan kode kesalahan sebagai argumen, kita dapat menghindarkan pemanggil dari kekacauan ifblok yang menguji kode keluar untuk kesalahan
  • jika kita memiliki penangan sinyal (menggunakan perangkap ), kita dapat menjalankan penangan kesalahan dari sana

Penanganan kesalahan dan pencatatan perpustakaan

Berikut implementasi lengkap penanganan error dan logging:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Pos terkait

codeforester
sumber
9

Ada beberapa cara lain untuk mengatasi masalah ini. Dengan asumsi salah satu persyaratan Anda adalah menjalankan skrip / fungsi shell yang berisi beberapa perintah shell dan memeriksa apakah skrip berhasil berjalan dan menampilkan kesalahan jika terjadi kegagalan.

Perintah shell pada umumnya mengandalkan kode keluar yang dikembalikan untuk memberi tahu shell jika berhasil atau gagal karena beberapa kejadian tak terduga.

Jadi apa yang ingin Anda lakukan termasuk dalam dua kategori ini

  • keluar karena kesalahan
  • keluar dan bersihkan kesalahan

Bergantung pada mana yang ingin Anda lakukan, ada opsi shell yang tersedia untuk digunakan. Untuk kasus pertama, shell menyediakan opsi denganset -e dan untuk kasus kedua Anda bisa melakukan traponEXIT

Haruskah saya gunakan exitdalam skrip / fungsi saya?

Menggunakan exit secara umum meningkatkan keterbacaan Dalam rutinitas tertentu, setelah Anda mengetahui jawabannya, Anda ingin segera keluar dari rutinitas panggilan. Jika rutinitas didefinisikan sedemikian rupa sehingga tidak memerlukan pembersihan lebih lanjut setelah mendeteksi kesalahan, tidak segera keluar berarti Anda harus menulis lebih banyak kode.

Jadi dalam kasus jika Anda perlu melakukan tindakan pembersihan pada skrip untuk membuat penghentian skrip bersih, lebih disukai untuk tidak menggunakanexit .

Haruskah saya gunakan set -euntuk kesalahan saat keluar?

Tidak!

set -eadalah upaya untuk menambahkan "deteksi kesalahan otomatis" ke shell. Tujuannya adalah untuk membatalkan shell setiap kali terjadi kesalahan, tetapi ia datang dengan banyak potensi jebakan misalnya,

  • Perintah yang merupakan bagian dari tes if kebal. Dalam contoh, jika Anda mengharapkannya untuk merusak testpemeriksaan pada direktori yang tidak ada, itu tidak akan terjadi, itu akan masuk ke kondisi lain

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
  • Perintah dalam pipa selain yang terakhir, kebal. Dalam contoh di bawah ini, karena kode keluar perintah yang paling baru dijalankan (paling kanan) dianggap ( cat) dan berhasil. Ini dapat dihindari dengan menetapkan set -o pipefailopsi tetapi ini masih peringatan.

    set -e
    somecommand that fails | cat -
    echo survived 

Direkomendasikan untuk digunakan - trapsaat keluar

Putusannya adalah jika Anda ingin menangani kesalahan alih-alih keluar secara membabi buta, alih-alih menggunakan set -e, gunakan a trappada ERRsinyal pseudo.

The ERRperangkap tidak untuk menjalankan kode ketika shell itu sendiri keluar dengan nol non kode kesalahan, tetapi ketika perintah dijalankan oleh yang shell yang bukan merupakan bagian dari kondisi (seperti di jika cmd, ataucmd || ) keluar dengan status keluar non-zero .

Praktik umumnya adalah kita mendefinisikan penangan perangkap untuk memberikan informasi debug tambahan pada baris mana dan apa yang menyebabkan keluar. Ingat kode keluar dari perintah terakhir yang menyebabkan ERRsinyal masih tersedia pada saat ini.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

dan kami hanya menggunakan handler ini seperti di bawah ini di atas skrip yang gagal

trap cleanup ERR

Menyatukan ini pada skrip sederhana yang terdapat falsepada baris 15, informasi yang akan Anda peroleh sebagai

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

The trapjuga menyediakan pilihan terlepas dari kesalahan hanya menjalankan pembersihan pada shell selesai (misalnya Anda keluar shell script), pada sinyalEXIT . Anda juga dapat menangkap banyak sinyal pada saat yang bersamaan. Daftar sinyal yang didukung untuk melakukan trap dapat ditemukan pada halaman manual trap.1p - Linux

Hal lain yang perlu diperhatikan adalah memahami bahwa tidak ada metode yang disediakan yang berfungsi jika Anda berurusan dengan sub-shell yang terlibat dalam hal ini, Anda mungkin perlu menambahkan penanganan kesalahan Anda sendiri.

  • Pada sub-shell dengan set -etidak akan berfungsi. Itu falsedibatasi untuk sub-shell dan tidak pernah disebarkan ke shell induk. Untuk melakukan penanganan kesalahan di sini, tambahkan logika Anda sendiri yang harus dilakukan(false) || false

    set -e
    (false)
    echo survived
  • Hal yang sama trapjuga terjadi dengan . Logika di bawah ini tidak akan berfungsi karena alasan yang disebutkan di atas.

    trap 'echo error' ERR
    (false)
Inian
sumber
5

Berikut adalah jebakan sederhana yang mencetak argumen terakhir dari apa pun yang gagal ke STDERR, melaporkan baris yang gagal, dan keluar dari skrip dengan nomor baris sebagai kode keluar. Perhatikan bahwa ini tidak selalu merupakan ide bagus, tetapi ini menunjukkan beberapa aplikasi kreatif yang dapat Anda kembangkan.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Saya memasukkannya ke dalam skrip dengan loop untuk mengujinya. Saya hanya memeriksa hit pada beberapa nomor acak; Anda mungkin menggunakan tes yang sebenarnya. Jika saya perlu jaminan, saya sebut salah (yang memicu jebakan) dengan pesan yang ingin saya lempar.

Untuk fungsionalitas yang dielaborasi, buatlah panggilan trap sebagai fungsi pemrosesan. Anda selalu dapat menggunakan pernyataan kasus pada arg ($ _) jika Anda perlu melakukan lebih banyak pembersihan, dll. Tetapkan ke var untuk mendapatkan sedikit gula sintaksis -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Output sampel:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Jelas, Anda bisa

runTest1 "Test1 fails" # message not used if it succeeds

Banyak ruang untuk perbaikan desain.

Kelemahannya termasuk fakta bahwa falsetidak cantik (dengan demikian gula), dan hal-hal lain yang tersandung perangkap mungkin terlihat sedikit bodoh. Tetap saja, saya suka metode ini.

Paul Hodges
sumber
4

Anda memiliki 2 opsi: Alihkan output skrip ke file, Perkenalkan file log di skrip dan

  1. Mengalihkan output ke file :

Di sini Anda berasumsi bahwa skrip mengeluarkan semua info yang diperlukan, termasuk peringatan dan pesan kesalahan. Anda kemudian dapat mengarahkan output ke file pilihan Anda.

./runTests &> output.log

Perintah di atas mengalihkan baik keluaran standar dan keluaran kesalahan ke file log Anda.

Dengan menggunakan pendekatan ini, Anda tidak perlu memasukkan file log ke dalam skrip, sehingga logikanya sedikit lebih mudah.

  1. Perkenalkan file log ke skrip :

Dalam skrip Anda, tambahkan file log baik dengan mengkodekannya secara keras:

logFile='./path/to/log/file.log'

atau meneruskannya dengan parameter:

logFile="${1}"  # This assumes the first parameter to the script is the log file

Sebaiknya tambahkan stempel waktu pada saat eksekusi ke file log di bagian atas skrip:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Anda kemudian dapat mengarahkan pesan kesalahan Anda ke file log

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Ini akan menambahkan kesalahan ke file log dan melanjutkan eksekusi. Jika Anda ingin menghentikan eksekusi saat terjadi kesalahan kritis, Anda bisaexit skrip:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Catat itu exit 1 menunjukkan bahwa program menghentikan eksekusi karena kesalahan yang tidak ditentukan. Anda dapat menyesuaikan ini jika Anda suka.

Dengan menggunakan pendekatan ini, Anda dapat menyesuaikan log Anda dan memiliki file log yang berbeda untuk setiap komponen skrip Anda.


Jika Anda memiliki skrip yang relatif kecil atau ingin mengeksekusi skrip orang lain tanpa mengubahnya, pendekatan pertama lebih cocok.

Jika Anda selalu ingin file log berada di lokasi yang sama, ini adalah opsi yang lebih baik dari 2. Juga jika Anda telah membuat skrip besar dengan banyak komponen maka Anda mungkin ingin membuat log setiap bagian secara berbeda dan pendekatan kedua adalah satu-satunya pilihan.

alamoot
sumber
3

Saya sering merasa berguna untuk menulis fungsi untuk menangani pesan kesalahan sehingga kodenya lebih bersih secara keseluruhan.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Ini mengambil kode kesalahan dari perintah sebelumnya dan menggunakannya sebagai kode kesalahan default saat keluar dari seluruh skrip. Ini juga mencatat waktu, dengan mikrodetik yang didukung (tanggal GNU %Nadalah nanodetik, yang kami potong menjadi mikrodetik nanti).

Jika opsi pertama adalah nol atau bilangan bulat positif, itu menjadi kode keluar dan kami menghapusnya dari daftar opsi. Kami kemudian melaporkan pesan ke kesalahan standar, dengan nama skrip, kata "ERROR", dan waktu (kami menggunakan perluasan parameter untuk memotong nanodetik menjadi mikrodetik, atau untuk waktu non-GNU, untuk memotong misalnya 12:34:56.%Nke 12:34:56). Titik dua dan spasi ditambahkan setelah kata ERROR, tetapi hanya jika ada pesan kesalahan yang diberikan. Terakhir, kami keluar dari skrip menggunakan kode keluar yang telah ditentukan sebelumnya, memicu jebakan seperti biasa.

Beberapa contoh (asumsikan kode tetap ada script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Adam Katz
sumber