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

sys*_*USE 49 bash job-control

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

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

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

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

编辑:有问题的脚本启动一个后台进程,启动第二后台进程,然后试图将所述第一进程返回到前景,以便它具有普通端子I/O(如如果直接运行),然后可以被重定向从在剧本之外.不能这样做到后台进程.

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

vla*_*adr 42

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

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.
Run Code Online (Sandbox Code Playgroud)

   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.
Run Code Online (Sandbox Code Playgroud)

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

  1. 是作业控制意味着大部分用于促进交互式控制(而脚本可以与PID的直接合作),而且还
  2. 我引用他原来的答案,...依赖于你之前在剧本中没有开始任何其他工作的事实,这是一个不好的假设.这是非常正确的.

UPDATE

回答您的评论:是的,没有人会阻止您在bash脚本中使用作业控制 - 没有强制禁用的情况 set -m(即是的,如果您需要,脚本中的作业控制将起作用.)请记住最后,特别是在脚本编写方面,总是有不止一种方法可以对猫进行换肤,但有些方法更便携,更可靠,更容易处理错误情况,解析输出等等.

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

  • 有一个非常重要的原因可以使作业控制在脚本中有用:它具有将后台进程放在自己的进程组中的副作用.通过一个简单的命令:`kill - <signal> - $ pgid`,这样可以更容易地向他们发送signels和他们的孩子们.处理信号整个进程树的所有其他方式要么包括复杂的(有时甚至是递归的)函数,这些函数通常都是错误的,或者在进程中冒险杀死父进程(没有双关语意图). (17认同)
  • +1准确详细.作业控制是一种使(交互式)提示上的处理作业更加方便的功能.没有理由为什么有人会想要它在脚本中,因为你可以保持后台进程的PID和*wait*on或*kill*它们. (2认同)
  • 好吧,我*得到*硬编码的工作号码是个坏主意.没问题.但是像"*默认*用于交互式"和"用户*通常*使用"和"用于*大多数*用于"的词语都强烈暗示在脚本中存在*一些*深奥的工作控制用例.否则设置-m应该在脚本中失败. (2认同)
  • 例如,您将如何启动后台进程,运行几个设置命令,然后使用pids将其移动到前台(我为docker容器做了很多)? (2认同)

And*_*ler 32

使用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
Run Code Online (Sandbox Code Playgroud)

没有什么"愚蠢"的.但是您将需要该wait命令,该命令在脚本继续之前等待所有后台作业.最后一个后台作业的PID存储在$!变量中,因此您也可以wait ${!}.还要注意nice命令.

有时这样的代码在makefile中很有用:

buildall:
    for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait
Run Code Online (Sandbox Code Playgroud)

这比控制更精细make -j.

注意,这&是一个行终止符,如;(command& 不写command&;).

希望这可以帮助.

  • 对于读者来说:`wait`也允许多个pid,例如`wait 3940 4001 4012 4024`,但是会等待*all*之后再继续. (2认同)

Jul*_*ano 7

只有在运行交互式shell时,作业控制才有用,即,您知道stdin和stdout已连接到终端设备(Linux上的/ dev/pts/*).然后,在前景上有东西,在背景上有其他东西等是有意义的.

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

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


小智 5

在脚本中打开作业控制以在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
Run Code Online (Sandbox Code Playgroud)

注意:从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
Run Code Online (Sandbox Code Playgroud)

您可以争辩说还有其他方法可以通过更便携的方式执行此操作,即没有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
Run Code Online (Sandbox Code Playgroud)

因此,总而言之,set -m 在脚本中没有那么有用,因为它为脚本带来的唯一有趣的功能是能够与SIGCHLD一起工作.还有其他方法可以实现相同的更短(等待-n)或更便携(自己发送信号).