Làm cách nào để giết các tiến trình / công việc nền khi tập lệnh shell của tôi thoát?


193

Tôi đang tìm cách dọn dẹp mớ hỗn độn khi kịch bản cấp cao nhất của tôi thoát ra.

Đặc biệt nếu tôi muốn sử dụng set -e, tôi ước quá trình chạy nền sẽ chết khi kịch bản thoát.

Câu trả lời:


186

Để dọn dẹp một số lộn xộn, trapcó 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 trapsẽ 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.


Sau đó, làm thế nào để bạn giết con chỉ? (hoặc tôi đang thiếu một cái gì đó rõ ràng)
elmarco

18
killall giết chết con cái của bạn, nhưng không phải bạn
orip

3
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)
user1431317

7
được killall backgroundcho là một giữ chỗ? backgroundkhông có trong trang người đàn ông ...
Evan Benn

171

Đ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
  • kill -- -$$gửi một SIGTERM cho toàn bộ nhóm quá trình, do đó giết chết con cháu.

  • Chỉ định tín hiệu EXITlà hữu ích khi sử dụng set -e(chi tiết ở đây ).


1
Nên hoạt động tốt trên toàn bộ, nhưng các quy trình con có thể thay đổi các nhóm quy trình. Mặt khác, nó không yêu cầu kiểm soát công việc và cũng có thể khiến một số quy trình cháu bị bỏ lỡ bởi các giải pháp khác.
michaeljt

5
Lưu ý, "kill 0" cũng sẽ giết tập lệnh bash cha. Bạn có thể muốn sử dụng "kill - - $ BASHPID" để chỉ giết những đứa trẻ của tập lệnh hiện tại. Nếu bạn không có $ BASHPID trong phiên bản bash của mình, bạn có thể xuất BASHPID = $ (sh -c 'echo $ PPID')
ACyclic

2
Cảm ơn bạn cho giải pháp tốt đẹp và rõ ràng! Thật không may, nó segfaults Bash 4.3, cho phép đệ quy bẫy. Tôi đã gặp vấn đề này trên 4.3.30(1)-releaseOSX và nó cũng được xác nhận trên Ubuntu . Mặc dù vậy, có một wokaround obvoius :)
skozin

4
Tôi không hiểu lắm -$$. 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?
Evan Benn

4
@EvanBenn: Kiểm tra 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 killhoặc man bash, và có thể được coi là một lỗi trong tài liệu.
user001

111

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 INTTERMthoát? Bởi vì cả hai nên kích hoạt kill 0mà không cần vào một vòng lặp vô hạn.

Tại sao kích hoạt kill 0trê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 .


3
Giải pháp duy nhất cho trường hợp của tôi trên Debian.
MindlessRanger

3
Cả câu trả lời của Johannes Schaub và câu trả lời do tokland cung cấp đều có thể giết chết các tiến trình nền của tập lệnh shell của tôi bắt đầu (trên Debian). Giải pháp này đã làm việc. Tôi không biết tại sao câu trả lời này không được nâng cao hơn. Bạn có thể mở rộng thêm về những gì chính xác kill 0có nghĩa là / không?
josch

7
Điều này thật tuyệt vời, nhưng cũng giết chết lớp vỏ cha mẹ của tôi :-(
vidstige

5
Giải pháp này là quá mức cần thiết. giết 0 (trong tập lệnh của tôi) đã phá hỏng toàn bộ phiên X của tôi! Có lẽ trong một số trường hợp, kill 0 có thể hữu ích, nhưng điều này không thay đổi thực tế rằng nó không phải là giải pháp chung và nên tránh nếu có thể trừ khi có lý do rất chính đáng để sử dụng nó. Sẽ rất tốt nếu thêm cảnh báo rằng nó có thể giết shell cha hoặc thậm chí toàn bộ phiên X, không chỉ các công việc nền của tập lệnh!
Lissanro Rayen

3
Mặc dù đây có thể là một giải pháp thú vị trong một số trường hợp, như được chỉ ra bởi @vidstige, điều này sẽ giết chết toàn bộ nhóm quy trình bao gồm quá trình khởi chạy (ví dụ như trình bao mẹ trong hầu hết các trường hợp). Chắc chắn không phải thứ bạn muốn khi bạn đang chạy tập lệnh thông qua IDE.
matpen

22

bẫy 'giết $ (công việc -p)' EXIT

Tôi sẽ chỉ thực hiện những thay đổi nhỏ đối với câu trả lời của Johannes và sử dụng các công việc -pr để hạn chế tiêu diệt đối với các tiến trình đang chạy và thêm một vài tín hiệu nữa vào danh sách:

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT

14

Các trap 'kill 0' SIGINT SIGTERM EXITgiả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:

  1. quá trình vỏ nhận SIGINThoặc SIGTERMhoặc EXIT;
  2. tín hiệu bị kẹt, thực thi kill 0, gửi SIGTERMđến tất cả các quy trình trong nhóm, bao gồm cả chính vỏ;
  3. đi tới 1 :)

Đ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 stopchứ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 ý!


trong 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)).
Trevor Boyd Smith

@TrevorBoydSmith, điều này sẽ không hoạt động như mong đợi, tôi đoán vậy. Ví dụ, lớp vỏ có thể bị giết SIGINT, nhưng kill 0sẽ 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ì SIGTERMsẽ bị loại bỏ trong stopcuộc gọi thứ hai .
skozin 16/2/2015

Có lẽ, trap - $1 && kill -s $1 0nê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! :)
skozin 16/2/2015

Không, trap - $1 && kill -s $1 0sẽ 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ì killgửi tín hiệu này theo mặc định.
skozin 16/2/2015

Tôi đã thử nghiệm đệ quy với EXIT, traptrình xử lý tín hiệu luôn chỉ được thực hiện một lần.
Trevor Boyd Smith

9

Để 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 -prtrống rỗng - tôi đã kết thúc trong 'cái bẫy' đó (ý định chơi chữ).


Trường hợp thử nghiệm [ -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.
Douwe van der Leest

Tôi nghi ngờ nó có liên quan đến thực tế là jobs -prkhô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ễ.
Douwe van der Leest

2

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.



1

Một tùy chọn khác là để tập lệnh tự đặt mình làm trưởng nhóm quy trình và bẫy killpg trong nhóm quy trình của bạn khi thoát.


0

Vì vậy, kịch bản tải tập lệnh. Chạy lệnh killall(hoặc bất cứ thứ gì có sẵn trên hệ điều hành của bạn) sẽ thực thi ngay khi tập lệnh kết thúc.


0

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.


0

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

0

Để đ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
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.