为什么我不能在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. 

当他说“很愚蠢”时,他的意思不仅是:

  1. 主要是为了促进交互控制(而脚本可以直接与pid一起工作),而且也是工作控制
  2. 我引用他原来的回答, …依靠事实,你以前没有开始任何其他工作,这是一个糟糕的假设 。 这是非常正确的。

UPDATE

在回答你的评论:是的,没有人会阻止你在你的bash脚本中使用作业控制 – 没有硬性的情况下强制禁用 set -m (即,是的,从脚本的作业控制将工作,如果你想要的话。 )请记住,最后,特别是在脚本编写中,总有不止一种方法来对一只猫进行皮肤处理,但有些方法更便于携带,更可靠,更容易处理错误情况,parsing输出等。

您的具体情况可能会或可能不会保证与其他用户认为“最佳实践”不同的方式。

使用bgfg作业控制仅在交互式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