【问题标题】:How to get the PID of a process that is piped to another process in Bash?如何获取通过管道传输到 Bash 中另一个进程的进程的 PID?
【发布时间】:2010-12-11 18:44:36
【问题描述】:

我正在尝试在 Bash 中实现一个简单的日志服务器。它应该将一个文件作为参数并使用 netcat 在端口上提供它。

( tail -f $1 & ) | nc -l -p 9977

但问题是当 netcat 终止时,tail 仍然在运行。 (澄清:如果我不分叉尾部进程,即使 netcat 终止,它也会继续永远运行。)

如果我以某种方式知道尾巴的 PID,那么我可以在之后杀死它。
显然,使用 $!将返回 netcat 的 PID。

如何获取尾部进程的PID?

【问题讨论】:

  • 如果不使用&会怎样? tail -f 应该在那里等着。我不明白& 的用途,尽管它看起来确实是更大脚本的一部分。无论如何,如果你杀死管道,我会认为尾巴会死(只要你没有背景它)。
  • 根据我对 shell 的理解,& 在这种情况下,其含义是“从背景开始”。至少,这是我现在看到的行为,试图将解决方案应用于我自己的类似问题。

标签: bash pipe pid


【解决方案1】:

另一种选择:使用重定向到子shell。这改变了后台进程的启动顺序,所以 $!给出tail 进程的PID。

tail -f $1 > >(nc -l -p 9977) &
wait $!

【讨论】:

  • 这种方式的好处是等待之后,$?也持有退出状态
  • 从长远来看,这似乎是最干净的方法。从启动顺序以外的任何一个过程的角度来看,它是否会改变任何东西(与标准管道相比)?
  • 实际上,该重定向的语法是错误的(尽管原理是正确的)。 >(foo) 被替换为新文件描述符的名称,而 > >(foo) 实际上将输出重定向到它。你希望第一行是tail -f $1 > >(nc -l -p 9977) &
  • 正是我需要的!谢谢!在我的情况下,仅重定向 pid 不起作用。
  • 如果您想在子shell 中同时捕获标准输出和标准错误,请使用&> 进行重定向。示例:tail -f $1 &> >(nc -l -p 9977) &.
【解决方案2】:

将 tail 的 PID 写入文件描述符 3,然后从那里捕获它。

( tail -f $1 & echo $! >&3 ) 3>pid | nc -l -p 9977
kill $(<pid)

【讨论】:

  • 我使用了一个变体:( tail -f $1 &amp; echo $! &gt;pid ) | nc -l -p 9977(不确定为什么在最终重定向到文件时使用文件描述符 3 会有所帮助)
  • 不知道为什么,但我的解决方案在输出几行日志后失败。可能在管道缓冲区已满时。那么初始进程似乎是在等待处理管道。
【解决方案3】:

这个怎么样:

jobs -x echo %1

%1 用于链中的第一个作业,%2 用于第二个等。jobs -x 将作业说明符替换为 PID。

【讨论】:

  • 这必须是“如何在链中更早地获取任何进程的 pid”的最干净 + 最短的解决方案,谢谢!它还可以与“&”一起使用,在后台运行的链中更早地获取进程的 pid!例如。 dd if=/dev/urandom bs=1M count=1024 | sha1sum &amp; pid=$(jobs - x echo %1)kill -USR1 $pid
  • 答案是错误的。整个管道只分配一个作业 ID:shopt -s lastpipe; sleep 1 | cat | jobs -> [1] Running sleep 1 | cat。它具有管道上第一个命令的 PID,但 jobs -l 打印所有 PID:shopt -s lastpipe; sleep 1 | cat | jobs -l; jobs -p -> [1] 5394 Running sleep 1 5395 | cat 5394
【解决方案4】:

这对我有用(SLES Linux):

tail -F xxxx | tee -a yyyy &
export TAIL_PID=`jobs -p`
# export TEE_PID="$!"

如果用户可以在同一台机器上为两个“实例”运行脚本,则此线程中提到的 ps|grep|kill 技巧将不起作用。

jobs -x echo %1 对我不起作用(手册页没有 -x 标志)但给了我尝试jobs -p 的想法。

【讨论】:

  • 注意,这种方式只能获取管道上第一个命令的PID。 jobs -l 将打印所有命令的 PID,但您必须解析其输出以提取它们。
  • 与许多其他 cmets 不同,这也适用于 /bin/sh 以获取第一个命令(进程组负责人)的 PI​​D,例如以便以后能够杀死整个管道。
【解决方案5】:

也许你可以使用fifo,这样你就可以捕获第一个进程的pid,例如:

FIFO=my_fifo

rm -f $FIFO
mkfifo $FIFO

tail -f $1 > $FIFO &
TAIL_PID=$!

cat $FIFO | nc -l -p 9977

kill $TAIL_PID

rm -f $FIFO

【讨论】:

  • 是的,我以前试过。使用 fifo 的问题是一样的:管道永远不会被终止,所以即使 netcat 终止,cat 也会继续运行。此外,该控件保留在 cat 行中,因此它永远不会执行 kill。
  • 这很奇怪 - 上面的脚本在 Mac OS X 上非常适合我。唯一的细微差别是我省略了 nc 的“-p”标志。
  • 可能是平台问题(关于如何处理管道)。我正在linux机器上尝试。无论如何感谢您的回答!
  • 如果删除cat 并使用nc -l -p 9977 &lt; $FIFO 会怎样?
【解决方案6】:

最后,我设法使用ps 找到了尾部进程。感谢 ennukiller 的想法。

