Exit Shell Script Berdasarkan Proses Exit Code

378

Saya memiliki skrip shell yang menjalankan sejumlah perintah. Bagaimana cara saya membuat skrip shell keluar jika ada perintah yang keluar dengan kode keluar non-nol?

Mark Roddy
sumber
3
Saya menjawab dengan asumsi Anda menggunakan bash, tetapi jika shell yang sangat berbeda dapat Anda tentukan dalam posting Anda?
Martin W
Metode keras: uji nilai $?setelah setiap perintah. Metode mudah: letakkan set -eatau #!/bin/bash -edi bagian atas skrip Bash Anda.
mwfearnley

Jawaban:

494

Setelah setiap perintah, kode keluar dapat ditemukan dalam $?variabel sehingga Anda akan memiliki sesuatu seperti:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Anda harus berhati-hati dengan perintah pipa karena $?hanya memberi Anda kode kembali elemen terakhir dalam pipa, jadi dalam kode:

ls -al file.ext | sed 's/^/xx: /"

tidak akan mengembalikan kode kesalahan jika file tidak ada (karena sedbagian dari pipa sebenarnya bekerja, mengembalikan 0).

The bashshell benar-benar menyediakan sebuah array yang dapat membantu dalam kasus itu, bahwa menjadi PIPESTATUS. Array ini memiliki satu elemen untuk setiap komponen pipa, yang dapat Anda akses secara individual seperti ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Perhatikan bahwa ini memberi Anda hasil dari falseperintah, bukan seluruh pipa. Anda juga bisa mendapatkan seluruh daftar untuk diproses sesuai keinginan Anda:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

Jika Anda ingin mendapatkan kode kesalahan terbesar dari sebuah pipa, Anda bisa menggunakan sesuatu seperti:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Ini berjalan melalui masing-masing PIPESTATUSelemen pada gilirannya, menyimpannya rcjika itu lebih besar dari nilai sebelumnya rc.

