Bagaimana saya bisa membunuh dan menunggu proses latar belakang untuk menyelesaikan dalam skrip shell ketika saya Ctrl + C?

24

Saya mencoba membuat skrip shell sehingga menjalankan proses latar belakang, dan ketika saya Ctrlcskrip shell, itu membunuh anak-anak, lalu keluar.

Yang terbaik yang berhasil saya lakukan adalah ini. Tampaknya kill 0 -INTjuga membunuh skrip sebelum menunggu terjadi, sehingga skrip shell mati sebelum anak-anak selesai.

Ada ide tentang bagaimana saya bisa membuat skrip shell ini menunggu anak-anak mati setelah mengirim INT?

#!/bin/bash
trap 'killall' INT

killall() {
    echo "**** Shutting down... ****"
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever
slipheed
sumber
Saya menemukan pertanyaan ini dengan mencari "shell intercept kill switch", terima kasih telah berbagi perintah trap, sangat berguna untuk mengeksekusi perintah setelah keluar dari aplikasi dengan Ctrl + C ketika aplikasi tidak keluar dengan benar melalui tombol GUI tutup.
baptx

Jawaban:

22

killPerintah Anda mundur.

Seperti banyak perintah UNIX, opsi yang dimulai dengan minus harus didahulukan, sebelum argumen lain.

Jika kamu menulis

kill -INT 0

ia melihat -INTopsi, dan mengirim SIGINTke 0( 0adalah nomor khusus yang berarti semua proses dalam grup proses saat ini).

Tetapi jika Anda menulis

kill 0 -INT

ia melihat 0, memutuskan tidak ada lagi opsi, jadi gunakan SIGTERMsecara default. Dan mengirimkan bahwa untuk kelompok proses saat ini, sama seperti jika Anda lakukan

kill -TERM 0 -INT    

(Ini juga akan mencoba mengirim SIGTERMke -INT, yang akan menyebabkan kesalahan sintaks, tetapi mengirim SIGTERMke 0pertama, dan tidak pernah sampai sejauh itu.)

Jadi skrip utama Anda adalah SIGTERMsebelum menjalankan waitdan echo DONE.

Menambahkan

trap 'echo got SIGTERM' TERM

di atas, tepat setelah

trap 'killall' INT

dan jalankan lagi untuk membuktikan ini.

Seperti yang ditunjukkan Stephane Chazelas, anak-anak Anda yang berlatar belakang ( process1, dll.) Akan mengabaikannya SIGINTsecara default.

Bagaimanapun, saya pikir pengiriman SIGTERMakan lebih masuk akal.

Akhirnya, saya tidak yakin apakah kill -process groupdijamin pergi ke anak-anak dulu. Mengabaikan sinyal saat mematikan mungkin merupakan ide yang bagus.

Jadi coba ini:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever
Mikel
sumber
Terima kasih, ini berhasil! Tampaknya masalah.
Slipheed
9

Sayangnya, perintah yang dimulai di latar belakang diatur oleh shell untuk mengabaikan SIGINT, dan lebih buruk lagi, mereka tidak bisa mengabaikannya dengan trap. Kalau tidak, yang harus Anda lakukan adalah

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Karena process1 dan process2 akan mendapatkan SIGINT ketika Anda menekan Ctrl-C karena mereka adalah bagian dari grup proses yang sama yang merupakan grup proses foreground terminal.

Kode di atas akan bekerja dengan pdksh dan zsh yang dalam hal itu tidak sesuai dengan POSIX.

Dengan shell lain, Anda harus menggunakan sesuatu yang lain untuk mengembalikan handler default untuk SIGINT seperti:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

atau gunakan sinyal yang berbeda seperti SIGTERM.

Stéphane Chazelas
sumber
2

Bagi mereka yang hanya ingin membunuh suatu proses dan menunggu sampai mati, tetapi tidak tanpa batas :

Itu menunggu maksimal 60 detik per jenis sinyal.

Peringatan: Jawaban ini sama sekali tidak terkait dengan menangkap sinyal mematikan dan mengirimkannya.

# close_app_sub GREP_STATEMENT SIGNAL DURATION_SEC
# GREP_STATEMENT must not match itself!
close_app_sub() {
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ ! -z "$APP_PID" ]; then
        echo "App is open. Trying to close app (SIGNAL $2). Max $3sec."
        kill $2 "$APP_PID"
        WAIT_LOOP=0
        while ps -p "$APP_PID" > /dev/null 2>&1; do
            sleep 1
            WAIT_LOOP=$((WAIT_LOOP+1))
            if [ "$WAIT_LOOP" = "$3" ]; then
                break
            fi
        done
    fi
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ -z "$APP_PID" ]; then return 0; else return "$APP_PID"; fi
}

close_app() {
    close_app_sub "$1" "-HUP" "60"
    close_app_sub "$1" "-TERM" "60"
    close_app_sub "$1" "-SIGINT" "60"
    close_app_sub "$1" "-KILL" "60"
    return $?
}

close_app "[f]irefox"

Ini memilih aplikasi untuk dibunuh berdasarkan nama atau argumen. Simpan tanda kurung untuk huruf pertama nama aplikasi untuk menghindari pencocokan grep itu sendiri.

Dengan beberapa perubahan, Anda dapat langsung menggunakan PID atau lebih sederhana pidof process_namedaripada pspernyataan.

Detail kode: Grep terakhir adalah untuk mendapatkan PID tanpa spasi tambahan.

KrisWebDev
sumber
1

Jika yang Anda inginkan adalah mengelola beberapa proses latar belakang, mengapa tidak menggunakan fitur kontrol pekerjaan bash?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs
Pepatah
sumber
0

Jadi saya bermain-main dengan ini juga. Di Bash, Anda dapat mendefinisikan fungsi dan melakukan hal-hal yang mewah dengan itu. Rekan kerja saya dan saya menggunakan terminator, jadi untuk eksekusi batch kita biasanya menelurkan sejumlah besar terminator windows jika kita ingin melihat output (kurang elegan daripada tmuxtab, tetapi Anda dapat menggunakan antarmuka yang lebih mirip GUI haha).

Inilah pengaturan yang saya buat, serta contoh hal-hal yang dapat Anda jalankan:

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

Lari itu

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

Sunting @ Maxim, baru saja melihat saran Anda, yang membuatnya menjadi lebih sederhana! Terima kasih!

eacousineau
sumber