Simpan kode keluar saat menjebak SIGINT dan sejenisnya?

14

Jika saya menggunakan trapseperti dijelaskan misalnya pada http://linuxcommand.org/wss0160.php#trap untuk menangkap ctrl-c (atau serupa) dan pembersihan sebelum keluar maka saya mengubah kode keluar kembali.

Sekarang ini mungkin tidak akan membuat perbedaan di dunia nyata (misalnya karena kode keluar tidak portabel dan di atas itu tidak selalu jelas seperti yang dibahas dalam kode keluar default ketika proses dihentikan? ) Tapi masih saya bertanya-tanya apakah ada benar-benar tidak ada cara untuk mencegah itu dan mengembalikan kode kesalahan default untuk skrip yang terputus?

Contoh (dalam bash, tapi pertanyaan saya tidak boleh dianggap spesifik-bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Keluaran:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Diedit untuk menghapus agar lebih sesuai dengan POSIX.)

(Diedit lagi untuk menjadikannya skrip bash sebagai gantinya, pertanyaan saya tidak spesifik untuk shell.)

Diedit untuk menggunakan "INT" portabel untuk jebakan yang mendukung "SIGINT" non-portabel.

Diedit untuk menghilangkan kawat gigi keriting yang tidak berguna dan menambahkan solusi potensial.

Memperbarui:

Saya memecahkannya sekarang dengan hanya keluar dengan beberapa kode kesalahan hardcoded dan menjebak EXIT. Ini mungkin bermasalah pada sistem tertentu karena kode kesalahan mungkin berbeda atau perangkap EXIT tidak mungkin tetapi dalam kasus saya cukup OK.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
phk
sumber
Script Anda terlihat sedikit aneh: Anda meminta readuntuk membaca dari proses yang sekarang dan trap cmd SIGINTtidak akan berfungsi seperti yang dikatakan standar yang harus Anda gunakan trap cmd INT.
schily
Ah ya, di bawah POSIX itu tentu saja tanpa awalan SIG.
phk
Ups, tapi "read-p" juga tidak akan didukung, jadi saya akan menyesuaikannya untuk bash.
phk
@schily: Saya tidak tahu apa yang Anda maksud dengan "proses".
phk
Nah, halaman manual Korn Shell mengatakan read -pmembaca input dari proses saat ini.
schily

Jawaban:

4

Yang perlu Anda lakukan adalah mengubah handler EXIT di dalam handler pembersihan Anda. Ini sebuah contoh:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '
berbatu
sumber
yang Anda maksud trap cleanup INTbukan trap cleanup EXIT?
Jeff Schaller
Saya pikir saya maksud EXIT. Ketika keluar akhirnya dipanggil, namun itu dilakukan, kami mengubah jebakan untuk keluar dengan kembali 0. Saya tidak percaya penangan sinyal rekursif.
berbatu
OK, dalam contoh Anda, saya tidak menjebak (SIG) INT sama sekali yang sebenarnya saya inginkan untuk melakukan pembersihan bahkan jika pengguna keluar dari skrip interaktif saya melalui ctrl-c.
phk
@ phk Ok. Saya pikir meskipun Anda mendapatkan ide tentang bagaimana memaksa keluar untuk mengembalikan 0 atau nilai lain yang saya kumpulkan adalah masalahnya. Saya berasumsi Anda akan dapat menyesuaikan kode untuk menempatkan pengaturan EXIT trap di dalam handler pembersihan nyata Anda yang disebut oleh SIGINT.
berbatu
@rocky: Tujuan saya adalah memiliki penangan jebakan sementara tidak mengubah kode keluar yang biasanya saya miliki tanpa penangan. Sekarang saya bisa mengembalikan kode keluar yang biasanya Anda dapatkan tetapi masalahnya adalah saya tidak tahu kode keluar yang tepat dalam kasus ini (seperti yang terlihat di utas yang saya tautkan dan pos dari meuh itu cukup rumit). Posting Anda masih membantu, saya tidak berpikir untuk mendefinisikan kembali penangan jebakan secara dinamis.
phk
9

Sebenarnya, menginterupsi internal bash readtampaknya sedikit berbeda dengan menginterupsi perintah yang dijalankan oleh bash. Biasanya, ketika Anda masuk trap, $?diatur dan Anda dapat mempertahankannya dan keluar dengan nilai yang sama:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Jika skrip Anda terputus saat menjalankan perintah suka sleep atau bahkan sejenis bawaan wait, Anda akan melihat

130 SIGINT
130 EXIT

dan kode keluar adalah 130. Namun, untuk read -p, tampaknya $?adalah 0 (pada versi saya dari bash 4.3.42).


Penanganan sinyal selama readmungkin sedang berlangsung, sesuai dengan file perubahan dalam rilis saya ... (/ usr / share / doc / bash / CHANGES)

