如何使管道等待文件结束或出现错误后停止?


12

在观看有关管道恶作剧的视频后,我尝试了以下命令。

man -k . | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf | zathura -

它基本上将要打印的手册页列表打印出来,供用户选择其中之一,然后使用xargs运行man -Tpdf %(打印以从xargs的输入中输出手册页git的pdf),然后将pdf传递给pdf阅读器(zathura )。

问题在于(如您在视频中看到的),即使在dmenu中选择一个联机帮助页之前,pdf阅读器也会启动。而且,如果我单击Esc并没有选择任何内容,则pdf阅读器仍处于打开状态,根本没有显示任何文档。

如何使pdf阅读器(以及管道链中的任何其他命令)仅在其输入到达文件末尾或完全收到输入时才运行?或者,或者,在链接的命令之一返回非零退出状态之后,如何使管道链停止(这样,如果dmenu返回未选择选项的错误,则不会运行以下命令)?


1
您正在使用什么外壳?这是狂欢吗?
terdon

我已经在bash,zsh和sh上尝试过。他们都有相同的行为。
Seninha

2
是的,此行为是标准行为,由于pipefail库萨兰丹达的回答中提到了bash的选择,我问哪个外壳。
terdon

Answers:


12

如何使pdf阅读器(以及管道链中的任何其他命令)仅在其输入到达文件末尾或完全收到输入时才运行?

ifne(在Debian中是moreutils打包的):

ifne 仅当标准输入不为空时,才运行以下命令。

在您的情况下:

 | ifne zathura -

感谢您的回答,我不知道该命令!此命令(以及中的其他命令moreutils)应该在原始Unix中,并由posix指定...这是一个基本的,
类似于

@Seninha的简单性ifne有点欺骗性。Unix没有“管道偷看”操作,因此ifne在决定运行相关命令之前,实际上必须至少读取一个字节。这意味着它不仅可以测试并执行命令,还必须创建另一个管道,派生另一个进程来运行相关命令,并将整个流从stdin管道复制到下游管道。如果“输入为空”的情况不常见,ifne则平均而言,很容易花费比节省的资源更多的资源。

@ Wumpus.Q.Wumbley这是一个神话-你不是有读任何字节,以确定是否有在管道中的数据。看这里。在Linux上,您实际上可以查看管道中的数据(即在不删除数据的情况下读取数据)。我已经在此处提到了“规范”答案,并在评论中提到了更多内容,但是它们被mods删除,因为他们可能认为这些事实像在削弱答案的出色程度。
mosvy

6

Pdf文件应该是可搜索的;任何pdf查看器都必须首先查看预告片,然后从那里跳转到外部参照表的偏移量。

由于管道是不可搜索的,zathura因此将使用混淆技巧,即将所有输入复制到临时文件中,然后照常使用该临时文件。这种“聪明”的把戏正在产生错误的希望,并导致人们认为pdf文件是可流式传输的。

但是无论如何,zathura实际上在显示文档之前确实要等待EOF,您不必为此做任何事情:

(sleep 10; cat file.pdf) | zathura -
# will really show the content of file.pdf after 10 seconds

问题是zathura没有选择只能在文件确定的情况下打开窗口,而在不是这种情况的情况下退出并返回错误-就像一切正常一样,它只会停留在该位置:

$ dd if=file.pdf bs=50000 count=1 status=none | zathura -
error: could not open document  # its window still hanging around showing nothing

$ echo $?
0  # really?

因此,即使您自己将输出重定向到一个临时文件,并且仅zathura在一切正常的情况下才运行,也无法保证如果zathura由于某种原因或其他原因不喜欢该输出,则不会为用户提供黑色窗口。


顺便说一句,

man -X man

会在X11窗口中显示一个联机帮助页gxditview,即使它看起来直接是70年代;-)

而且,当然,您始终可以使用:

... | xargs xterm -e man

除了许多其他增强功能外,它还使您可以在搜索和适当的文本选择中使用正则表达式。


6

管道中的所有命令几乎同时启动。只有通过管道进行同步的I / O。而且,管道只能保存管道缓冲区允许的尽可能多的信息。

因此,您不可避免地要运行管道的一个阶段,因为

  1. 无论如何,其他阶段一经启动,该阶段的命令就会启动,并且
  2. 如果该命令没有使用管道上的输入,它将阻塞管道的前几个阶段。

而是将输出写入文件,同时使管道完成。然后使用该文件。

示例(作为带有一个参数的函数):

myman () {
    tmpfile=$( mktemp )

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile" && [ -s  "$tmpfile" ]
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

zathura如果管道失败(xargs部分返回非零)或生成的文件为空,则这将另外不运行程序。

bash外壳程序中,您可能还需要设置pipefailshell选项,set -o pipefail以使管道返回失败的管道中第一个命令的退出状态。然后,您想创建tmpfile变量local

myman () {
    local tmpfile=$( mktemp )

    if [ -o pipefail ]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile"
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

pipefail如果尚未设置此功能,则会在功能持续时间内设置该选项,然后根据需要取消设置该选项。它摆脱了-s对输出文件的测试。


1
为什么rm -f呢 您是否在考虑管道更改tmpfile权限的情况?
terdon

2
@terdon我正在考虑临时文件被过早删除的情况。rm -f如果文件已被删除(可能是通过zathura,我不知道)不会出错。
Kusalananda

第一个功能无法按预期工作:它也会使zathura显示黑色窗口,但是现在zathura在管道完成之后运行,而不是在管道旁边运行。这是因为管道返回xargs的退出状态,该状态为0。在管道中失败的命令是dmenu(当我什么都不选择时返回1)。具有该pipefail选项的bash函数按预期方式工作(在zsh中也具有相同的选项)。
Seninha

1
@Sininha我通过检查生成的文件是否为非空来修复了第一个功能。
Kusalananda
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.