Apa yang terjadi adalah bahwa baik bash
dan ping
menerima SIGINT ( bash
menjadi tidak interaktif, baik ping
dan bash
berjalan dalam kelompok proses yang sama yang telah dibuat dan ditetapkan sebagai kelompok proses latar depan terminal oleh shell interaktif Anda berlari bahwa script dari).
Namun, bash
menangani SIGINT itu secara tidak sinkron, hanya setelah perintah yang saat ini berjalan telah keluar. bash
hanya keluar setelah menerima SIGINT itu jika perintah yang sedang berjalan mati karena SIGINT (yaitu status keluarnya menunjukkan bahwa ia telah dibunuh oleh SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Di atas, bash
, sh
dan sleep
menerima SIGINT ketika saya menekan Ctrl-C, tapi sh
keluar biasanya dengan kode 0 keluar, sehingga bash
mengabaikan SIGINT, yang sebabnya kita melihat "di sini".
ping
, setidaknya yang dari iputils, berperilaku seperti itu. Ketika terganggu, ia mencetak statistik dan keluar dengan status keluar 0 atau 1 tergantung pada apakah pingnya dijawab atau tidak. Jadi, ketika Anda menekan Ctrl-C saat ping
sedang berjalan, bash
perhatikan bahwa Anda telah menekan Ctrl-C
di penangan SIGINT-nya, tetapi karena ping
keluar secara normal, bash
tidak keluar.
Jika Anda menambahkan sleep 1
dalam loop itu dan tekan Ctrl-C
saat sleep
sedang berjalan, karena sleep
tidak memiliki penangan khusus pada SIGINT, itu akan mati dan melaporkan bash
bahwa itu mati karena SIGINT, dan dalam kasus itubash
akan keluar (itu benar-benar akan bunuh diri dengan SIGINT sehingga untuk melaporkan gangguan ke induknya).
Mengenai mengapa bash
berperilaku seperti itu, saya tidak yakin dan saya perhatikan perilaku tidak selalu deterministik. Saya baru saja mengajukan pertanyaan pada bash
milis pengembangan ( Pembaruan : @Jilles sekarang telah menemukan alasannya dalam jawabannya ).
Satu-satunya shell lain yang saya temukan berperilaku serupa adalah ksh93 (Pembaruan, seperti yang disebutkan oleh @Jilles, begitu juga FreeBSDsh
). Di sana, SIGINT tampaknya benar-benar diabaikan. Dan ksh93
keluar setiap kali perintah dibunuh oleh SIGINT.
Anda mendapatkan perilaku yang sama seperti di bash
atas tetapi juga:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Tidak menampilkan "tes". Yaitu, ia keluar (dengan membunuh dirinya sendiri dengan SIGINT di sana) jika perintah yang ditungguinya mati oleh SIGINT, bahkan jika itu, ia sendiri tidak menerima SIGINT itu.
Pekerjaan di sekitar adalah menambahkan:
trap 'exit 130' INT
Di bagian atas skrip untuk memaksa bash
keluar setelah menerima SIGINT (perhatikan bahwa dalam hal apa pun, SIGINT tidak akan diproses secara serempak, hanya setelah perintah yang saat ini berjalan telah keluar).
Idealnya, kami ingin melaporkan kepada orang tua kami bahwa kami meninggal karena SIGINT (sehingga jika itu adalah bash
skrip lain misalnya, bash
skrip itu juga terputus). Melakukan exit 130
tidak sama dengan sekarat SIGINT (meskipun beberapa shell akan diatur$?
nilai yang sama untuk kedua kasus), namun sering digunakan untuk melaporkan kematian oleh SIGINT (pada sistem di mana SIGINT adalah 2 yang paling banyak).
Namun untuk bash
, ksh93
atau FreeBSD sh
, itu tidak berhasil. 130 status keluar tidak dianggap sebagai kematian oleh SIGINT dan skrip induk tidak akan dibatalkan di sana.
Jadi, alternatif yang mungkin lebih baik adalah bunuh diri dengan SIGINT setelah menerima SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Jika pengguna mengetik Ctrl + C saat mengedit salah satu file,vi
cukup tampilkan pesan. Tampaknya masuk akal bahwa perulangan harus dilanjutkan setelah pengguna selesai mengedit file. (Dan ya, saya tahu Anda bisa mengatakanvi *.txt; cp *.txt newdir
; Saya hanya mengirimkanfor
perulangan sebagai contoh.)vi
(vim
setidaknya setidaknya) tidak menonaktifkan ttyisig
saat mengedit (itu tidak jelas ketika Anda menjalankan:!cmd
, dan itu akan sangat berlaku dalam kasus itu).ping
keluar dengan 0 setelah menerima SIGINT. Saya menemukan perilaku serupa ketika skrip bash berisisudo
bukanping
, tetapisudo
keluar dengan 1 setelah menerima SIGINT. unix.stackexchange.com/questions/479023/…Penjelasannya adalah bahwa bash mengimplementasikan WCE (tunggu dan keluar bersama) untuk SIGINT dan SIGQUIT per http://www.cons.org/cracauer/sigint.html . Itu berarti bahwa jika bash menerima SIGINT atau SIGQUIT sambil menunggu proses untuk keluar, itu akan menunggu sampai proses keluar dan akan keluar sendiri jika proses keluar pada sinyal itu. Ini memastikan bahwa program yang menggunakan SIGINT atau SIGQUIT di antarmuka penggunanya akan berfungsi seperti yang diharapkan (jika sinyal tidak menyebabkan program berhenti, skrip akan berlanjut secara normal).
Kelemahan muncul dengan program yang menangkap SIGINT atau SIGQUIT tetapi kemudian berhenti karena itu tetapi menggunakan keluar normal () alih-alih dengan mengirim ulang sinyal ke diri mereka sendiri. Mungkin tidak dapat mengganggu skrip yang memanggil program semacam itu. Saya pikir perbaikan nyata ada dalam program-program seperti ping dan ping6.
Perilaku serupa diimplementasikan oleh ksh93 dan FreeBSD / bin / sh, tetapi tidak oleh sebagian besar shell lainnya.
sumber
exit(130)
misalnya misalnya jika Anda mengganggumksh -c 'sleep 10;:'
).Ketika Anda menduga, ini karena SIGINT dikirim ke proses bawahan, dan shell berlanjut setelah proses itu keluar.
Untuk menangani ini dengan cara yang lebih baik, Anda dapat memeriksa status keluar dari perintah yang sedang berjalan. Kode pengembalian Unix mengkode metode yang keluar dari proses (panggilan atau sinyal sistem) dan nilai apa yang diteruskan
exit()
atau sinyal apa yang menghentikan proses. Ini semua agak rumit, tetapi cara tercepat untuk menggunakannya adalah mengetahui bahwa proses yang diakhiri oleh sinyal akan memiliki kode pengembalian yang tidak nol. Dengan demikian, jika Anda memeriksa kode kembali dalam skrip Anda, Anda dapat keluar sendiri jika proses anak dihentikan, menghilangkan kebutuhan akan kekurangan-kekurangan sepertisleep
panggilan yang tidak perlu . Cara cepat untuk melakukan ini di seluruh skrip Anda adalah dengan menggunakanset -e
, meskipun mungkin memerlukan beberapa penyesuaian untuk perintah yang status keluarnya merupakan nol yang diharapkan.sumber
ping
kembali dengan status keluar 0 setelah menerima SIGINT dan yangbash
kemudian mengabaikan SIGINT yang diterimanya sendiri jika memang demikian. Menambahkan "set -e" atau memeriksa status keluar tidak akan membantu di sini. Menambahkan perangkap eksplisit pada SIGINT akan membantu.Terminal memperhatikan kontrol-c dan mengirimkan
INT
sinyal ke grup proses latar depan, yang di sini mencakup shell, karenaping
belum membuat grup proses latar depan baru. Ini mudah diverifikasi dengan menjebakINT
.Jika perintah yang dijalankan telah membuat grup proses foreground baru, maka control-c akan pergi ke grup proses itu, dan bukan ke shell. Dalam hal ini, shell perlu memeriksa kode keluar, karena tidak akan diberi sinyal oleh terminal.
(
INT
Penanganan pada kulit dapat luar biasa rumit, dengan cara, sebagai shell kadang-kadang perlu untuk mengabaikan sinyal, dan kadang-kadang tidak Sumber menyelam jika penasaran, atau merenungkan:.tail -f /etc/passwd; echo foo
)sumber
ping
tidak punya alasan untuk memulai grup proses baru di sini dan versi ping (iputils pada Debian) yang dengannya saya dapat mereproduksi masalah OP tidak membuat grup proses.Yah, saya mencoba menambahkan
sleep 1
skrip bash, dan bang!Sekarang saya bisa menghentikannya dengan dua Ctrl+C.
Saat menekan Ctrl+C,
SIGINT
sinyal dikirim ke proses yang saat ini dieksekusi, yang perintahnya dijalankan di dalam loop. Kemudian, proses subkulit terus mengeksekusi perintah berikutnya dalam loop, yang memulai proses lain. Untuk dapat menghentikan skrip perlu mengirim duaSIGINT
sinyal, satu untuk mengganggu perintah saat ini dalam pelaksanaan dan satu untuk mengganggu proses subkulit .Dalam skrip tanpa
sleep
panggilan, menekan Ctrl+Csangat cepat dan berkali-kali sepertinya tidak berfungsi, dan tidak mungkin untuk keluar dari loop. Dugaan saya adalah bahwa menekan dua kali tidak cukup cepat untuk membuatnya tepat pada saat yang tepat antara gangguan proses yang dijalankan saat ini dan awal yang berikutnya. Setiap Ctrl+Cpenekanan akan mengirimkanSIGINT
ke proses yang dijalankan di dalam loop, tetapi tidak ke subshell .Dalam skrip dengan
sleep 1
, panggilan ini akan menunda eksekusi selama satu detik, dan ketika terganggu oleh yang pertama Ctrl+C(pertamaSIGINT
), subkulit akan membutuhkan waktu lebih banyak untuk menjalankan perintah berikutnya. Jadi sekarang, yang kedua Ctrl+C(keduaSIGINT
) akan menuju ke subkulit , dan eksekusi skrip akan berakhir.sumber
Coba ini:
Sekarang ubah baris pertama menjadi:
dan coba lagi - lihat apakah ping sekarang dapat disela.
sumber
misalnya
pgrep -f firefox
akan membuat PID berjalanfirefox
dan akan menyimpan PID ini ke file bernamaany_file_name
. Perintah 'sed' akan menambahkankill
di awal nomor PID dalam file 'any_file_name'. Baris ketiga akanany_file_name
mengajukan file yang dapat dieksekusi. Sekarang baris keempat akan membunuh PID yang tersedia dalam fileany_file_name
. Menulis empat baris di atas dalam file dan menjalankan file itu dapat melakukan Control- C. Bekerja sangat baik untuk saya.sumber
Jika ada yang tertarik pada perbaikan untuk
bash
fitur ini , dan tidak terlalu banyak dengan filosofi di baliknya , berikut ini adalah proposal:Jangan menjalankan perintah yang bermasalah secara langsung, tetapi dari pembungkus yang a) menunggu sampai terminasi b) tidak mengacaukan sinyal dan c) tidak mengimplementasikan mekanisme WCE itu sendiri, tetapi mati begitu saja ketika menerima a
SIGINT
.Pembungkus tersebut dapat dibuat dengan
awk
+ nyasystem()
fungsi.Masukkan skrip seperti OP:
sumber
Anda adalah korban bug bash yang terkenal. Bash melakukan kontrol pekerjaan untuk skrip yang merupakan kesalahan.
Apa yang terjadi adalah bahwa bash menjalankan program eksternal dalam grup proses yang berbeda dari yang digunakan untuk skrip itu sendiri. Karena TTY processgroup diatur ke processgroup dari proses foreground saat ini, hanya proses foreground ini yang dimatikan dan loop dalam skrip shell berlanjut.
Untuk memverifikasi: Ambil dan kompilasi Bourne Shell baru-baru ini yang mengimplementasikan pgrp (1) sebagai program bawaan, kemudian tambahkan / bin / sleep 100 (atau / usr / bin / sleep tergantung pada platform Anda) ke loop skrip dan kemudian mulai Bourne Shell. Setelah Anda menggunakan ps (1) untuk mendapatkan ID proses untuk perintah sleep dan bash yang menjalankan skrip, panggil
pgrp <pid>
dan ganti "<pid>" dengan ID proses sleep dan bash yang menjalankan skrip. Anda akan melihat ID grup proses yang berbeda. Sekarang panggil sesuatu sepertipgrp < /dev/pts/7
(ganti nama tty dengan tty yang digunakan oleh skrip) untuk mendapatkan grup proses tty saat ini. Grup proses TTY sama dengan grup proses dari perintah sleep.Untuk memperbaikinya: gunakan shell yang berbeda.
Sumber-sumber Bourne Shell baru-baru ini ada dalam paket alat schily saya yang dapat Anda temukan di sini:
http://sourceforge.net/projects/schilytools/files/
sumber
bash
itu? AFAIKbash
hanya melakukan itu jika Anda melewatkan opsi -m atau -i.bash -c 'ps -j; ps -j; ps -j'
)./bin/sh -ce
. Saya harus menambahkan solusi buruk ke dalamsmake
yang secara eksplisit membunuh kelompok proses untuk perintah yang sedang berjalan untuk mengizinkan^C
untuk membatalkan panggilan berlapis. Apakah Anda memeriksa apakah bash mengubah grup proses dari id grup proses yang diawali dengan?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
tidak melaporkan pgid yang sama untuk ps dan bash di semua 3 ps doa. (ARGV0 = sh adalahzsh
cara untuk melewatkan argv [0]).