perubahan antara versi ini, bash-4.3-alpha, dan versi sebelumnya, bash-4.2-release.

  1. Fitur Baru di Bash

    r. Ketika dalam mode Posix, `read 'dapat terputus oleh sinyal yang terperangkap. Setelah menjalankan penangan perangkap, baca mengembalikan sinyal 128 + dan membuang input yang dibaca sebagian.

meuh
sumber
OK, itu benar-benar aneh. Ini 130 pada shell interaktif pada bash 4.3.42 (di bawah cygwin), 0 di bawah shell yang sama ketika dalam skrip dan mode POSIX atau tidak ada bedanya. Tapi kemudian di bawah tanda hubung dan busybox selalu 1.
phk
Saya mencoba mode POSIX dengan jebakan yang tidak keluar, dan itu me-restart read, seperti dicatat dalam file PERUBAHAN (ditambahkan ke jawaban saya). Jadi, itu mungkin sedang dalam proses.
meuh
Kode keluar 130 adalah 100% non-portabel. Sementara Bourne Shell digunakan 128 + signosebagai kode keluar untuk sinyal, ksh93 menggunakan 256 + signo. POSIX mengatakan: sesuatu di atas 128 ....
schily
@ Schily Benar, seperti yang tercantum di utas yang saya tautkan pada pos asli ( unix.stackexchange.com/questions/99112 ).
phk
6

Kode keluar sinyal yang biasa akan tersedia pada $?saat masuk ke penangan perangkap:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Jika ada jebakan EXIT yang terpisah, Anda dapat menggunakan metodologi yang sama: segera pertahankan status keluar dari penangan sinyal (jika ada) bersih-bersih, lalu kembalikan status keluar yang disimpan.

Tom Hale
sumber
Ini berlaku untuk sebagian besar kerang? Sangat bagus. localbukan POSIX.
phk
1
Diedit. Saya belum diuji selain {ba,z}sh. AFAIK, itu yang terbaik yang bisa dilakukan, terlepas dari itu.
Tom Hale
Ini tidak berfungsi dalam tanda hubung ( $?adalah 1 apa pun sinyal yang diterima).
MoonSweep
2

Hanya mengembalikan beberapa kode kesalahan tidak cukup untuk mensimulasikan keluar oleh SIGINT. Saya terkejut bahwa tidak ada yang menyebutkan sejauh ini. Bacaan lebih lanjut: https://www.cons.org/cracauer/sigint.html

Cara yang tepat adalah:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Ini bekerja dengan Bash, Dash, dan zsh. Untuk portabilitas lebih lanjut, Anda perlu menggunakan spesifikasi sinyal numerik (di sisi lain, zsh mengharapkan parameter string untuk killperintah ...)

Perhatikan juga perlakuan khusus dari EXITsinyal. Hal ini disebabkan oleh beberapa cangkang (yaitu Bash) yang menjalankan jebakan EXITuntuk setiap sinyal juga (setelah jebakan yang mungkin ditentukan pada sinyal itu). Reset EXITperangkap mencegah hal itu.

Hanya mengeksekusi kode saat keluar "normal"

Pemeriksaan [ $sig = EXIT ]memungkinkan untuk mengeksekusi kode hanya pada jalan keluar yang normal (non-sinyal). Namun, semua sinyal harus memiliki jebakan yang pada akhirnya mengatur ulang jebakan pada EXITsaat itu; normal_exit_only_cleanupjuga akan dipanggil untuk sinyal yang tidak. Ini juga akan dieksekusi melalui pintu keluar set -e. Ini dapat diperbaiki dengan menjebak ERR(yang tidak didukung oleh Dash) dan menambahkan tanda centang [ $sig = ERR ]sebelum kill.

Versi Bash-only yang disederhanakan

Di sisi lain, perilaku ini berarti bahwa di Bash Anda bisa melakukannya

trap cleanup EXIT

untuk hanya menjalankan beberapa kode pembersihan dan mempertahankan status keluar.

diedit

  • Perilaku rumit Bash tentang "EXIT menjebak semuanya"

  • Hapus sinyal KILL, yang tidak bisa dijebak

  • Hapus awalan SIG dari nama sinyal

  • Jangan berusaha kill -s EXIT

  • Ambil set -e/ ERR memperhitungkan

philipp2100
sumber
Tidak ada persyaratan umum bahwa sebuah program yang memerangkap sinyal berakhir dengan cara yang menjaga fakta bahwa ia menerima sinyal. Misalnya suatu program dapat menyatakan bahwa pengiriman SIGINTadalah cara untuk mematikannya dan mungkin memutuskan untuk keluar dengan 0 jika ia berhasil berakhir tanpa kesalahan atau kode kesalahan lain jika tidak dimatikan dengan benar. Contoh kasus: top. Jalankan top; echo $?lalu tekan Ctrl-C. Status yang dibuang di layar adalah 0.
Louis
1
Terima kasih telah menunjukkannya. Namun, poster secara khusus bertanya tentang menjaga kode keluar.
philipp2100