【发布时间】:2010-08-31 14:30:54
【问题描述】:
我编写了一个依赖于其他服务器响应的脚本(使用wget 来提取数据),并且我希望它始终毫无疑问地在后台运行。我知道一种解决方案是编写一个包装器脚本,该脚本将调用我的脚本并附加&,但我想避免这种混乱。
有没有办法让 bash(或 zsh)脚本确定它是否被称为 ./foo.sh &,如果没有,则退出并重新启动它本身?
【问题讨论】:
我编写了一个依赖于其他服务器响应的脚本(使用wget 来提取数据),并且我希望它始终毫无疑问地在后台运行。我知道一种解决方案是编写一个包装器脚本,该脚本将调用我的脚本并附加&,但我想避免这种混乱。
有没有办法让 bash(或 zsh)脚本确定它是否被称为 ./foo.sh &,如果没有,则退出并重新启动它本身?
【问题讨论】:
后台进程的定义(我认为)是它有一个控制终端,但它不属于该终端的前台进程组。我不认为任何 shell,甚至 zsh,都无法通过内置函数让您访问该信息。
在 Linux(可能还有其他 unices)上,当进程是其终端前台进程组的一部分时,ps 的 STAT 列包含 +。因此,对您的问题的字面回答是,您可以将脚本的内容放在 main 函数中并使用以下命令调用它:
case $(ps -o stat= -p $$) in
*+*) main "$@" &;;
*) main "$@";;
esac
但你还是可以运行main "$@" &。在 Unix 上,fork 很便宜。
但是,我强烈建议您不要按照您的建议去做。这使得某人无法运行您的脚本并在之后执行其他操作——人们期望能够编写your_script; my_postprocessing 或your_script && my_postprocessing,但是分叉脚本的主要任务使这成为不可能。考虑到在调用脚本时偶尔会节省一个字符,因此以这种方式显着降低脚本的用处是不值得的。
如果您真的是想让脚本在后台运行以便用户可以关闭他的终端,那么您需要做更多的工作——您需要daemonize脚本,这不仅包括后台,还包括关闭所有打开终端的文件描述符,使进程成为会话领导者等等。我认为这需要将您的脚本拆分为一个守护程序包装脚本和一个主脚本。但是,除非明确停止,否则通常不会终止程序,这不是您描述的行为。
【讨论】:
我不知道该怎么做,但你可以在父脚本中设置变量并在子脚本中检查它:
if [[ -z "$_BACKGROUNDED" ]] ; then
_BACKGROUNDED=1 exec "$0" "$@" & exit
fi
# Put code here
适用于 bash 和 zsh。
【讨论】:
exec $0 $@ 会使您的论点一团糟。为了安全起见,引用是必需的:exec "$0" "$@" -- 否则你会得到 ./yourprogram "argument one" "argument two" 与 ./yourprogram "argument" "one" "argument" "two" 运行相同 -- 那是在我们开始谈论通配符之前。
$@ 将删除所有等于空字符串的参数,而不是 $0 如果 @987654328 引用@ 扩展为空字符串将表现得好像它不存在一样。 IE。通配符或空格或其他任何问题都没有问题,但由于空字符串,您仍然需要引用。
如果您在后台,“tty”命令会说“不是 tty”,如果您在前台,则给出控制终端名称(例如 /dev/pts/1)。一个简单的判断方法。
【讨论】:
tty 的 shell 脚本会为我返回一个终端,无论我是在前台调用脚本还是将 & 放在结束。
请记住,您不能(或不建议)编辑正在运行的脚本。 This question and the answers 提供解决方法。
【讨论】:
我很久以前不写shell脚本了,但我可以给你一个很好的想法(我希望)。您可以检查 $$ 的值(这是进程的 PID)并与命令“jobs -l”的输出进行比较。最后一条命令将返回所有后台进程(作业)的 PID,如果 $$ 的值包含在“jobs -l”的结果中,这意味着当前脚本正在后台运行。
【讨论】:
jobs -l 无法访问父进程的作业列表,请检查zsh -c 'jobs -l; echo $$; sleep 1s' & 的输出:除了$$,它不会回显任何内容。