为什么我不能在bash脚本中使用作业控制?

时间:2009-03-27 15:40:34

标签: bash job-control

this answer到另一个question,我被告知

  

在脚本中没有作业控制权   (试图打开它是愚蠢的)

这是我第一次听到这个,而且我仔细考虑了关于工作控制的bash.info部分(第7章),没有提到这些断言中的任何一个。 [更新:手册页稍微好一点,提到'典型'使用,默认设置和终端I / O,但没有真正的理由为什么作业控制对于脚本来说特别不明智。] < / p>

那么为什么基于脚本的作业控制不起作用,又是什么使它成为一种不好的做法(又名“愚蠢”)?

编辑:有问题的脚本启动后台进程,启动第二个后台进程,然后尝试将第一个进程放回前台,以便它具有正常的终端I / O(就​​好像直接运行),然后可以从脚本外部重定向 。不能对后台进程这样做。

正如另一个问题的accepted answer所指出的,还有其他脚本可以在不尝试作业控制的情况下解决该特定问题。精细。 lambasted脚本使用硬编码的作业号 - 显然很糟糕。但我试图了解工作控制是否是一种根本注定要失败的方法。它似乎仍然可能可以工作......

7 个答案:

答案 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.

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

  1. 是作业控制意味着主要是为了促进交互式控制(而脚本可以直接使用pid),但
  2. 我引用他原来的答案, ...依赖于你之前没有在剧本中开始任何其他工作的事实,这是一个不好的假设。这是完全正确的。
  3. <强>更新

    回答你的评论:是的,没有人会阻止你在你的bash脚本中使用作业控制 - 强制禁用 set -m并不困难(即是的,工作如果你想要它,脚本中的控制将起作用。)请记住,最后,特别是在脚本编写中,总是有不止一种方法可以为猫皮肤设置,但有些方法更便携,更可靠,使处理更简单错误情况,解析输出等

    您的特定情况可能会或可能不会保证与lhunath(以及其他用户)认为“最佳做法”的方式不同。

答案 1 :(得分:29)

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