GNU/Linux >> Belajar Linux >  >> Linux

Tidak Dapat Menghentikan Skrip Bash Dengan Ctrl+c?

Saya menulis skrip bash sederhana dengan loop untuk mencetak tanggal dan melakukan ping ke mesin jarak jauh:

#!/bin/bash
while true; do
    #     *** DATE: Thu Sep 17 10:17:50 CEST 2015  ***
    echo -e "\n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
done

Ketika saya menjalankannya dari terminal, saya tidak dapat menghentikannya dengan Ctrl+C .
Sepertinya mengirim ^C ke terminal, tetapi skrip tidak berhenti.

MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C                                                          <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms

Tidak peduli berapa kali saya menekannya atau seberapa cepat saya melakukannya. Saya tidak bisa menghentikannya.
Lakukan tes dan realisasikan sendiri.

Sebagai solusi sampingan, saya menghentikannya dengan Ctrl+Z , yang menghentikannya dan kemudian kill %1 .

Apa yang sebenarnya terjadi di sini dengan ^C ?

Jawaban yang Diterima:

Apa yang terjadi adalah keduanya bash dan ping menerima SIGINT (bash menjadi tidak interaktif, keduanya ping dan bash dijalankan dalam grup proses yang sama yang telah dibuat dan ditetapkan sebagai grup proses latar depan terminal oleh shell interaktif tempat Anda menjalankan skrip tersebut).

Namun, bash menangani SIGINT itu secara asinkron, hanya setelah perintah yang sedang berjalan telah keluar. bash hanya keluar setelah menerima SIGINT itu jika perintah yang sedang berjalan mati dari SIGINT (yaitu status keluarnya menunjukkan bahwa perintah itu telah dimatikan 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, tetapi sh keluar secara normal dengan 0 kode keluar, jadi bash mengabaikan SIGINT, itulah sebabnya kami melihat "di sini".

ping , setidaknya yang dari iputils, berperilaku seperti itu. Ketika terputus, ia mencetak statistik dan keluar dengan status keluar 0 atau 1 tergantung pada apakah pingnya dibalas atau tidak. Jadi, ketika Anda menekan Ctrl-C sambil ping sedang berjalan, bash perhatikan bahwa Anda telah menekan Ctrl-C di penangan SIGINT-nya, tetapi sejak ping keluar secara normal, bash tidak keluar.

Jika Anda menambahkan sleep 1 di loop itu dan tekan Ctrl-C saat sleep sedang berjalan, karena sleep tidak memiliki handler khusus pada SIGINT, ia akan mati dan melapor ke bash bahwa itu mati karena SIGINT, dan dalam hal ini bash akan keluar (ia akan mati sendiri dengan SIGINT untuk melaporkan gangguan tersebut kepada induknya).

Terkait:#!/bin/bash – tidak ada file atau direktori seperti itu?

Mengapa bash berperilaku seperti itu, saya tidak yakin dan saya perhatikan perilaku itu tidak selalu deterministik. Saya baru saja mengajukan pertanyaan di bash milis pengembangan (Pembaruan :@Jilles sekarang telah menemukan alasan dalam jawabannya).

Satu-satunya shell lain yang saya temukan berperilaku serupa adalah ksh93 (Pembaruan, seperti yang disebutkan oleh @Jilles, begitu juga FreeBSD sh ). Di sana, SIGINT tampaknya diabaikan. Dan ksh93 keluar setiap kali perintah dimatikan oleh SIGINT.

Anda mendapatkan perilaku yang sama dengan bash di atas tetapi juga:

ksh -c 'sh -c "kill -INT \$\$"; echo test'

Tidak menampilkan "tes". Artinya, ia keluar (dengan membunuh dirinya sendiri dengan SIGINT di sana) jika perintah yang ditunggunya mati dari SIGINT, meskipun ia sendiri tidak menerima SIGINT itu.

Solusinya adalah menambahkan:

trap 'exit 130' INT

Di bagian atas skrip untuk memaksa bash untuk keluar setelah menerima SIGINT (perhatikan bahwa bagaimanapun juga, SIGINT tidak akan diproses secara sinkron, hanya setelah perintah yang sedang berjalan telah keluar).

Idealnya, kami ingin melaporkan kepada orang tua kami bahwa kami meninggal karena SIGINT (sehingga jika itu adalah bash lain skrip misalnya, bash . itu skrip juga terganggu). Melakukan exit 130 tidak sama dengan sekarat SIGINT (meskipun beberapa shell akan mengatur $? ke nilai yang sama untuk kedua kasus), namun sering digunakan untuk melaporkan kematian oleh SIGINT (pada sistem di mana SIGINT adalah yang paling banyak).

Namun untuk bash , ksh93 atau FreeBSD sh , itu tidak berhasil. Status keluar 130 itu tidak dianggap sebagai kematian oleh SIGINT dan skrip induk tidak batalkan 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

Linux
  1. Bagaimana Menghentikan Skrip Loop Bash Di Terminal?

  2. Perintah Rm Dalam Skrip Bash Tidak Bekerja Dengan Variabel?

  3. nama dasar dengan spasi dalam skrip bash?

  1. Penerjemah Shell mana yang Menjalankan Skrip Tanpa Shebang?

  2. Skrip bash menganggur hingga peristiwa CTRL+c dicatat

  3. Menambahkan ke $PYTHONPATH dengan skrip bash

  1. Skrip Bash Dengan `set -e` Tidak Berhenti Pada Perintah `… &&...`?

  2. Panggil skrip Python dari bash dengan argumen

  3. Izin ditolak dengan bash.sh untuk menjalankan cron