Làm cách nào tôi có thể giết và chờ các quá trình nền kết thúc trong tập lệnh shell khi tôi Ctrl + C?


24

Tôi đang cố gắng thiết lập một tập lệnh shell để nó chạy các quy trình nền và khi tôi Ctrlctập lệnh shell, nó sẽ giết chết các con, sau đó thoát ra.

Điều tốt nhất mà tôi quản lý để đưa ra là điều này. Dường như kill 0 -INTcũng giết chết tập lệnh trước khi chờ đợi xảy ra, vì vậy tập lệnh shell chết trước khi trẻ em hoàn thành.

Bất cứ ý tưởng nào về cách tôi có thể làm cho kịch bản shell này chờ đợi những đứa trẻ chết sau khi gửi 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


Tôi đã tìm thấy câu hỏi này bằng cách tìm kiếm "trình chặn khóa chặn", nhờ chia sẻ lệnh bẫy, rất hữu ích khi thực hiện lệnh sau khi thoát khỏi ứng dụng bằng Ctrl + C khi ứng dụng không thoát chính xác thông qua nút GUI đóng.
baptx

Câu trả lời:


22

killLệnh của bạn là ngược.

Giống như nhiều lệnh UNIX, các tùy chọn bắt đầu bằng dấu trừ phải đến trước, trước các đối số khác.

Nếu bạn viết

kill -INT 0

nó xem -INTtùy chọn này và gửi SIGINTđến 0( 0là một số đặc biệt có nghĩa là tất cả các quy trình trong nhóm quy trình hiện tại).

Nhưng nếu bạn viết

kill 0 -INT

Nó thấy 0, quyết định không có thêm tùy chọn, vì vậy sử dụng SIGTERMtheo mặc định. Và gửi cho nhóm quy trình hiện tại, giống như khi bạn đã làm

kill -TERM 0 -INT    

(nó cũng sẽ cố gửi SIGTERMđến -INT, điều này sẽ gây ra lỗi cú pháp, nhưng nó sẽ gửi SIGTERMđến 0đầu tiên và không bao giờ đi xa đến thế.)

Vì vậy, kịch bản chính của bạn đang nhận được SIGTERMtrước khi nó chạy waitecho DONE.

Thêm vào

trap 'echo got SIGTERM' TERM

ở trên cùng, ngay sau

trap 'killall' INT

và chạy nó một lần nữa để chứng minh điều này.

Như Stephane Chazelas chỉ ra, những đứa trẻ của bạn ( process1, v.v.) sẽ SIGINTmặc định bỏ qua .

Trong mọi trường hợp, tôi nghĩ gửi SIGTERMsẽ có ý nghĩa hơn.

Cuối cùng, tôi không chắc liệu có kill -process groupđược đảm bảo đi đến trẻ em trước không. Bỏ qua các tín hiệu trong khi tắt có thể là một ý tưởng tốt.

Vì vậy, hãy thử điều này:

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

Cảm ơn, điều này làm việc! Điều đó dường như đã được vấn đề.
trượt

9

Thật không may, các lệnh bắt đầu trong nền được thiết lập bởi shell để bỏ qua SIGINT và tệ hơn nữa, chúng không thể bỏ qua nó với trap. Nếu không, tất cả những gì bạn phải làm là

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

Bởi vì process1 và process2 sẽ nhận được SIGINT khi bạn nhấn Ctrl-C vì chúng là một phần của cùng một nhóm quy trình, là nhóm quy trình tiền cảnh của thiết bị đầu cuối.

Mã ở trên sẽ hoạt động với pdksh và zsh mà về mặt đó không phải là tuân thủ POSIX.

Với các shell khác, bạn sẽ phải sử dụng một cái gì đó khác để khôi phục trình xử lý mặc định cho SIGINT như:

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

hoặc sử dụng tín hiệu khác như SIGTERM.


2

Đối với những người chỉ muốn giết một quá trình và chờ cho nó chết, nhưng không phải là vô thời hạn :

Nó chờ tối đa 60 giây cho mỗi loại tín hiệu.

Cảnh báo: Câu trả lời này không có cách nào liên quan đến việc định hình tín hiệu tiêu diệt và gửi đi.

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

Nó chọn ứng dụng để giết theo tên hoặc đối số. Giữ dấu ngoặc cho chữ cái đầu tiên của tên ứng dụng để tránh khớp chính grep.

Với một số thay đổi, bạn có thể trực tiếp sử dụng PID hoặc đơn giản hơn pidof process_namethay vìps câu lệnh.

Chi tiết mã: grep cuối cùng là để có được PID mà không có dấu cách.


1

Nếu những gì bạn muốn là quản lý một số quy trình nền, tại sao không sử dụng các tính năng kiểm soát công việc 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

0

Vì vậy, tôi đã mày mò xung quanh với điều này là tốt. Trong Bash, bạn có thể định nghĩa các hàm và làm những thứ ưa thích với chúng. Đồng nghiệp của tôi và tôi sử dụng terminator, vì vậy đối với các lần thực thi hàng loạt, chúng tôi thường sinh ra một loạt các cửa sổ kết thúc nếu chúng tôi muốn xem đầu ra (kém thanh lịch hơntmux các tab, nhưng bạn có thể sử dụng giao diện giống GUI hơn haha).

Đây là thiết lập mà tôi đã đưa ra, cũng như một ví dụ về những thứ bạn có thể chạy:

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

Chạy nó

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

Chỉnh sửa @Maxim, chỉ cần thấy đề xuất của bạn, điều đó làm cho nó đơn giản hơn rất nhiều! Cảm ơn!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.