【问题标题】:Execute process in background, without printing "Done", and get PID后台执行进程,不打印“Done”,获取PID
【发布时间】:2014-07-19 18:25:51
【问题描述】:

这似乎是一件微不足道的事情,但我很困惑。

要在后台执行某些操作,请使用&

>>> sleep 5 &
[1] 21763
>>> #hit enter
[1]+  Done                    sleep 5

但是拥有一个来自 bashrc 的后台脚本输出作业信息是非常令人沮丧的,所以你可以通过 this 来修复它:

>>> (sleep 5 &)

好的,现在我想为waitkill 获取sleep 的PID。不幸的是,它在子外壳中运行,因此典型的 $! 方法不起作用:

>>> echo $!
21763
>>> (sleep 5 &)
>>> echo $!
21763 #hasn't changed

所以我想,也许我可以让 subshel​​l 以这种方式打印它的 PID:

>>> sleep 5 & echo $!
[1] 21803 #annoying job-start message (stderr)
21803 #from the echo

但是现在当我将它扔到子shell中时,无论我如何尝试捕获子shell的标准输出,它似乎都会阻塞,直到sleep完成。

>>> pid=$(sleep 5 & echo $!)

如何在后台运行某些东西,获取它的 PID 并阻止它打印作业信息和“Done”?

【问题讨论】:

  • 仅供参考,在脚本中作业控制默认是关闭的,因此您不应该看到在那种情况下打印的作业编号信息。
  • @CharlesDuffy 正如 jm666 正确猜测的那样,我正在采购这个脚本并希望它在我的主 shell 的后台运行。将在问题中澄清

标签: bash


【解决方案1】:

解决方案 A

在调用进程时,将 shell 的 stderr 重定向到该调用实例的 >/dev/null。我们可以通过复制 fd 2 来做到这一点,这样我们仍然可以将复制的 fd 用于该过程。我们在一个块中执行所有这些操作以使重定向临时:

{ sleep 5 2>&3 & pid=$!; } 3>&2 2>/dev/null

现在为了防止稍后显示“完成”消息,我们将进程从作业表中排除,这是通过 disown 命令完成的:

{ sleep 5 2>&3 & disown; pid=$!; } 3>&2 2>/dev/null

如果未启用作业控制,则不需要。可以使用set +mshopt -u -o monitor 禁用作业控制。

解决方案 B

我们也可以使用命令替换来调用进程。我们遇到的唯一问题是该进程仍将自身挂接到由$() 创建的读取标准输出的管道,但我们可以通过在它之前复制原始标准输出然后使用该进程的文件描述符来解决此问题:

{ pid=$( sleep 200s >&3 & echo $! ); } 3>&1

如果我们将进程的输出重定向到 /dev/null 之类的地方,则可能没有必要:

pid=$( sleep 200s >/dev/null & echo $! )

与进程替换类似:

{ read pid < <(sleep 200s >&3 & echo $!); } 3>&1

有些人可能会说process substitution 不需要重定向,但问题是可能正在访问其stdout 的进程会很快死掉。例如:

$ function x { for A in {1..100}; do echo "$A"; sleep 1s; done }
$ read pid < <(x & echo $!)
$ kill -s 0 "$pid" &>/dev/null && echo "Process active." || echo "Process died."
Process died.
$ read pid < <(x > /dev/null & echo $!)
$ kill -s 0 "$pid" &>/dev/null && echo "Process active." || echo "Process died."
Process active.
  • 您也可以选择使用exec 3&gt;&amp;1 创建一个永久重复的fd,这样您就可以在下一行使用pid=$( sleep 200s &gt;&amp;3 &amp; echo $! )

【讨论】:

  • 关于保留输出的好处。我特别喜欢这个,因为该过程也不需要在子外壳中运行。然而,正如@jm666 指出的那样,我失去了工作控制-bash: fg: no job control。目前我还是更喜欢 subshel​​l 方法
【解决方案2】:

您可以使用read 公告捕获输出:

read -r pid < <(sleep 10 & echo $!)

然后:

ps -p $pid
  PID TTY           TIME CMD
78541 ttys001    0:00.00 sleep 10

【讨论】:

  • 非常有趣。你能解释一下为什么我需要使用read吗?另外,我早该意识到这会发生:bash: wait: pid 22108 is not a child of this shell.
  • wait 不起作用,因为 sleep 是在子 shell 中执行的,并且后台进程 id 不是当前 shell 进程的子进程。
  • @jozxyqk, read 读取一行并立即返回而不会进一步阻塞,而典型的命令替换会继续尝试读取直到子进程的标准输出关闭。
【解决方案3】:

set +m 在 bash 中禁用监控模式。换句话说,它摆脱了烦人的Done 消息。 要再次启用,请使用set -m

例如:

$ set +m
$ (sleep 5; echo some) &
[1] 23545 #still prints the job number

        #after 5 secs
some
$  #no Done message...

【讨论】:

  • 好吧,这只是消除了所有复杂性,不是吗。干杯!
  • @jozxyqk 并不是真的因为它禁用了作业控制,所以您没有收到关于后台作业何时等待输入、无法列出作业等的通知...不建议在.bashrc 只是偶尔当你真的知道你在做什么时。 ;)
  • @jozxyqk :) ;) 当然。套装很给力。看到这里的人:gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
【解决方案4】:

试试这个:

pid=$((sleep 5 & echo $!) | sed 1q)

【讨论】:

  • 确实,这行得通。你能解释一下为什么sed 1q 的行为与cat 不同吗?
  • sed 1q 读取第一行,然后立即退出。如果管道中的一个成员终止(此处为sed),那么所有其他成员(此处为$(...))也将终止,然后才能将输出分配给$pid。只有 sleep 5 在后台继续。
  • 如果这种解决方案(阻止标准输出)被接受,那么您将不再需要 sed。 See my solution.
  • 这比@anubhava 的方法有什么优势吗?让我觉得做同样事情的效率较低(考虑到使用sed 与进程内内置read 相比的开销)。
  • @CharlesDuffy 一件显而易见的事情是它在必要时不需要命名管道。
【解决方案5】:

我找到了一个很好的方法,不需要子shell,会保持父子关系。 因为:[1] 21763[1]+ Done sleep 5都是stderr,也就是&amp;2。 我们可以将&amp;2重定向到/dev/null,代码如下:

exec 7>&2 2>/dev/null # Here backup 2 to 7, and redirect 2 to /dev/null
sleep 5
wait
exec 2>&7 7>&- # here restore 7 to 2, and delete 7.

见:Using exec

【讨论】:

    猜你喜欢
    • 2017-04-04
    • 1970-01-01
    • 2017-01-23
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多