在this answer到另一个question,我被告知
在脚本中没有作业控制权 (试图打开它是愚蠢的)
这是我第一次听到这个,而且我仔细考虑了关于工作控制的bash.info部分(第7章),没有提到这些断言中的任何一个。 [更新:手册页稍微好一点,提到'典型'使用,默认设置和终端I / O,但没有真正的理由为什么作业控制对于脚本来说特别不明智。] < / p>
那么为什么基于脚本的作业控制不起作用,又是什么使它成为一种不好的做法(又名“愚蠢”)?
编辑:有问题的脚本启动后台进程,启动第二个后台进程,然后尝试将第一个进程放回前台,以便它具有正常的终端I / O(就好像直接运行),然后可以从脚本外部重定向 。不能对后台进程这样做。
正如另一个问题的accepted answer所指出的,还有其他脚本可以在不尝试作业控制的情况下解决该特定问题。精细。 lambasted脚本使用硬编码的作业号 - 显然很糟糕。但我试图了解工作控制是否是一种根本注定要失败的方法。它似乎仍然可能可以工作......
答案 0 :(得分:41)
他的意思是默认情况下作业控制在非交互模式下关闭(即在脚本中)。
来自bash
手册页:
JOB CONTROL
Job control refers to the ability to selectively stop (suspend)
the execution of processes and continue (resume) their execution at a
later point.
A user typically employs this facility via an interactive interface
supplied jointly by the system’s terminal driver and bash.
和
set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
...
-m Monitor mode. Job control is enabled. This option is on by
default for interactive shells on systems that support it (see
JOB CONTROL above). Background processes run in a separate
process group and a line containing their exit status is
printed upon their completion.
当他说“愚蠢”时,他的意思不仅仅是:
<强>更新强>
回答你的评论:是的,没有人会阻止你在你的bash脚本中使用作业控制 - 强制禁用 set -m
并不困难(即是的,工作如果你想要它,脚本中的控制将起作用。)请记住,最后,特别是在脚本编写中,总是有不止一种方法可以为猫皮肤设置,但有些方法更便携,更可靠,使处理更简单错误情况,解析输出等
您的特定情况可能会或可能不会保证与lhunath
(以及其他用户)认为“最佳做法”的方式不同。
答案 1 :(得分:29)
使用bg
和fg
的作业控制仅在交互式shell中有用。但&
与wait
一起在脚本中也很有用。
在多处理器系统上,生成后台作业可以极大地提高脚本的性能,例如:在构建脚本中,您希望每个CPU启动至少一个编译器,或者使用ImageMagick工具并行处理图像等。
以下示例最多可运行8个并行gcc来编译数组中的所有源文件:
#!bash
...
for ((i = 0, end=${#sourcefiles[@]}; i < end;)); do
for ((cpu_num = 0; cpu_num < 8; cpu_num++, i++)); do
if ((i < end)); then gcc ${sourcefiles[$i]} & fi
done
wait
done
这没有什么“愚蠢”的。但是您需要wait
命令,该命令在脚本继续之前等待所有后台作业。最后一个后台作业的PID存储在$!
变量中,因此您也可以wait ${!}
。另请注意nice
命令。
有时这样的代码在makefile中很有用:
buildall:
for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait
这比make -j
提供了更好的控制。
请注意,&
是一个行终止符,如;
(写command&
而非command&;
)。
希望这有帮助。
答案 2 :(得分:6)
只有在运行交互式shell时,作业控制才有用,即,您知道stdin和stdout已连接到终端设备(Linux上的/ dev / pts / *)。然后,在前景上有东西,在背景上有其他东西等是有道理的。
另一方面,脚本没有这样的保证。脚本可以变为可执行文件,并且无需连接任何终端即可运行。在这种情况下,拥有前台或后台进程是没有意义的。但是,您可以在后台以非交互方式运行其他命令(将“&amp;”附加到命令行)并使用$!
捕获其PID。然后使用kill
来终止它们(在终端上模拟Ctrl-C或Ctrl-Z,shell是交互式的)。您还可以使用wait
(而非fg
)等待后台流程完成。
答案 3 :(得分:5)
在脚本中启用作业控制以设置陷阱可能很有用 SIGCHLD。手册中的JOB CONTROL部分说:
当作业改变状态时,shell立即学习。一般, bash等待,直到它要在报告之前打印提示 更改作业的状态,以免中断任何其他输出。如果 bash报告启用了set builtin命令的-b选项 立即改变。 对每个陷阱执行SIGCHLD上的任何陷阱 退出的孩子。
(重点是我的)
以下面的脚本为例:
dualbus@debian:~$ cat children.bash
#!/bin/bash
set -m
count=0 limit=3
trap 'counter && { job & }' CHLD
job() {
local amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
}
counter() {
((count++ < limit))
}
counter && { job & }
wait
dualbus@debian:~$ chmod +x children.bash
dualbus@debian:~$ ./children.bash
sleeping 6 seconds
sleeping 0 seconds
sleeping 7 seconds
注意:从bash 4.3
开始,CHLD陷阱似乎被打破了在bash 4.3中,你可以使用&#39; wait -n&#39;实现同样的目的, 虽然:
dualbus@debian:~$ cat waitn.bash
#!/home/dualbus/local/bin/bash
count=0 limit=3
trap 'kill "$pid"; exit' INT
job() {
local amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
}
for ((i=0; i<limit; i++)); do
((i>0)) && wait -n; job & pid=$!
done
dualbus@debian:~$ chmod +x waitn.bash
dualbus@debian:~$ ./waitn.bash
sleeping 3 seconds
sleeping 0 seconds
sleeping 5 seconds
你可以争辩说还有其他方法可以做到这一点 便携式方式,即没有CHLD或等待-n:
dualbus@debian:~$ cat portable.sh
#!/bin/sh
count=0 limit=3
trap 'counter && { brand; job & }; wait' USR1
unset RANDOM; rseed=123459876$$
brand() {
[ "$rseed" -eq 0 ] && rseed=123459876
h=$((rseed / 127773))
l=$((rseed % 127773))
rseed=$((16807 * l - 2836 * h))
RANDOM=$((rseed & 32767))
}
job() {
amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
kill -USR1 "$$"
}
counter() {
[ "$count" -lt "$limit" ]; ret=$?
count=$((count+1))
return "$ret"
}
counter && { brand; job & }
wait
dualbus@debian:~$ chmod +x portable.sh
dualbus@debian:~$ ./portable.sh
sleeping 2 seconds
sleeping 5 seconds
sleeping 6 seconds
因此,总而言之,设置-m 不在脚本中非常有用,因为 它为脚本带来的唯一有趣的功能是能够 与SIGCHLD合作。还有其他方法可以实现同样的目标 更短(等待-n)或更便携(自己发送信号)。
答案 4 :(得分:1)
Bash确实支持工作控制。在shell脚本编写中,通常有一个假设,你不能依赖于你有bash的事实,但是你有一个vanilla Bourne shell(sh
),它在历史上没有工作控制。
这些天我很难想象一个系统,在这个系统中你真的被限制在真正的Bourne shell中。大多数系统/bin/sh
都会与bash
相关联。不过,这是可能的。你可以做的一件事是不是指定
#!/bin/sh
你可以这样做:
#!/bin/bash
那,以及您的文档,会明确您的脚本需要bash
。
答案 5 :(得分:0)
可能是o / t但是我经常在长时间运行的作业中将ssh用于服务器时使用nohup,这样如果我退出,作业仍然完成。
我想知道人们是否会混淆停止并从主交互式shell开始并产生后台进程? wait命令允许你产生很多东西,然后等待它们全部完成,就像我说我一直使用nohup。它比这更复杂,而且使用率很低 - sh也支持这种模式。看一下手册。
你也有
kill -STOP pid
如果我想暂停当前正在运行的sudo,我经常这样做,如:
kill -STOP $$
但是,如果你从编辑那里跳到外壳,那么你会感到懊恼 - 它只会坐在那里。
我倾向于使用助记符-KILL等,因为有打字的危险
kill - 9 pid # note the space
在过去,你有时会把机器关闭,因为它会杀死init!
答案 6 :(得分:0)
作业可以在bash脚本中运行
但是,你......需要关注产生的员工 像:
ls -1 /usr/share/doc/ | while read -r doc ; do ... done
作业将在|
的每一侧都有不同的背景绕过这个可能是使用for而不是while:
for `ls -1 /usr/share/doc` ; do ... done
这应该演示如何在脚本中使用作业...... 提到我的注释是......真实的(不知道为什么那种行为)
#!/bin/bash
for i in `seq 7` ; do ( sleep 100 ) & done
jobs
while [ `jobs | wc -l` -ne 0 ] ; do
for jobnr in `jobs | awk '{print $1}' | cut -d\[ -f2- |cut -d\] -f1` ; do
kill %$jobnr
done
#this is REALLY ODD ... but while won't exit without this ... dunno why
jobs >/dev/null 2>/dev/null
done
sleep 1
jobs