如何确定一个命令是否通过管道传输到另一个命令中?

Chr*_*ing 10 bash shell-script

使用将有关自身的一些信息转储到 stderr 然后调用cat以形成管道的 bash 函数,我如何知道管道中的哪个阶段调用了哪个其他阶段?

一种解决方案是为每个阶段提供一个名称,如, a, b, c, d,然后当我查看输出时,我知道a > b > c > d. 但假设我不想命名阶段。有没有办法可以查看阶段的文件描述符并看到一个阶段stdout是另一个阶段,stdin所以我知道前者正在进入后者?如果是这样,我如何识别共享管道?我试过了,lsof但我不知道如何建立链条。

例如,

    printf '%s\n' main "$(lsof -p $$ | grep dev)" > /dev/stderr

    pipe() {
        printf '%s %s:%s %s\n' $1 $BASHPID $BASH_SUBSHELL $$ > /dev/stderr
        printf '%s\n' $1 "$(lsof -p $$ | grep dev)" > /dev/stderr
        cat
    }

    echo
    echo hi | pipe a | pipe b | pipe c | pipe d
Run Code Online (Sandbox Code Playgroud)

生产

main
bash    51147 Setup    0u   CHR               16,2  0t476770                3717 /dev/ttys002
bash    51147 Setup    1u   CHR               16,2  0t476770                3717 /dev/ttys002
bash    51147 Setup    2u   CHR               16,2  0t476770                3717 /dev/ttys002
bash    51147 Setup   26u   CHR               15,0  0t214349                 579 /dev/ptmx
bash    51147 Setup   27u   CHR               15,1 0t1206500                 579 /dev/ptmx

a 51176:1 51147
d 51147:0 51147
b 51177:1 51147
c 51179:1 51147
b
bash    51147 Setup    1u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup    2u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup   26u   CHR               15,0  0t214349                 579 /dev/ptmx
bash    51147 Setup   27u   CHR               15,1 0t1206500                 579 /dev/ptmx
bash    51147 Setup  254u   CHR               16,2  0t477304                3717 /dev/ttys002
d
bash    51147 Setup    1u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup    2u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup   26u   CHR               15,0  0t214349                 579 /dev/ptmx
bash    51147 Setup   27u   CHR               15,1 0t1206500                 579 /dev/ptmx
bash    51147 Setup  254u   CHR               16,2  0t477304                3717 /dev/ttys002
a
bash    51147 Setup    1u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup    2u   CHR               16,2  0t477304                3717 /dev/ttys002
bash    51147 Setup   26u   CHR               15,0  0t214349                 579 /dev/ptmx
bash    51147 Setup   27u   CHR               15,1 0t1206500                 579 /dev/ptmx
bash    51147 Setup  254u   CHR               16,2  0t477304                3717 /dev/ttys002
c
bash    51147 Setup    1u   CHR               16,2  0t478702                3717 /dev/ttys002
bash    51147 Setup    2u   CHR               16,2  0t478702                3717 /dev/ttys002
bash    51147 Setup   26u   CHR               15,0  0t214349                 579 /dev/ptmx
bash    51147 Setup   27u   CHR               15,1 0t1206500                 579 /dev/ptmx
bash    51147 Setup  254u   CHR               16,2  0t478702                3717 /dev/ttys002
hi
Run Code Online (Sandbox Code Playgroud)

但我没有看到stdoutin的标识符astdinfor b

xhi*_*nne 18

管道是匿名的(与命名管道相反),因为它们是用pipe(2)命令创建的。但是,它们当然有一个由lsof(您可以在 中看到ls -l /proc/<pid>/fd)显示的内部 ID 。

但是我没有看到 stdout 的标识符a作为b.

这是因为您没有lsof采用正确的流程。$$扩展到您的脚本 PID,而不是由管道命令创建的子 shell 的 PID。您必须$BASHPID改用。

如此修改您的函数:

pipe() {
    local pid=$BASHPID
    printf '%s %s:%s %s\n' "$1" "$pid" "$BASH_SUBSHELL" $$ > /dev/stderr
    printf '%s\n' "$1" "$(lsof -a -p "$pid" -d 0,1)" > /dev/stderr
    cat
}
Run Code Online (Sandbox Code Playgroud)

你现在会得到类似的东西:

COMMAND  PID  USER    FD  TYPE DEVICE SIZE/OFF      NODE NAME
bash    1290  xhienne 0r  FIFO   0,12      0t0 180254229 pipe
bash    1290  xhienne 1w  FIFO   0,12      0t0 180254230 pipe
Run Code Online (Sandbox Code Playgroud)

... 其中节点号是管道的内部 ID。然后您将能够注意到管道 #12345678 将 stdout of 连接a到 stdin of b,依此类推。