Mengapa saya tidak dapat membunuh batas waktu yang dipanggil dari skrip Bash dengan penekanan tombol?

11

[Sunting: Ini terlihat mirip dengan beberapa pertanyaan lain yang menanyakan cara membunuh semua proses yang muncul - semua jawaban tampaknya menggunakan pkill. Jadi inti dari pertanyaan saya mungkin: Apakah ada cara untuk menyebarkan Ctrl-C / Z ke semua proses yang dihasilkan oleh skrip?]

Saat memanggil SoX recdengan timeoutperintah dari coreutils (dibahas di sini ), sepertinya tidak ada cara untuk membunuhnya dengan keystroke setelah dipanggil dari dalam skrip Bash.

Contoh:

timeout 10 rec test.wav

... dapat dibunuh dengan Ctrl+ Catau Ctrl+ Zdari bash, tetapi tidak ketika dipanggil dari dalam skrip.

timeout 10 ping nowhere

... dapat dibunuh dengan Ctrl+ Catau Ctrl+ Zdari bash, dan dengan Ctrl+ Zketika dijalankan dari dalam skrip.

Saya dapat menemukan ID proses dan mematikannya seperti itu, tetapi mengapa saya tidak dapat menggunakan keystroke break standar? Dan apakah ada cara untuk menyusun skrip saya sehingga saya bisa?

meetar
sumber
2
Ctrl + Z tidak mematikan proses, hanya menjeda prosesnya. Mereka akan tetap berjalan jika Anda memberikan bgdari fgperintah. Lagi pula, apakah ada perbedaan antara contoh 1 dan 3d Anda?
terdon
Saya tidak memiliki timeoutpada sistem saya tetapi membunuh sleepkarya apakah itu diketik langsung pada baris perintah, bersumber, dieksekusi, atau melewati penerjemah secara eksplisit
Kevin
@terdon Terima kasih, saya telah menjelaskan contoh-contohnya.
meetar

Jawaban:

18

Kunci sinyal seperti Ctrl+ Cmengirim sinyal ke semua proses dalam grup proses latar depan .

Dalam kasus khas, grup proses adalah pipa. Misalnya, dalam head <somefile | sort, proses yang berjalan headdan proses yang berjalan sortberada dalam kelompok proses yang sama, seperti halnya shell, sehingga mereka semua menerima sinyal. Saat Anda menjalankan pekerjaan di latar belakang ( somecommand &), pekerjaan itu berada di grup prosesnya sendiri, jadi menekan Ctrl+ Ctidak memengaruhinya.

The timeoutProgram menempatkan diri dalam kelompok proses sendiri. Dari kode sumber:

/* Ensure we're in our own group so all subprocesses can be killed.
   Note we don't just put the child in a separate group as
   then we would need to worry about foreground and background groups
   and propagating signals between them.  */
setpgid (0, 0);

Ketika timeout terjadi, timeoutberjalan melalui cara sederhana membunuh kelompok proses yang menjadi anggotanya. Karena telah menempatkan dirinya dalam grup proses terpisah, proses induknya tidak akan berada dalam grup. Menggunakan grup proses di sini memastikan bahwa jika aplikasi anak bercabang menjadi beberapa proses, semua prosesnya akan menerima sinyal.

Ketika Anda menjalankan timeoutlangsung pada baris perintah dan tekan Ctrl+ C, SIGINT yang dihasilkan diterima oleh timeoutdan oleh proses anak, tetapi tidak oleh shell interaktif yang merupakan timeoutproses induk. Ketika timeoutdipanggil dari skrip, hanya shell yang menjalankan skrip yang menerima sinyal: timeouttidak mendapatkannya karena berada di grup proses yang berbeda.

Anda dapat mengatur penangan sinyal dalam skrip shell dengan trapbuiltin. Sayangnya, itu tidak sesederhana itu. Pertimbangkan ini:

#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date

Jika Anda menekan Ctrl+ Csetelah 2 detik, ini masih menunggu 5 detik penuh, lalu cetak pesan "Terganggu". Itu karena shell menahan diri dari menjalankan kode jebakan sementara pekerjaan latar depan aktif.

Untuk mengatasinya, jalankan pekerjaan di latar belakang. Di penangan sinyal, panggilan killuntuk menyampaikan sinyal ke timeoutgrup proses.

#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Sangat licik - bekerja dengan indah! Saya jauh lebih pintar sekarang, merci!
meetar
13

Membangun jawaban luar biasa yang diberikan oleh Gilles. Perintah batas waktu memiliki opsi latar depan, jika digunakan maka CTRL + C akan keluar dari perintah batas waktu.

#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date
Marwan Alsabbagh
sumber
Ini adalah jawaban yang bagus - lebih sederhana dari jawaban yang diterima dan bekerja dengan baik untuk saya, menggunakan timeoutdari GNU coreutils.
RichVel
1

Pada dasarnya Ctrl+ Cmengirim SIGINTsinyal, sedangkan Ctrl+ Z mengirim SIGTSTPsinyal.

SIGTSTPhanya menghentikan proses, SIGCONTakan melanjutkannya.

Ini bekerja pada foreground-proses yang bercabang pada baris perintah.

Jika proses Anda adalah proses latar belakang, Anda harus mengirim sinyal itu ke proses dengan cara yang berbeda. killakan melakukan itu. Secara teori, operator "-" - pada pembunuhan itu juga harus menandakan proses anak tetapi ini jarang bekerja seperti yang diharapkan.

Untuk bacaan lebih lanjut: Unix-Sinyal

Nils
sumber
Ini benar, setidaknya hingga "jarang bekerja seperti yang diharapkan" (yang tergantung pada harapan Anda - Anda harus membaca tentang kelompok proses), tetapi sama sekali tidak relevan. Pertanyaannya tidak mengkhianati kebingungan tentang SIGINT dan SIGTSTP.
Gilles 'SO- berhenti bersikap jahat'
0

Meningkatkan jawaban @Gilles dengan memberikan pendekatan "temukan dan ganti" untuk skrip yang ada:

  1. Tambahkan potongan ini di awal kode Anda:
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}
  1. Tambahkan (atau gabungkan) kode berikut ke dalam INThandler Anda :
pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            #echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
  1. Ganti timeoutperintah dengan my_timeoutfungsi di skrip Anda.

Contoh

Berikut ini contoh skrip:

#!/bin/bash

# see "killing timeout": /unix//a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}

cleanup(){
    echo "-----------------------------------------"
    echo "Restoring previous routing table settings"
}

pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
trap cleanup EXIT

echo "Executing 5 temporary timeouts..."
unreachable_ip="192.168.44.5"
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
echo "ctrl+c now to execute cleanup"
my_timeout 9s ping -c 1 "$unreachable_ip" &> /dev/null 
ceremcem
sumber