ฉันจะฆ่าและรอให้กระบวนการส่วนหลังเสร็จสิ้นในเชลล์สคริปต์ได้อย่างไรเมื่อฉัน Ctrl + C


24

ฉันกำลังพยายามตั้งค่าเชลล์สคริปต์เพื่อให้ทำงานในกระบวนการแบ็คกราวน์และเมื่อฉันCtrlcเชลล์สคริปมันจะฆ่าเด็ก ๆ แล้วก็ออกไป

สิ่งที่ดีที่สุดที่ฉันสามารถทำได้คือ ดูเหมือนว่าkill 0 -INTสคริปต์ยังฆ่าสคริปต์ก่อนที่จะเกิดการรอดังนั้นเชลล์สคริปต์จะตายก่อนที่เด็ก ๆ จะเสร็จสมบูรณ์

ความคิดใด ๆ เกี่ยวกับวิธีการที่ฉันสามารถทำให้รอเชลล์สคริปต์สำหรับเด็กที่จะตายหลังจากที่ส่ง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

3
ไม่ข้ามโพสต์ serverfault.com/questions/448054
Patrick

ฉันพบคำถามนี้โดยค้นหา "shell intercept kill switch" ขอบคุณสำหรับการแชร์คำสั่ง trap มันมีประโยชน์มากในการรันคำสั่งหลังจากออกจากแอปด้วย Ctrl + C เมื่อแอปไม่ออกอย่างถูกต้องผ่านปุ่มปิด GUI
baptx

คำตอบ:


22

killคำสั่งของคุณย้อนกลับ

เช่นเดียวกับคำสั่ง UNIX หลายตัวเลือกที่ขึ้นต้นด้วยเครื่องหมายลบจะต้องมาก่อนก่อนอาร์กิวเมนต์อื่น

ถ้าคุณเขียน

kill -INT 0

มันเห็น-INTเป็นตัวเลือกและส่งSIGINTไปยัง0( 0เป็นหมายเลขพิเศษหมายถึงกระบวนการทั้งหมดในกลุ่มกระบวนการปัจจุบัน)

แต่ถ้าคุณเขียน

kill 0 -INT

มันเห็น0, ตัดสินใจว่าไม่มีตัวเลือกเพิ่มเติมดังนั้นจึงใช้งานSIGTERMตามค่าเริ่มต้น และส่งที่ไปกลุ่มกระบวนการปัจจุบันเช่นเดียวกับถ้าคุณทำ

kill -TERM 0 -INT    

(มันจะลองส่งSIGTERMไป-INTซึ่งจะทำให้เกิดข้อผิดพลาดทางไวยากรณ์ แต่จะส่งSIGTERMไปที่0ก่อนและไม่เคยไปไกลขนาดนั้น)

ดังนั้นสคริปต์หลักของคุณจะได้รับSIGTERMก่อนที่จะได้รับการเรียกใช้และwaitecho DONE

เพิ่ม

trap 'echo got SIGTERM' TERM

ที่ด้านบนหลังจาก

trap 'killall' INT

และเรียกใช้อีกครั้งเพื่อพิสูจน์สิ่งนี้

เมื่อ Stephane Chazelas ชี้เด็กที่มีพื้นหลัง ( process1ฯลฯ ) ของคุณจะไม่สนใจSIGINTตามค่าเริ่มต้น

ไม่ว่าในกรณีใดฉันคิดว่าการส่งข้อความSIGTERMจะสมเหตุสมผลมากกว่า

ในที่สุดฉันไม่แน่ใจว่าkill -process groupจะรับประกันการไปหาลูกก่อนหรือไม่ การละเว้นสัญญาณในขณะที่ปิดเครื่องอาจเป็นความคิดที่ดี

ลองทำสิ่งนี้:

#!/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

ขอบคุณมันใช้งานได้! ดูเหมือนว่าจะมีปัญหา
slipheed

9

แต่น่าเสียดายที่คำสั่งเริ่มต้นในพื้นหลังที่กำหนดโดยเปลือกจะไม่สนใจ SIGINT trapและแย่กว่านั้นพวกเขาไม่สามารถยกเลิกการไม่สนใจมันด้วย มิฉะนั้นสิ่งที่คุณต้องทำคือ

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

เพราะ process1 และ process2 จะได้รับ SIGINT เมื่อคุณกด Ctrl-C เนื่องจากเป็นส่วนหนึ่งของกลุ่มกระบวนการเดียวกันซึ่งเป็นกลุ่มกระบวนการพื้นหน้าของเทอร์มินัล

รหัสข้างต้นจะทำงานกับ pdksh และ zsh ซึ่งในเรื่องนั้นไม่สอดคล้องกับ POSIX

ด้วยเชลล์อื่นคุณจะต้องใช้อย่างอื่นเพื่อกู้คืนตัวจัดการเริ่มต้นสำหรับ SIGINT เช่น:

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

หรือใช้สัญญาณอื่นเช่น SIGTERM


2

สำหรับผู้ที่ต้องการฆ่ากระบวนการและรอให้กระบวนการตาย แต่ไม่สิ้นสุด :

มันรอได้สูงสุด 60 วินาทีต่อประเภทสัญญาณ

คำเตือน: คำตอบนี้ไม่เกี่ยวข้องกับการดักจับสัญญาณการฆ่าและการส่งสัญญาณ

# 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"

มันเลือกแอปที่จะฆ่าตามชื่อหรือข้อโต้แย้ง เก็บวงเล็บสำหรับอักษรตัวแรกของชื่อแอพเพื่อหลีกเลี่ยง grep ที่ตรงกัน

ด้วยการเปลี่ยนแปลงบางอย่างคุณสามารถใช้ PID หรือง่ายกว่าpidof process_nameแทนที่จะเป็นpsคำสั่ง

รายละเอียดรหัส: grep สุดท้ายคือการรับ PID โดยไม่มีช่องว่างต่อท้าย


1

หากสิ่งที่คุณต้องการคือการจัดการกระบวนการพื้นหลังบางอย่างทำไมไม่ใช้คุณสมบัติการควบคุมงานทุบตี?

$ 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

0

ดังนั้นฉันจึงแก้ไขมันด้วยเช่นกัน ใน Bash คุณสามารถกำหนดฟังก์ชั่นและทำสิ่งต่าง ๆ กับสิ่งเหล่านั้นได้ เพื่อนร่วมงานของฉันและฉันใช้terminatorดังนั้นสำหรับการประมวลผลแบบแบทช์เรามักจะวางไข่หน้าต่างเทอร์มิเนเตอร์ทั้งกลุ่มถ้าเราต้องการดูผลลัพธ์ (สง่างามน้อยกว่าtmuxแท็บ

นี่เป็นเซ็ตอัพที่ฉันสร้างขึ้นมาพร้อมกับตัวอย่างของสิ่งที่คุณสามารถเรียกใช้:

#!/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

ใช้มัน

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

แก้ไข @ Maxax เพิ่งเห็นข้อเสนอแนะของคุณที่ทำให้ง่ายขึ้น! ขอบคุณ!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.