我已经使用ps 从args 中grep tail 并杀死它。这是一种黑客攻击,但它确实有效。 :)

如果你能找到更好的方法,请分享。

这是完整的脚本:
(最新版本可以在这里找到:http://docs.karamatli.com/dotfiles/bin/logserver

if [ -z "$1" ]; then
    echo Usage: $0 LOGFILE [PORT]
    exit -1
fi
if [ -n "$2" ]; then
    PORT=$2
else
    PORT=9977
fi

TAIL_CMD="tail -f $1"

function kill_tail {
    # find and kill the tail process that is detached from the current process
    TAIL_PID=$(/bin/ps -eo pid,args | grep "$TAIL_CMD" | grep -v grep | awk '{ print $1 }')
    kill $TAIL_PID
}
trap "kill_tail; exit 0" SIGINT SIGTERM

while true; do
    ( $TAIL_CMD & ) | nc -l -p $PORT -vvv
    kill_tail
done

【讨论】:

  • 不应该在$! 中提供tail 命令的PID,以便您可以简单地使用kill $! 而不是kill_tail
【解决方案7】:

ncat 在退出时自动终止tail -f(在 Mac OS X 10.6.7 上)!

# simple log server in Bash using ncat
# cf. http://nmap.org/ncat/
touch file.log
ncat -l 9977 -c "tail -f file.log" </dev/null   # terminal window 1
ncat localhost 9977 </dev/null                  # terminal window 2
echo hello > file.log                           # terminal window 3

【讨论】:

    【解决方案8】:

    一种方法是简单地使用脚本 ppid 执行 ps -ef 和 grep for tail

    【讨论】:

    • 我无法使用 ppid 获取它,因为当我将它分叉到子 shell 中时它已分离。但我设法使用 ps 程序的 args 参数对其进行了 grep。
    【解决方案9】:

    你试过了吗:

    nc -l -p 9977 -c "tail -f $1"
    

    (未经测试)

    如果您的nc 没有-c,则-e 或带有脚本文件。您可能必须有一个使用GAPING_SECURITY_HOLE 选项编译的nc。是的,您应该从该选项名称中推断出适当的警告。

    【讨论】:

    • 哦,我从来没有想过。我希望它有效:) 但由于“tail -f”的性质,它仍然没有终止
    • 没有-f,没关系。但我想实时服务。
    【解决方案10】:

    您可以仅使用 Bash I/O 重定向将tail 命令的 pid 存储在变量中(请参阅How to get the PID of a process in a pipeline)。

    # terminal window 1
    # using nc on Mac OS X (FreeBSD nc)
    : > /tmp/foo
    PID=$( { { tail -f /tmp/foo 0<&4 & echo $! >&3 ; } 4<&0 | { nc -l 9977 ;} & } 3>&1 | head -1 )
    kill $PID
    
    # terminal window 2
    nc localhost 9977
    
    # terminal window 3
    echo line > /tmp/foo
    

    【讨论】:

      【解决方案11】:

      不是一个理想的答案,但我找到了我工作的记录器守护程序的解决方法:

      #!/bin/sh
      tail -f /etc/service/rt4/log/main/current --pid=$$ | grep error
      

      来自 $info 尾:

      --pid=PID
                with -f, terminate after process ID, PID dies
      

      【讨论】:

        【解决方案12】:

        您可以使用两次coproc 命令。

        给定的示例转换为:

        coproc TAIL { tail -f $1; }; exec {TAIL[1]}<&-
        coproc NC { nc -v -l -p 9977; } <&"${TAIL[0]}" >&1
        wait $NC_PID; echo "nc exit code: $!"
        kill $TAIL_PID; echo "done"
        

        (我在其中抛出了一个 -v 和几个 echo 以进行故障排除。)

        使用coproc 感觉很像在其他各种脚本语言中使用 Popen()。

        【讨论】:

        • 关闭tail 的标准输入,例如。示例中的&lt;/dev/nullexec 这样做很好,这样就不会留下悬空未连接的流,但对于tail 命令来说并不是绝对必要的,因为tail 在给定a 时从不从stdin 读取文件名。当第一个 coproc 命令是 tail 时,可能会被忽略。
        • 请注意 coproc 在 Bash 4 中是新的(例如 OSX 仍然使用 3.2)。它在 OSX 的 zsh 中可用。
        【解决方案13】:

        bobbogo answer 有效,但需要中间 pid 文件

        您可以利用process substitution 功能&gt;(),其工作方式类似于|,但无需等待tail 完成

        tail -f $1 > >(nc -l -p 9977) & pid=!
        

        【讨论】:

        【解决方案14】:

        tail 的 --pid 选项是你最好的朋友。它将允许您完全控制在后台运行的管道。阅读 tail 命令选项以获得更大的弹性,以防您的文件被另一个进程主动轮换,这可能会让您拖尾一个非活动的 inode。下面的示例虽然不用于处理数据,但演示了对尾部的“强加”限制以及随时告诉它退出的能力。用于衡量 httpd 的服务压力。

          # Set the tail to die in 100 second even if we die unexpectedlly.
        sleep 100 & ;  ctlpid=$!
        tail -q -n 0 --follow=name --retry --max-unchanged-stats=1 --pid=$ctlpid -f  /var/log/httpd/access_log 2>/dev/null | wc –l > /tmp/thisSampleRate &
        …. Do some other work
        ….  Can kill the pipe at any time by killing $ctlpid 
        …. Calculate preassure if /tmp/thisSampleRate is ready
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-03
          • 1970-01-01
          • 1970-01-01
          • 2022-01-16
          相关资源
          最近更新 更多