在Bash中如何退出管道?

9
以下是Bash语句的翻译:

对于下面的bash语句:

tail -Fn0 /tmp/report | while [ 1 ]; do echo "pre"; exit; echo "past"; done

我得到了 "pre",但没有退出到bash提示符,然后如果我在 /tmp/report 中输入任何内容,我就可以从这个脚本退出并进入bash提示符。

我认为这是合理的。 'exit' 使 'while' 语句退出,但 'tail' 仍然存活。 如果有东西输入到 /tmp/report,则 'tail' 将输出到管道,然后 'tail' 将检测到管道关闭,然后 'tail' 退出。

  1. 我说的对吗?如果不对,能否有人提供正确的解释?
  2. 是否可能向 'while' 语句中添加任何内容以立即从整个管道语句中退出?我知道我可以将 tail 的 pid 保存到一个临时文件中,在 'while' 中读取此文件,然后杀死 tail。 有更简单的方法吗?
  3. 让我扩大一下问题。如果在脚本文件中使用此 tail|while,是否可能同时实现以下项目? a. 如果输入 Ctrl-C 或信号到达主shell进程,则主shell和由主shell生成的各种子shell和后台进程将退出 b. 我可以仅在触发情况下从 tail|while 退出,并保留其他子进程继续运行 c. 最好不要使用临时文件或管道文件。

可能是重复的问题:无法使用Ctrl+c终止shell - shellter
2个回答

7
你说得对。 while 循环在子 shell 中执行,因为它的输入被重定向了,exit 只会退出该子 shell。
如果你运行的是 bash 4.x,你可以尝试使用协程来实现你想要的效果。
coproc TAIL { tail -Fn0 /tmp/report.txt ;}
while [ 1 ]
do
    echo "pre"
    break
    echo "past"
done <&${TAIL[0]}
kill $TAIL_PID

http://www.gnu.org/software/bash/manual/html_node/Coprocesses.html

在早期版本中,您可以使用一个写入命名管道的后台进程:

pipe=/tmp/tail.$$
mkfifo $pipe
tail -Fn0 /tmp/report.txt >$pipe &
TAIL_PID=$!
while [ 1 ]
do
    echo "pre"
    break
    echo "past"
done <$pipe
kill $TAIL_PID
rm $pipe

谢谢你的帮助!现在我的知识正在增长。嗯,我的bash是3.2.5版本。也许你的是最好的,而我还在期待其他更好的版本。 - Qiu Yangfan
你可以使用在后台写入到命名管道的进程来模拟协处理器。 - Barmar
添加了使用命名管道的解决方案。 - Barmar
1
当读取器关闭管道时,管道的编写者会收到错误。如果在循环内部进行重定向,则每次通过循环打开和关闭它,而第一次关闭将终止编写者。 - Barmar
1
在第二个版本中,管道的读取端口在整个循环中只打开一次,并且仅在循环完成时关闭。 - Barmar
显示剩余3条评论

2

你可以(不可靠地)杀掉进程组:

tail -Fn0 /tmp/report | while :
do 
  echo "pre"
  sh -c 'PGID=$( ps -o pgid= $$ | tr -d \  ); kill -TERM -$PGID'
  echo "past"
done

这可能会向比您想要的更多的进程发送信号。如果您在交互式终端中运行上述命令,那么应该没问题,但是在脚本中运行时,很可能(实际上是很有可能)进程组将包括运行该命令的脚本。为了避免发送信号,最好启用监视并在后台运行管道,以确保为管道形成新的进程组:

#!/bin/sh

# In Posix shells that support the User Portability Utilities option
# this includes bash & ksh), executing "set -m" turns on job control. 
# Background processes run in a separate process group.  If the shell
# is interactive, a line containing their exit status is printed to
# stderr upon their completion.
set -m
tail -Fn0 /tmp/report | while :
do 
  echo "pre"
  sh -c 'PGID=$( ps -o pgid= $$ | tr -d \  ); kill -TERM -$PGID'
  echo "past"
done &
wait

请注意,我已将while [ 1 ]替换为while :,因为while [ 1 ]风格较差(它的行为与while [ 0 ]完全相同)。请注意保留HTML标记。

根据我的测试,-m参数很重要,而无论是否在后台运行似乎都是一样的,在这两种情况下,tail和while都在同一个新进程组中。您能否更详细地解释一下后台运行的情况? - Qiu Yangfan
标准要求如果启用作业控制,则后台管道应作为单独的进程组运行。虽然shell可以将前台组作为不同的进程组运行,但这并非必需。 - William Pursell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接