为什么我不能在bash脚本中使用作业控制?
在回答这个 问题时 ,我被告知
在脚本中你没有工作控制(并试图打开它是愚蠢的)
这是我第一次听到这个消息,而且我深入了解Job Control(第7章)的bash.info部分,并没有提到这两个断言。 [ 更新:手册页更好一点,提到'典型的'使用,默认设置和terminalI / O,但没有真正的原因,为什么作业控制特别不适合脚本]
那么,为什么不以脚本为基础的工作控制工作,又是什么使它成为一个不好的做法(又名“愚蠢的”)呢?
编辑:有问题的脚本启动后台进程,启动第二个后台进程,然后尝试将第一个进程放回到前台,使其具有正常的terminalI / O(如直接运行),然后可以redirect在剧本之外 。 不能这样做到后台进程。
正如对另一个问题所接受的答案所指出的那样,还有其他脚本可以解决这个特定的问题,而不会尝试控制任务。 精细。 而且这个严厉的脚本使用了一个硬编码的工作号码 – 显然是不好的。 但是我想知道工作控制是否是一个根本性的注定的方法。 它似乎仍然可能工作…
他的意思是在非交互模式下 (即在一个脚本中) 默认closures作业控制 。
从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.
当他说“很愚蠢”时,他的意思不仅是:
- 主要是为了促进交互控制(而脚本可以直接与pid一起工作),而且也是工作控制
- 我引用他原来的回答, …依靠事实,你以前没有开始任何其他工作,这是一个糟糕的假设 。 这是非常正确的。
UPDATE
在回答你的评论:是的,没有人会阻止你在你的bash脚本中使用作业控制 – 没有硬性的情况下强制禁用 set -m
(即,是的,从脚本的作业控制将工作,如果你想要的话。 )请记住,最后,特别是在脚本编写中,总有不止一种方法来对一只猫进行皮肤处理,但有些方法更便于携带,更可靠,更容易处理错误情况,parsing输出等。
您的具体情况可能会或可能不会保证与其他用户认为“最佳实践”不同的方式。
使用bg
和fg
作业控制仅在交互式shell中有用。 但是,与wait
一起在脚本中也是有用的。
在多处理器系统中,产生后台作业可以极大地提高脚本的性能,例如,在您希望每个CPU至less启动一个编译器的构build脚本中,或者使用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存储在$!
variables,所以你也可以wait ${!}
。 还请注意nice
命令。
有时候这样的代码在makefiles中很有用:
buildall: for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait
这比make -j
提供了更好的控制。
请注意, &
是一个行终止符像;
(写command&
不command&;
)。
希望这可以帮助。
只有在运行交互式shell时,作业控制才是有用的,也就是说,您知道stdin和stdout已连接到terminal设备(Linux上的/ dev / pts / *)。 那么,有前景的东西,背景的东西等是有道理的。
而脚本则没有这样的保证。 脚本可以变为可执行文件,并且不需要附加任何terminal即可运行。 在这种情况下进行前景或后台处理是没有意义的。
但是,您可以在后台以非交互方式运行其他命令(在命令行中附加“&”),并使用$!
捕获它们的PID $!
。 然后你使用kill
来杀死或暂停它们(模拟terminal上的Ctrl-C或Ctrl-Z,它是交互式的)。 您也可以使用wait
(而不是fg
)等待后台进程完成。
在脚本中打开作业控制以在SIGCHLD上设置陷阱可能很有用。 手册中的JOB CONTROL部分说:
每当作业改变状态时,shell都会立即学习。 通常情况下,bash会等待在报告作业状态变化之前打印提示,以便不中断任何其他输出。 如果启用set builtin命令的-b选项,bash会立即报告这种更改。 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中,你可以使用'wait -n'来实现同样的事情:
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在脚本中没有用处,因为它给脚本带来的唯一有趣的function是能够使用SIGCHLD。 还有其他的方法可以实现同样的事情,或者更短(等待)或者更便携(自己发送信号)。
如你所说,Bash支持工作控制。 在shell脚本编写中,经常会有一个假设,你不能依赖于你有bash的事实,但是你有一个历史上没有工作控制的vanilla Bourne shell( sh
)。
我现在很难想象一个系统,在这个系统中,你真的被限制在真正的Bourne shell中。 大多数系统的/bin/sh
将被链接到bash
。 不过,这是可能的。 你可以做的一件事是代替指定
#!/bin/sh
你可以做:
#!/bin/bash
这和你的文档,将清楚你的脚本需要bash
。
可能是o / t,但是我经常在长时间运行的服务器上使用nohup服务器,所以如果我退出了,这个工作仍然完成。
我想知道是否有人从一个主交互式shell和产卵后台进程中停止和开始混淆? 等待命令可以让你产生很多东西,然后等待它们完成,就像我说过的,我一直使用nohup。 它比这更复杂,而且使用不足 – sh也支持这种模式。 看看手册。
你也有
kill -STOP pid
我经常这样做,如果我想暂停当前运行的sudo,如下所示:
kill -STOP $$
但是,如果你从一位编辑跳出来的话,那么就会有一些不幸的事情发生。
我倾向于使用助记符-KILL等,因为有打字的危险
kill - 9 pid # note the space
在过去,有时候可能会把机器拿下来,因为它会导致初始化!
工作在bash脚本中工作
但是,你…需要注意产生的工作人员,如:
ls -1 /usr/share/doc/ | while read -r doc ; do ... done
工作将在|的每一边都有不同的上下文
绕过这可能是用来代替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