54

这个对另一个问题的回答中,有人告诉我

在脚本中你没有工作控制(并且试图打开它是愚蠢的)

这是我第一次听到这个,我仔细研究了作业控制(第 7 章)的 bash.info 部分,发现没有提到任何一个断言。[更新:手册页稍微好一点,提到了“典型”使用、默认设置和终端 I/O,但没有真正的理由说明作业控制对脚本特别不明智。]

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

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

正如另一个问题的公认答案所指出的,存在其他脚本可以解决该特定问题而无需尝试作业控制。美好的。被抨击的脚本使用了一个硬编码的工作编号——显然很糟糕。但我试图了解工作控制是否是一种从根本上注定要失败的方法。似乎它仍然可以工作......

4

7 回答 7

52

他的意思是在非交互模式下(即在脚本中)默认关闭作业控制。

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. 我引用他的原始答案,... 取决于您之前没有在脚本中开始任何其他工作的事实,这是一个不好的假设。这是完全正确的。

更新

回答您的评论:是的,没有人会阻止您在 bash 脚本中使用作业控制 -强制禁用 没有硬性案例set -m(即,是的,如果您愿意,脚本中的作业控制将起作用。)请记住最后,特别是在脚本中,总是有不止一种方法可以给猫剥皮,但有些方法更便携、更可靠、更容易处理错误情况、解析输出等。

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

于 2009-03-27T15:46:21.020 回答
36

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

请注意,它&是一个行终止符,例如;(write command& not command&;)。

希望这可以帮助。

于 2012-10-02T08:54:06.577 回答
8

作业控制仅在您运行交互式 shell 时有用,即您知道标准输入和标准输出连接到终端设备(Linux 上的 /dev/pts/*)。然后,在前台放置一些东西,在后台放置一些其他东西等是有意义的。

另一方面,脚本没有这样的保证。脚本可以是可执行的,并且可以在不连接任何终端的情况下运行。在这种情况下,拥有前台或后台进程是没有意义的。

但是,您可以在后台以非交互方式运行其他命令(将“&”附加到命令行)并使用$!. 然后你kill用来杀死或挂起它们(在终端上模拟 Ctrl-C 或 Ctrl-Z,它的 shell 是交互式的)。您也可以使用wait(而不是fg)等待后台进程完成。

于 2009-03-27T15:55:05.337 回答
7

在脚本中打开作业控制以在 SIGCHLD 上设置陷阱可能很有用。手册中的作业控制部分说:

每当作业更改状态时,shell 都会立即学习。通常,bash 会等到它即将打印提示时才会报告作业状态的变化,以免中断任何其他输出。如果启用了 set 内置命令的 -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

因此,总而言之,set -m在脚本中并没有那么有用,因为它为脚本带来的唯一有趣的特性就是能够使用 SIGCHLD。还有其他方法可以实现同样的事情,或者更短(等待 -n)或更便携(自己发送信号)。

于 2014-04-14T20:30:13.123 回答
2

正如您所说,Bash 确实支持作业控制。在编写 shell 脚本时,通常假设您不能依赖于您拥有 bash 的事实,而是拥有 vanilla Bourne shell ( sh),它在历史上没有作业控制。

这些天我很难想象一个系统,其中你真的被限制在真正的 Bourne shell 中。大多数系统/bin/sh将链接到bash. 不过,这是可能的。您可以做的一件事是而不是指定

#!/bin/sh

你可以做:

#!/bin/bash

那,以及您的文档,将清楚您的脚本需要bash.

于 2009-03-27T15:46:51.067 回答
0

可能是 o/t,但我经常在 ssh 进入长期运行的工作的服务器时使用 nohup,这样如果我退出,工作仍然可以完成。

我想知道人们是否会混淆从主交互式 shell 停止和启动并产生后台进程?wait 命令允许你生成很多东西,然后等待它们全部完成,就像我说的那样,我一直使用 nohup。它比这更复杂,也没有得到充分利用 - sh 也支持这种模式。看看手册。

你也有

kill -STOP pid

如果我想暂停当前正在运行的 sudo,我经常会这样做,例如:

kill -STOP $$

但是,如果您从编辑器跳出到外壳,那么您就有祸了——它只会坐在那里。

我倾向于使用助记符 -KILL 等,因为有打字的危险

kill - 9 pid # note the space

在过去,您有时可以将机器关闭,因为它会杀死 init!

于 2009-03-29T09:22:09.053 回答
-1

工作确实在 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
于 2012-05-24T11:29:49.727 回答