我期望类似于C语言中的
setpgid()
。setpgid()
。setpgrp
实用程序是由于任何技术障碍吗? - Piotr Dobrogost(…) | less -R
更改为(…) | (set -m; exec less -R)
后,我再也没有输出了。 - Piotr Dobrogostset -m
需要一个终端设备,但该设备可能不存在。 - josch我会回答我所理解的部分:
如何强制当前的bash shell脚本成为自己的进程组:
我在我的bash脚本开头添加了这个:
pgid_from_pid() {
local pid=$1
ps -o pgid= "$pid" 2>/dev/null | egrep -o "[0-9]+"
}
pid="$$"
if [ "$pid" != "$(pgid_from_pid $pid)" ]; then
exec setsid "$(readlink -f "$0")" "$@"
fi
我为什么需要这个东西?
从交互式bash会话启动程序时,它会得到自己的新进程组。但是,如果您的程序是从bash脚本(非交互式)调用的,则不是这种情况。如果您的程序依赖于在两种条件下都成为进程组所有者,则需要使用此功能。
我认为 Bourne、bash 或 zsh 不会让你这样做,但你可以使用内置的 setpgrp
在 Perl 中实现(注意与 POSIX 稍有不同的名称)。将 PID 设置为零以修改 Perl 进程本身的组:
setpgrp(0, 12345) || die "$!"
你可能认为可以通过在Bash中使用Perl(例如将$$
传递给Perl脚本)来设置Bash进程的组,但我认为Perl进程无法修改它没有派生出来的进程的组。
根据你想要做什么,各种shell中的作业控制功能可能以不同的方式提供所需的功能,比如如果你只想从终端分离。
更新:我认为这个答案收到了几个没有明确解释的负评。我猜测是因为投票者误解了问题,问题是询问如何更改当前shell的进程组。或者他们知道如何从shell执行setpgrp,但却保守秘密。
cron
启动的 shell)会在同一个进程组中运行程序(因为没有控制终端,所以没有必要在进程之间进行复用)。而且,不能使用 shell 内置命令来更改进程组。 - Maxim Egorushkinset -m
,新进程将在一个新的进程组中生成,如果它们被放到后台,它们不会忽略SIGINT和SIGQUIT。if [ $$ = $(ps -o pgid -hp $$) ]; then
echo already a process group leader;
else
set -m
$0 "$@" #optionally with &
set +m
fi
set -m
接管终端前台进程组后运行,除非它们在后台运行。
set -m
显然是半标准的,如果实现支持“用户可移植性工具”,则 POSIX 要求使用。实际上,它适用于 bash
、dash
、ksh
、pdksh
、sh
、yash
和 zsh
。但 posh
不支持。如果您的意图是清理任何生成的子shell进程(即使脚本本身不是直接从交互式shell启动,而是来自另一个进程,并且因此不会自动成为其自己的进程组长),则可以参考以下综合答案。必要时将当前脚本作为新的进程组长重新启动。
# First, obtain the current PGID, by parsing the output of "ps".
pgid=$(($(ps -o pgid= -p "$$")))
# Check if we're already the process group leader; if not, re-launch ourselves.
# Use setsid instead of set -m (...) to avoid having another subshell in between. This helps that the trap gets executed when the script is killed.
[ $$ -eq $pgid ] || exec setsid --wait "${BASH_SOURCE[0]}" "$@"
# Kill any subshell processes when the script exits.
trap "kill -- -$pgid" EXIT
# Note: If the script only starts background jobs, and that's all you care about, you can replace all of the above with this simple trap:
#trap "jobs -p | xargs kill --" EXIT # Kill remaining jobs when the script exits.
当一个脚本调用另一个进行子shell清理时,会引入另一种复杂性。进程组领导不会嵌套;一旦一个脚本担任领导职责,它的生命周期就不再由父脚本控制,因此,当父脚本被中断或终止时,嵌套脚本将继续存在。这通常不是用户想要的。
以下脚本片段通过协作模型扩展了上述实现,以便只有顶级脚本担任进程组领导,并通过导出$PGID
将其传递给子shell。如果子shell发现已经存在领导者,则不会自己担任领导,而是将自己的清理任务限制在剩余的作业中。其他子shell将在顶级脚本退出时被终止。(因此,当一个脚本只调用一个或只调用几个其他脚本时,这种协作模型最有效)。
if [ -z "$PGID" ]; then # No parent script has become the process group leader yet.
pgid=$(($(ps -o pgid= -p "$$"))) # By defining this, we'll be killing subshell processes of this process group when we're done or interrupted. Any children with the same ambition will defer to us.
if [ $$ -eq $pgid ]; then
export PGID=$pgid # We are (already / after setsid) in our own process group, announce our leadership to any children, so that they don't become leaders themselves and thereby decouple themselves from our lifetime control.
else
exec setsid --wait "${BASH_SOURCE[0]}" "$@" # Use setsid instead of set -m (...) to avoid having another subshell in between.
fi
fi
if [ -n "$pgid" ]; then
trap "kill -- -$pgid" EXIT # If we're the leader, kill subshell processes when the script exits.
else
trap "jobs -p | xargs kill --" EXIT # Someone else is the leader; killing remaining jobs is all we can do here.
fi
$(jobs -p)
会强制将jobs -p
放入子shell中,其中作业列表将为空(尝试sleep 10&sleep&sleep&
)。您需要将pid列表写入临时文件,然后从该文件中读取它。至少对于大多数shell来说(ksh和bash设法避免子shell,并且它们还为陷阱内置执行类似的魔术--您的解决方案应在这两个shell中工作,但不适用于其他shell)。 - Petr Skocik