Câu trả lời:
Để dọn dẹp một số lộn xộn, trap
có thể được sử dụng. Nó có thể cung cấp một danh sách các thứ được thực thi khi có tín hiệu cụ thể đến:
trap "echo hello" SIGINT
nhưng cũng có thể được sử dụng để thực thi một cái gì đó nếu shell thoát ra:
trap "killall background" EXIT
Đó là một nội dung, vì vậy help trap
sẽ cung cấp cho bạn thông tin (hoạt động với bash). Nếu bạn chỉ muốn giết công việc nền, bạn có thể làm
trap 'kill $(jobs -p)' EXIT
Xem ra để sử dụng duy nhất '
, để ngăn chặn vỏ thay thế $()
ngay lập tức.
kill $(jobs -p)
không hoạt động trong dấu gạch ngang, bởi vì nó thực hiện thay thế lệnh trong một lớp con (xem Thay thế lệnh trong dấu gạch ngang)
killall background
cho là một giữ chỗ? background
không có trong trang người đàn ông ...
Điều này làm việc cho tôi (được cải thiện nhờ các bình luận viên):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX và nó cũng được xác nhận trên Ubuntu . Mặc dù vậy, có một wokaround obvoius :)
-$$
. Nó ước tính thành '- <PID> `vd -1234
. Trong trang man kill // trang dựng sẵn, một dấu gạch đầu dòng chỉ định tín hiệu được gửi. Tuy nhiên - có thể chặn điều đó, nhưng sau đó dấu gạch ngang hàng đầu không có giấy tờ khác. Có ai giúp đỡ không?
man 2 kill
, giải thích rằng khi một PID âm, tín hiệu được gửi đến tất cả các quy trình trong nhóm quy trình với ID được cung cấp ( en.wikipedia.org/wiki/Process_group ). Thật khó hiểu khi điều này không được đề cập trong man 1 kill
hoặc man bash
, và có thể được coi là một lỗi trong tài liệu.
Cập nhật: https://stackoverflow.com/a/53714583/302079 cải thiện điều này bằng cách thêm trạng thái thoát và chức năng dọn dẹp.
trap "exit" INT TERM
trap "kill 0" EXIT
Tại sao phải chuyển đổi INT
và TERM
thoát? Bởi vì cả hai nên kích hoạt kill 0
mà không cần vào một vòng lặp vô hạn.
Tại sao kích hoạt kill 0
trên EXIT
? Bởi vì lối thoát bình thường cũng sẽ kích hoạt kill 0
.
Tại sao kill 0
? Bởi vì các subshells lồng nhau cũng cần phải bị giết. Điều này sẽ đưa xuống toàn bộ cây quá trình .
kill 0
có nghĩa là / không?
Các trap 'kill 0' SIGINT SIGTERM EXIT
giải pháp được mô tả trong câu trả lời @ tokland của thực sự tốt đẹp, nhưng mới nhất Bash treo với một lỗi segmantation khi sử dụng nó. Đó là bởi vì Bash, bắt đầu từ câu 4.3, cho phép đệ quy bẫy, trở thành vô hạn trong trường hợp này:
SIGINT
hoặc SIGTERM
hoặc EXIT
;kill 0
, gửi SIGTERM
đến tất cả các quy trình trong nhóm, bao gồm cả chính vỏ;Điều này có thể được giải quyết bằng cách hủy đăng ký bẫy thủ công:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Cách lạ mắt hơn, cho phép in tín hiệu thu được và tránh các thông báo "Đã kết thúc:":
#!/usr/bin/env bash
trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678
local func="$1"; shift
for sig in "$@"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf '\n%s\n' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
CẬP NHẬT : thêm ví dụ tối thiểu; cải thiện stop
chức năng để tránh các bẫy tín hiệu không cần thiết và để ẩn các thông báo "Đã kết thúc:" khỏi đầu ra. Cảm ơn Trevor Boyd Smith vì những gợi ý!
stop()
bạn cung cấp đối số đầu tiên là số tín hiệu nhưng sau đó bạn mã hóa cứng những tín hiệu nào đang được hủy đăng ký. thay vì mã hóa cứng các tín hiệu được hủy đăng ký, bạn có thể sử dụng đối số đầu tiên để hủy đăng ký stop()
hàm (làm như vậy sẽ có khả năng dừng các tín hiệu đệ quy khác (trừ 3 mã hóa cứng)).
SIGINT
, nhưng kill 0
sẽ gửi SIGTERM
, nó sẽ bị kẹt lại một lần nữa. Tuy nhiên, điều này sẽ không tạo ra đệ quy vô hạn, bởi vì SIGTERM
sẽ bị loại bỏ trong stop
cuộc gọi thứ hai .
trap - $1 && kill -s $1 0
nên làm việc tốt hơn. Tôi sẽ kiểm tra và cập nhật câu trả lời này. Cảm ơn bạn cho ý tưởng tốt đẹp! :)
trap - $1 && kill -s $1 0
sẽ không làm việc quá, vì chúng ta không thể giết chết EXIT
. Nhưng nó thực sự đủ để làm bẫy TERM
, bởi vì kill
gửi tín hiệu này theo mặc định.
EXIT
, trap
trình xử lý tín hiệu luôn chỉ được thực hiện một lần.
Để an toàn hơn, tôi thấy tốt hơn là xác định chức năng dọn dẹp và gọi nó từ bẫy:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
hoặc tránh chức năng hoàn toàn:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Tại sao? Bởi vì chỉ cần sử dụng trap 'kill $(jobs -pr)' [...]
một giả định rằng sẽ có các công việc nền chạy khi điều kiện bẫy được báo hiệu. Khi không có việc làm, người ta sẽ thấy thông báo sau (hoặc tương tự):
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
bởi vì jobs -pr
trống rỗng - tôi đã kết thúc trong 'cái bẫy' đó (ý định chơi chữ).
[ -n "$(jobs -pr)" ]
này không hoạt động trên bash của tôi. Tôi sử dụng GNU bash, phiên bản 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Thông báo "giết: sử dụng" liên tục xuất hiện.
jobs -pr
không trả lại các PID của con của các quá trình nền. Nó không xé toàn bộ cây quá trình, chỉ cắt bỏ rễ.
Một phiên bản đẹp hoạt động trong Linux, BSD và MacOS X. Đầu tiên hãy thử gửi SIGTERM và nếu nó không thành công, sẽ giết quá trình sau 10 giây.
KillJobs() {
for job in $(jobs -p); do
kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
done
}
TrapQuit() {
# Whatever you need to clean here
KillJobs
}
trap TrapQuit EXIT
Xin lưu ý rằng các công việc không bao gồm các quy trình con lớn.
function cleanup_func {
sleep 0.5
echo cleanup
}
trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT
# exit 1
# exit 0
Giống như https://stackoverflow.com/a/22644006/10082476 , nhưng có thêm mã thoát
công việc -p không hoạt động trong tất cả các shell nếu được gọi trong shell phụ, có thể trừ khi đầu ra của nó được chuyển hướng vào một tệp nhưng không phải là một đường ống. (Tôi cho rằng ban đầu nó chỉ nhằm mục đích sử dụng tương tác.)
Điều gì về những điều sau đây:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Yêu cầu gọi "công việc" là cần thiết với shell dash của Debian, không cập nhật công việc hiện tại ("%%") nếu nó bị thiếu.
Tôi đã điều chỉnh câu trả lời của @ tokland kết hợp với kiến thức từ http://veithen.github.io/2014/11/16/sigterm-propagation.html khi tôi nhận thấy điều trap
đó không kích hoạt nếu tôi đang chạy quy trình nền trước (không được nền với &
):
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT
echo $@
"$@" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
Ví dụ về nó hoạt động:
$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1] + 31568 suspended bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100
niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100
niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep
$ bg
[1] + 31568 continued bash killable-shell.sh sleep 100
$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1] + 31568 terminated bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
Để đa dạng, tôi sẽ đăng biến thể của https://stackoverflow.com/a/2173421/102484 , vì giải pháp đó dẫn đến thông báo "Đã chấm dứt" trong môi trường của tôi:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT