簡體   English   中英

bash 腳本如何對后台任務執行相當於 Ctrl-C 的操作?

[英]How can bash script do the equivalent of Ctrl-C to a background task?

有沒有辦法調用一個子進程,以便它和它的所有后代都被發送一個中斷,就像你 Ctrl-C 一個前台任務一樣? 我試圖殺死一個調用長期運行的孩子的啟動器腳本。 我試過kill -SIGINT $child (它不會將中斷發送給它的后代,所以是一個無操作)和kill -SIGINT -$child (它在交互調用時有效,但在腳本中運行時無效)。

這是一個測試腳本。 長時間運行的腳本是test.sh --child 當您調用test.sh --parent ,它會調用test.sh --child &然后嘗試殺死它。 我怎樣才能讓父母成功殺死孩子?

#!/bin/bash

if [ "$1" = "--child" ]; then
sleep 1000

elif [ "$1" = "--parent" ]; then
"$0" --child &
for child in $(jobs -p); do
  echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $(jobs -p)

else
echo "Must be invoked with --child or --parent."
fi

我知道你可以修改長時間運行的子進程來trap信號,將它們發送到它的子進程,然后等待(從Bash 腳本殺死后台(大)子進程在 Ctrl+C 上),但是有沒有辦法不修改子腳本?

對於任何想知道的人,這就是您如何在后台啟動孩子並在 ctrl+c 上殺死他們的方法:

#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait
somecommand &

$!返回子進程的 pid

somecommand &
pid[0]=$!
anothercommand &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait

我將從這個模型開始,而不是從 bash 作業控制(bg、fg、jobs)開始。 通常 init 會繼承和收割孤兒進程。 你想解決什么問題?

閱讀: 如何從腳本發送信號 SIGINT 到腳本? 巴什

也來自info bash

   To facilitate the implementation of the user interface to job  control,
   the operating system maintains the notion of a current terminal process
   group ID.  Members of this process group (processes whose process group
   ID is equal to the current terminal process group ID) receive keyboard-
   generated signals such as SIGINT.  These processes are said  to  be  in
   the  foreground.  Background processes are those whose process group ID
   differs from the terminal's; such processes are immune to keyboard-gen‐
   erated signals. 

因此bash通過進程組 ID區分后台進程和前台進程。 如果進程組 id等於進程 id ,則該進程是前台進程,並在收到SIGINT信號時終止。 否則它不會終止(除非它被困)。

您可以看到進程組 ID

ps x -o  "%p %r %y %x %c "

因此,當您從腳本中運行后台進程(使用& )時,它將忽略SIGINT信號,除非它被捕獲。

但是,您仍然可以使用其他信號殺死子進程,例如SIGKILLSIGTERM等。

例如,如果您將腳本更改為以下內容,它將成功終止子進程:

#!/bin/bash

if [ "$1" = "--child" ]; then
  sleep 1000
elif [ "$1" = "--parent" ]; then
  "$0" --child &
  for child in $(jobs -p); do
    echo kill "$child" && kill "$child"
  done
  wait $(jobs -p)

  else
  echo "Must be invoked with --child or --parent."
fi

輸出:

$ ./test.sh --parent
kill 2187
./test.sh: line 10:  2187 Terminated              "$0" --child

您可以通過一個簡單的小SIGINT繼續將SIGINT用於后台任務:將您的異步子進程調用放在一個函數或{ } ,並為其提供setsid以便它擁有自己的進程組。

這是您的腳本,保持其全部初衷:

  • 使用和傳播SIGINT而不是使用其他信號

  • 只修改調用: "$0" --child &{ setsid "$0" --child; } & "$0" --child & { setsid "$0" --child; } & { setsid "$0" --child; } &

  • 添加獲取子實例PID所需的代碼,這是后台子shell中的唯一進程。

這是你的代碼:

#!/bin/bash

if [ "$1" = "--child" ]; then
sleep 1000

elif [ "$1" = "--parent" ]; then
{ setsid "$0" --child; } &
subshell_pid=$!
pids=$(ps -ax -o ppid,pid --no-headers |
    sed -r 's/^ +//g;s/ +/ /g' |
    grep "^$subshell_pid " | cut -f 2 -d " ");
for child in $pids;  do
  echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $subshell_pid

else
echo "Must be invoked with --child or --parent."

這是 bash 手冊中重要的文檔部分

進程組 id 對后台進程的影響(在文檔的作業控制部分):

[...] 進程組 ID 等於當前終端進程組 ID [..] 的進程接收鍵盤生成的信號,例如 SIGINT。 據說這些進程在前台。 后台進程是那些進程組 ID 與終端不同的進程; 此類進程不受鍵盤生成信號的影響

SIGINTSIGQUIT默認處理程序(在文檔的信號部分):

bash 運行的非內置命令將信號處理程序設置為 shell 從其父級繼承的值。 當作業控制無效時,除了這些繼承的處理程序之外,異步命令還會忽略 SIGINT 和 SIGQUIT

以及關於陷阱的修改(在trap內置文檔中):

進入外殼時被忽略的信號不能被捕獲或重置

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM