keluar dari skrip shell dari subkulit

30

Pertimbangkan cuplikan ini:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

Biasanya ketika funcdipanggil itu akan menyebabkan script untuk mengakhiri, yang merupakan perilaku yang dimaksud. Namun, jika dijalankan dalam sub-shell, seperti di

result=`func`

itu tidak akan keluar dari skrip. Ini berarti kode panggilan harus memeriksa status keluar dari fungsi setiap kali. Apakah ada cara untuk menghindari ini? Apakah ini untuk apa set -e?

Ernest AC
sumber
1
saya ingin fungsi "berhenti" yang mencetak pesan ke stderr dan menghentikan skrip, tetapi tidak berhenti ketika fungsi yang memanggil berhenti dijalankan dalam sub-shell, seperti dalam contoh
Ernest AC
2
Tentu saja, karena keluar dari subkulit bukan yang sekarang. Cukup memanggil fungsi secara langsung: func.
1
saya tidak dapat memanggilnya secara langsung karena mengembalikan string yang harus disimpan dalam variabel
Ernest AC
1
@ErnestAC Berikan semua detail dalam pertanyaan awal. Fungsi di atas tidak mengembalikan string.
1
@tor saya mengubah contoh
Ernest AC

Jawaban:

10

Anda dapat membunuh shell asli ( kill $$) sebelum memanggil exit, dan itu mungkin berhasil. Tapi:

  • bagi saya agak jelek
  • itu akan pecah jika Anda memiliki subkulit kedua di sana, yaitu, gunakan subkulit di dalam subkulit.

Sebagai gantinya, Anda bisa menggunakan salah satu dari beberapa cara untuk mengembalikan nilai dalam Bash FAQ . Sayangnya, kebanyakan dari mereka tidak begitu hebat. Anda mungkin hanya terjebak memeriksa kesalahan setelah setiap panggilan fungsi ( -ememiliki banyak masalah ) Entah itu, atau beralih ke Perl.

derobert
sumber
5
Terima kasih. Saya lebih suka beralih ke Python.
Ernest AC
2
Saat saya menulis, ini tahun 2019. Memberitahu seseorang untuk "beralih ke Perl" adalah konyol. Maaf untuk diperdebatkan, tetapi apakah Anda akan memberi tahu seseorang yang frustrasi dengan 'C' untuk beralih ke Cobol, yang setara dengan IMO? Seperti yang ditunjukkan Ernest, Python adalah pilihan yang jauh lebih baik. Preferensi saya adalah Ruby. Either way, apa pun kecuali Perl.
Graham Nicholls
38

Anda dapat memutuskan bahwa status keluar 77 misalnya berarti keluar dari setiap tingkat subkulit, dan lakukan

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -Edalam kombinasi dengan ERRjebakan agak mirip dengan versi yang disempurnakan set -eyang memungkinkan Anda untuk menentukan penanganan kesalahan Anda sendiri.

Di zsh, ERR traps diwarisi secara otomatis, jadi Anda tidak perlu set -E, Anda juga dapat mendefinisikan jebakan sebagai TRAPERR()fungsi, dan memodifikasinya $functions[TRAPERR], sepertifunctions[TRAPERR]="echo was here; $functions[TRAPERR]"

Stéphane Chazelas
sumber
1
Solusi menarik! Jelas lebih elegan daripada kill $$.
3
Satu hal yang harus diperhatikan, perangkap ini tidak akan menangani perintah yang diinterpolasi, misalnya echo "$(exit 77)"; script akan melanjutkan seolah-olah kita telah menulisecho ""
Warbo
Interresting! Apakah ada keberuntungan pada bash (cukup tua) yang tidak memiliki -E? mungkin kita harus menggunakan jebakan untuk mendefinisikan sinyal USER dan menggunakan kill untuk sinyal itu? Saya akan melakukan riset ulang juga ...
Olivier Dulac
Bagaimana cara mengetahui, ketika tidak berada dalam perangkap sub-shell, untuk mengembalikan 1 bukan 77?
ceving
7

Sebagai alternatif kill $$, Anda juga dapat mencoba kill 0, ini akan berfungsi dalam kasus subshell bersarang (semua penelepon dan proses samping akan menerima sinyal) ... tetapi masih brutal dan jelek.

Stéphane Gimenez
sumber
2
Bukankah ini proses pembatalan id 0?
Ernest AC
5
Itu akan membunuh seluruh grup proses. Anda dapat mengenai hal-hal yang tidak Anda inginkan (jika misalnya Anda telah memulai beberapa hal di latar belakang).
derobert
2
@ErnestAC melihat kill (2) manual, pids ≤0 memiliki arti khusus.
derobert
0

Coba ini ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

Hasil yang saya dapatkan adalah ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

Catatan

  • Parameterisasi untuk memungkinkan iftes menjadi trueatau false(lihat 2 berjalan)
  • Ketika iftes ini false, kami tidak pernah mencapai subkulit.
DocSalvager
sumber
Ini sangat mirip dengan ide asli yang diposkan pengguna dan dikatakan tidak berfungsi. Saya tidak berpikir ini berfungsi untuk kasus subkulit. Tes Anda menggunakan pintu keluar palsu setelah kotak "shell" dan tidak pernah sampai ke kotak uji "subshell". Saya percaya itu akan gagal untuk kasus itu karena subkulit akan keluar dari panggilan "keluar 1" tetapi tidak menyebarkan kesalahan ke shell luar.
stuckj
0

(Jawaban spesifik Bash) Bash tidak memiliki konsep pengecualian. Namun, dengan set -o errexit (atau yang setara: set -e) di level terluar, perintah yang gagal akan menghasilkan subkulit yang keluar dengan status keluar yang tidak nol. Jika ini adalah satu set subkulit bersarang tanpa persyaratan di sekitar pelaksanaan subkulit tersebut, ini akan secara efektif 'menggulung' seluruh skrip dan keluar.

Ini bisa rumit ketika mencoba memasukkan bit dari berbagai kode bash ke dalam skrip yang lebih besar. Satu bash bash mungkin bekerja dengan baik sendiri, tetapi ketika dieksekusi di bawah errexit (atau tanpa errexit), berperilaku dengan cara yang tidak terduga.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
ada yang salah
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
ada yang salah
Tapi kita sampai di sini
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / bin / bash
berhenti () {
    gema "$ {1}"
    keluar 1
}

jika salah; kemudian
    gema "foo"
lain
    (
        hentikan "ada yang salah"
    )
    gema "Tapi kita sampai di sini"
fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #
Brian Chrisman
sumber
-2

Contoh saya untuk keluar dalam satu liner:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit
vicente
sumber
1
Ini sepertinya tidak benar-benar menjadi jawaban yang akan bekerja dengan perintah berjalan di sub-shells seperti yang diminta oleh OP ... Itu mengatakan sementara saya tidak setuju dengan suara turun yang diberikan jawaban. Suara yang turun tanpa komentar atau alasan sama tidak membantunya dengan jawaban yang buruk.
DVS