您如何确定传送给您的实际命令?


9

假设我有一个名为的bash脚本log.sh。在此脚本中,我想从管道中读取输入,但我也想知道用于将输入管道输送给我的命令。例:

tail -f /var/log/httpd/error | log.sh

在shell脚本中,我想知道命令tail -f /var/log/httpd/error


我对你为什么想要这个感到很好奇。
伊格纳西奥·巴斯克斯

我的猜测是您在制作某种类型的GUI程序来捕获和处理pid吗?
palbakulich 2011年

我想知道,所以我可以区分将结果放在哪里。根据命令和文件名,我想执行不同的操作。
圣约翰·约翰逊的

4
对我来说,这听起来像是对“最不惊讶原则”的重大违反。如果脚本在不同情况下应做不同的事情,则应由命令行选项而不是其输入来源来控制。
Dave Sherohman

@戴夫,我同意。只是说,为了这个示例,我只想“知道”传入的命令是什么。
圣约翰·约翰逊

Answers:


7

晃建议使用lsof

这是编写脚本的方法:

whatpipe2.sh

#!/bin/bash

pid=$$
pgid=$(ps -o pgid= -p $pid)
lsofout=$(lsof -g $pgid)
pipenode=$(echo "$lsofout" | awk '$5 == "0r" { print $9 }')
otherpids=$(echo "$lsofout" | awk '$5 == "1w" { print $2 }')
for pid in $otherpids; do
    if cmd=$(ps -o cmd= -p $pid 2>/dev/null); then
        echo "$cmd"
        break
    fi
done

运行它:

$ tail -f /var/log/messages | ./whatpipe2.sh
tail -f /var/log/messages
^C

另一种方法是使用过程组。

whatpipe1.sh

#!/bin/bash    

pid=$$
# ps output is nasty, can (and usually does) start with spaces
# to handle this, I don't quote the "if test $_pgrp = $pgrp" line below
pgrp=$(ps -o pgrp= -p $pid)
psout=$(ps -o pgrp= -o pid= -o cmd=)
echo "$psout" | while read _pgrp _pid _cmd; do
    if test $_pgrp = $pgrp; then
        if test $_pid != $pid; then
            case $_cmd in
            ps*)
                # don't print the "ps" we ran to get this info
                # XXX but this actually means we exclude any "ps" command :-(
                ;;
            *)
                echo "$_cmd"
                ;;
            esac
        fi
    fi
done

运行它:

$ tail -f /var/log/messages | ./whatpipe1.sh
tail -f /var/log/messages
^C

请注意,只有当管道左侧的命令运行足够长的时间ps才能看到它们时,它们两者才起作用。您说您曾与一起使用它tail -f,所以我怀疑这是一个问题。

$ sleep 0 | ./whatpipe1.sh 

$ sleep 1 | ./whatpipe1.sh
sleep 1

而不是这个巨大的帖子,我会使用基于lsof的脚本给出第二个答案。不错的工作。
akira

@akira谢谢。进行了几次尝试,以使其干净和便携式。沿途学习了一些关于procfs和lsof的知识。谢谢你的主意。
Mikel

我接受了您的回答,因为它提供了其他人可以直接使用的答案。@Akira,您完成了大部分工作,对不起,我也无法接受您的工作。
圣约翰·约翰逊,

10

该管道将​​在您的进程的打开文件描述符列表中显示为条目:

 % ls -l /proc/PID/fd
 lr-x------ 1 xyz xyz 64 Feb 11 08:05 0 -> pipe:[124149866]
 lrwx------ 1 xyz xyz 64 Feb 11 08:05 1 -> /dev/pts/2
 lrwx------ 1 xyz xyz 64 Feb 11 08:05 2 -> /dev/pts/2
 lr-x------ 1 xyz xyz 64 Feb 11 08:05 10 -> /tmp/foo.sh

您还可以使用类似:

 % lsof -p PID
 sh      29890 xyz  cwd    DIR   0,44    4096  77712070 /tmp
 sh      29890 xyz  rtd    DIR   0,44    4096  74368803 /
 sh      29890 xyz  txt    REG   0,44   83888  77597729 /bin/dash
 sh      29890 xyz  mem    REG   0,44 1405508  79888619 /lib/tls/i686/cmov/libc-2.11.1.so
 sh      29890 xyz  mem    REG   0,44  113964  79874782 /lib/ld-2.11.1.so
 sh      29890 xyz    0r  FIFO    0,6         124149866 pipe
 sh      29890 xyz    1u   CHR  136,2                 4 /dev/pts/2
 sh      29890 xyz    2u   CHR  136,2                 4 /dev/pts/2
 sh      29890 xyz   10r   REG   0,44      66  77712115 /tmp/foo.sh

因此,比起管道的inode来说:),您现在可以在/proc/该管道下搜索所有其他进程。那么您将获得传递给您的命令:

 % lsof | grep 124149866 
 cat     29889 xyz    1w  FIFO                0,6          124149866 pipe
 sh      29890 xyz    0r  FIFO                0,6          124149866 pipe

在此示例中,通过cat管道传输到ward sh。在其中,/proc/29889您可以找到一个名为的文件cmdline,该文件告诉您确切的名称:

 % cat /proc/29889/cmdline
 cat/dev/zero%  

命令行的字段由NUL分隔,因此看起来有点难看:)


我不知道接受哪个答案。@Akira,您提供了有关如何确定的实际细分,而@Mikel给了我一个脚本。使事情变得很棒+困难的方法。
圣约翰·约翰逊

1

这是lsof在现代Linux发行版上使用现代工具的紧凑型解决方案:

cmd=$(lsof -t -p $$ -a -d 0 +E | while read p; do
    [ $p -ne $$ ] && echo "$(tr \\000 " " </proc/$p/cmdline)"
done)

这会列出+E当前shell进程(-p $$ -a -d 0)上FD 0 的终结点文件(),然后将输出限制为仅PID(-t),从而在管道的两侧产生PID。

注意:

  1. 在源端发现的PID可能不止一个,例如,{ echo Hi; sleep 5 ; } | whatpipe.sh可能会产生bash(输入子外壳)和sleep 5
  2. +E仅当使用lsof编译时才可用-DHASUXSOCKEPT。对于大多数现代Linux发行版来说,这应该是正确的,但无论如何都要检查安装:lsof -v 2>&1 | grep HASUXSOCKEPT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.