考虑到以下情况:父子程序(可以是C ++程序或Shell脚本)执行子Shell脚本,当我们在执行子Shell脚本时按Control + C(或配置为INTR的任何字符)时, SIGINT发送到前台进程组中的所有进程。这包括父进程。
有没有办法覆盖此默认行为?CHILD进程独自处理信号而没有传播给父进程吗?
考虑到以下情况:父子程序(可以是C ++程序或Shell脚本)执行子Shell脚本,当我们在执行子Shell脚本时按Control + C(或配置为INTR的任何字符)时, SIGINT发送到前台进程组中的所有进程。这包括父进程。
有没有办法覆盖此默认行为?CHILD进程独自处理信号而没有传播给父进程吗?
Answers:
(灵感来自吉尔斯的答案)
在ISIG
设置了标志的情况下,没有父Child
脚本就获取脚本的唯一方法是使其位于自己的进程组中。这可以通过选件完成。SIGINT
SIGINT
set -m
如果您-m
在Child
外壳程序脚本中打开该选项,它将在不交互的情况下执行作业控制。这将导致它在单独的进程组中运行内容,从而阻止父级SIGINT
在INTR
读取字符时收到。
-m
如果实现支持用户可移植性实用程序选项,则应支持此选项。所有作业应在各自的过程组中运行。在外壳程序在完成后台作业之后发出提示之前,应立即将报告后台作业退出状态的消息写入标准错误。如果前台作业停止,则外壳程序应为此写入一条消息,指示标准错误,其格式如作业实用程序所述。另外,如果作业改变了状态而不是退出(例如,如果它停止输入或输出或被SIGSTOP信号停止),则外壳程序应在写入下一个提示之前立即写入一条类似的消息。默认情况下,此选项对于交互式Shell启用。
该-m
选项与相似-i
,但是它几乎没有改变shell的行为-i
。
例:
该Parent
脚本:
#!/bin/sh
trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
echo "PARENT: pid=$$"
echo "PARENT: Spawning child..."
./Child
echo "PARENT: child returned"
echo "PARENT: exiting normally"
该Child
脚本:
#!/bin/sh -m
# ^^
# notice the -m option above!
trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
echo "CHILD: pid=$$"
echo "CHILD: hit enter to exit"
read foo
echo "CHILD: exiting normally"
当您在等待输入时按Control+ C时,会发生以下情况Child
:
$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally
请注意,SIGINT
永远不会执行父级的处理程序。
另外,如果您想修改Parent
而不是Child
,可以这样做:
该Parent
脚本:
#!/bin/sh
trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
echo "PARENT: pid=$$"
echo "PARENT: Spawning child..."
sh -m ./Child # or 'sh -m -c ./Child' if Child isn't a shell script
echo "PARENT: child returned"
echo "PARENT: exiting normally"
该Child
脚本(正常,没有必要-m
):
#!/bin/sh
trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
echo "CHILD: pid=$$"
echo "CHILD: hit enter to exit"
read foo
echo "CHILD: exiting normally"
SIGINT
的持续时间Child
。这不能解决您的问题,但是可以为您提供所需的东西。Child
为:
stty -g
备份当前的终端设置。stty -isig
不生成与信号INTR
,QUIT
以及SUSP
文字。kill -QUIT 0
时Control+ \被读出,kill -INT $$
当Control+ C被读取)。这并非无关紧要,并且如果Child
脚本或其运行的任何内容都是交互式的,则可能无法使其顺利运行。EXIT
)。stty -isig
,请等用户Enter击键或其他非特殊键再杀死Child
。setpgid
用C,Python,Perl等编写自己的实用程序,可用于调用setpgid()
。这是一个粗略的C实现:
#define _XOPEN_SOURCE 700
#include <unistd.h>
#include <signal.h>
int
main(int argc, char *argv[])
{
// todo: add error checking
void (*backup)(int);
setpgid(0, 0);
backup = signal(SIGTTOU, SIG_IGN);
tcsetpgrp(0, getpid());
signal(SIGTTOU, backup);
execvp(argv[1], argv + 1);
return 1;
}
来自的示例用法Child
:
#!/bin/sh
[ "${DID_SETPGID}" = true ] || {
# restart self after calling setpgid(0, 0)
exec env DID_SETPGID=true setpgid "$0" "$@"
# exec failed if control reached this point
exit 1
}
unset DID_SETPGID
# do stuff here
正如您从POSIX引用的那章所解释的那样,SIGINT被发送到整个前台进程组。因此,为避免杀死程序父程序,请安排它在自己的进程组中运行。
Shell setpgrp
不能通过内置或语法结构进行访问,但是有一种间接的方法来实现它,即交互运行Shell。(感谢StéphaneGimenez的把戏。)
ksh -ic '
… the part that needs to be interruptible without bothering the parent …
'
ksh
)。示例:${ENV}
源,外壳程序在遇到错误时不会立即退出SIGQUIT
并被SIGTERM
忽略。