paxdiablo
sumber
39
Fitur yang sama hanya dalam satu baris kode portabel: ls -al file.ext || exit $?([[]] tidak portabel)
MarcH
19
MarcH, saya pikir Anda akan menemukan bahwa [[ ]]ini cukup portabel bash, yang merupakan pertanyaan yang ditandai :-) Anehnya, lstidak berfungsi command.comsehingga tidak portabel juga, saya tahu, tapi itu jenis argumen yang sama dengan Anda menyajikan.
paxdiablo
39
Saya tahu ini kuno, tetapi harus dicatat bahwa Anda bisa mendapatkan kode keluar dari perintah dalam pipa melalui array PIPESTATUS(yaitu, ${PIPESTATUS[0]}untuk perintah pertama, ${PIPESTATUS[1]}untuk yang kedua, atau ${PIPESTATUS[*]}untuk daftar semua stati keluar.
DevSolar
11
Perlu ditekankan bahwa scripting shell elegan dan idiomatik sangat jarang perlu diperiksa $?secara langsung. Anda biasanya menginginkan sesuatu seperti if ls -al file.ext; then : nothing; else exit $?; fiyang tentu saja seperti @MarcH katakan setara dengan ls -al file.ext || exit $?tetapi jika thenatau elseklausa yang agak lebih kompleks, itu lebih dapat dipertahankan.
tripleee
9
[[ $rc != 0 ]]akan memberikan 0: not foundatau 1: not foundkesalahan. Ini harus diubah menjadi [ $rc -ne 0 ]. Juga rc=$?bisa dihapus dan digunakan [ $? -ne 0 ].
CurtisLeeBolin
223

Jika Anda ingin bekerja dengan $ ?, Anda harus memeriksanya setelah setiap perintah, karena $? diperbarui setelah setiap perintah keluar. Ini berarti bahwa jika Anda menjalankan pipa, Anda hanya akan mendapatkan kode keluar dari proses terakhir dalam pipa.

Pendekatan lain adalah melakukan ini:

set -e
set -o pipefail

Jika Anda meletakkan ini di bagian atas skrip shell, sepertinya bash akan menangani ini untuk Anda. Seperti yang dicatat oleh poster sebelumnya, "set -e" akan menyebabkan bash keluar dengan kesalahan pada perintah sederhana apa pun. "set -o pipefail" akan menyebabkan bash keluar dengan kesalahan pada perintah apa pun dalam pipa juga.

Lihat di sini atau di sini untuk sedikit lebih banyak diskusi tentang masalah ini. Berikut ini adalah bagian manual bash pada set builtin.

Jeff Hill
sumber
6
Ini benar-benar harus menjadi jawaban teratas: jauh lebih mudah untuk melakukan ini daripada menggunakan PIPESTATUSdan memeriksa kode keluar di mana-mana.
candu
2
#!/bin/bash -eadalah satu-satunya cara untuk memulai skrip shell. Anda selalu dapat menggunakan hal-hal seperti foo || handle_error $?jika Anda harus benar-benar memeriksa status keluar.
Davis Herring
53

" set -e" mungkin cara termudah untuk melakukan ini. Masukkan saja sebelum perintah apa pun di program Anda.

Allen
sumber
6
@SwaroopCH set -eskrip Anda akan dibatalkan jika ada perintah dalam skrip Anda keluar dengan status kesalahan dan Anda tidak menangani kesalahan ini.
Andrew
2
set -e100% setara dengan set -o errexityang tidak seperti yang sebelumnya dapat dicari. Cari opengroup + errexit untuk dokumentasi resmi.
MarcH
30

Jika Anda memanggil keluar di bash tanpa parameter, itu akan mengembalikan kode keluar dari perintah terakhir. Dikombinasikan dengan ATAU bash seharusnya hanya memanggil keluar, jika perintah sebelumnya gagal. Tapi saya belum menguji ini.

command1 || keluar;
command2 || keluar;

Bash juga akan menyimpan kode keluar dari perintah terakhir di variabel $ ?.

Arvodan
sumber
25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
chemila
sumber
3
Bukankah ini seharusnya exit $?(tanpa parens)?
malthe
21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. Bagaimana cara mendapatkan kode keluar cmd1masukcmd1|cmd2

    Pertama, perhatikan bahwa cmd1kode keluar bisa berupa nol dan masih tidak berarti kesalahan. Ini terjadi misalnya di

    cmd | head -1

    Anda mungkin mengamati status keluar 141 (atau 269 dengan ksh93) cmd1, tetapi karena cmdterputus oleh sinyal SIGPIPE ketika head -1dihentikan setelah membaca satu baris.

    Untuk mengetahui status keluar dari elemen-elemen pipa cmd1 | cmd2 | cmd3

    Sebuah. dengan zsh:

    Kode keluar disediakan dalam larik khusus pipestatus. cmd1kode keluar $pipestatus[1], cmd3kode keluar $pipestatus[3], sehingga $?selalu sama dengan $pipestatus[-1].

    b. dengan bash:

    Kode keluar disediakan dalam PIPESTATUSlarik khusus. cmd1kode keluar ${PIPESTATUS[0]}, cmd3kode keluar ${PIPESTATUS[2]}, sehingga $?selalu sama dengan ${PIPESTATUS: -1}.

    ...

    Untuk lebih jelasnya lihat tautan berikut .

gatoatigrado
sumber
19

untuk bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

sumber
19
Mungkin tidak boleh "keluar 0", karena itu menunjukkan keberhasilan.
Nobar
3
exit_code = $?; echo "script dibatalkan, karena kesalahan"; exit $ exit_code
RaSergiy
1
@ HAL9001 apakah Anda memiliki bukti ini? Dokumentasi IBM mengatakan sebaliknya .
Patrick James McDougle
11

Dalam bash ini mudah, cukup ikat mereka dengan &&:

command1 && command2 && command3

Anda juga dapat menggunakan nested jika konstruk:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
Martin W
sumber
+1 Ini adalah solusi paling sederhana yang saya cari. Selain itu, Anda juga dapat menulis if (! command)jika Anda mengharapkan kode kesalahan bukan nol dari perintah.
Berci
ini untuk perintah sekuensial .. bagaimana jika saya ingin meluncurkan 3 itu secara paralel dan membunuh semua orang jika ada yang gagal?
Vasile Surdu
4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit
Yordan Georgiev
sumber
2
Jarang ada alasan untuk menggunakannya $*; gunakan "$@"sebagai gantinya untuk menghemat ruang dan wildcard.
Davis Herring