如何安全、有序地运行管道?

Cyk*_*ker 5 bash pipe fifo

在Linux中,是否可以运行管道:

cmd1 | cmd2
Run Code Online (Sandbox Code Playgroud)

以这样的方式:

  1. cmd2cmd1直到完全完成才开始运行,并且

  2. 如果cmd1有错误,则cmd2根本不运行,并且管道的退出状态是 的退出状态cmd1。

举个例子,如何制作这个管道:

false | echo ok
Run Code Online (Sandbox Code Playgroud)

什么都不输出并返回非零状态?


失败的解决方案1

set -o pipefail
Run Code Online (Sandbox Code Playgroud)

该管道确实具有非零退出状态,但cmd2即使cmd1失败仍然运行。

解决方案2失败

cmd1 && cmd2
Run Code Online (Sandbox Code Playgroud)

这不是管道。无 I/O 重定向。

解决方案 3 失败

mkfifo /tmp/fifo
cmd1 > /tmp/fifo && cmd2 < /tmp/fifo
Run Code Online (Sandbox Code Playgroud)

它会阻塞。

次优解

touch /tmp/file
cmd1 > /tmp/file && cmd2 < /tmp/file
Run Code Online (Sandbox Code Playgroud)

这似乎有效。但它有几个缺点:

  1. 它将数据写入 I/O 速度较慢的磁盘。(当然您可以使用tmpfs,但这是一个额外的系统要求)。

  2. 您必须仔细选择临时文件名。否则它可能会意外覆盖现有文件。mktemp可能会有所帮助,但未命名管道可以完全节省您的命名工作。

  3. 临时文件所在的文件系统可能不够大,无法容纳整个数据。

  4. 临时文件不会自动清理。

xhi*_*nne 4

我们不知道cmd1输出的大小,但管道的缓冲区大小有限。一旦将一定量的数据写入管道,任何后续写入都将阻塞,直到有人读取管道(类似于失败的解决方案 3)。

\n\n

您必须使用保证不阻塞的机制。对于非常大的数据,请使用临时文件。否则,如果您有能力将数据保留在内存中(这毕竟是管道的想法),请使用:

\n\n
result=$(cmd1) && cmd2 < <(printf \'%s\' "$result")\nunset result\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里 的结果cmd1存储在变量 中result。如果cmd1成功,cmd2则执行并提供 中的数据result。最后,result取消设置以释放相关内存。

\n\n

注意:以前,我使用此处字符串 ( <<< "$result") 来提供cmd2数据,但 St\xc3\xa9phane Chazelas 观察到这bash会创建一个您不想要的临时文件。

\n\n

回答评论中的问题:

\n\n
    \n
  • 是的,命令可以随意链接:

    \n\n
    result=$(cmd1) \\\n&& result=$(cmd2 < <(printf \'%s\' "$result")) \\\n&& result=$(cmd3 < <(printf \'%s\' "$result")) \\\n...\n&& cmdN < <(printf \'%s\' "$result")\nunset result\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
  • 不,上述解决方案不适合二进制数据,因为:

    \n\n
      \n
    1. 命令替换$(...)会吃掉所有尾随换行符。
    2. \n
    3. \\0命令替换结果中的NUL 字符 ( ) 的行为未指定(例如,Bash 会丢弃它们)。
    4. \n
  • \n
  • 是的,为了避免二进制数据的所有这些问题,您可以使用类似的编码器base64(或uuencode,或自制的编码器,仅处理 NUL 字符和尾随换行符):

    \n\n
    result=$(cmd1 > >(base64)) && cmd2 < <(printf \'%s\' "$result" | base64 -d)\nunset result\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    在这里,我必须使用进程替换 ( >(...)) 才能保持cmd1退出值不变。

  • \n
\n\n

也就是说,仅仅为了确保数据不写入磁盘似乎就相当麻烦。中间临时文件是更好的解决方案。请参阅St\xc3\xa9phane\ 的答案,它解决了您对此的大部分担忧。

\n

  • @Kusalananda,这取决于 shell,在支持 `&lt;&lt;&lt;` zsh 运算符的 5 个类似 Bourne 的 shell 中,NUL 将保留在 `zsh` 中,在 `bash` 和 `mksh` 以及第一个之后的所有内容中被丢弃NUL 将在“ksh93”和“yash”中丢失。在任何这些 shell 中,尾随的换行符都会丢失,并且会通过“&lt;&lt;&lt;”添加回来。另请注意,大多数情况下“&lt;&lt;&lt;”是通过临时文件实现的。因此,除了存储在内存中之外,数据还会写入文件系统。 (3认同)
  • Linux 上的 @Kusalananda `ls -ld /proc/self/fd/0 &lt;&lt;&lt; x` 在 `${TMPDIR-/tmp}` 中给出了已删除的常规临时文件 (2认同)