使用 /dev/stdin 和 Heredoc 从命令行传递文件

使用 /dev/stdin 和 Heredoc 从命令行传递文件

我很好奇如何将heredocs 作为文件传递给命令行实用程序背后的理论。

最近,我发现我可以将文件作为heredoc传递。

例如:

awk '{ split($0, arr, " "); print arr[2] }' <<EOF
foo bar baz
EOF
bar

这对我来说是有利的,原因如下:

  • Heredocs 提高了多行输入的可读性。
  • 我不需要记住每个实用程序标志来从命令行传递文件内容。
  • 我可以在给定文件中使用单引号和双引号。
  • 我可以控制外壳扩展。

例如:

ruby <<EOF
puts "'hello $HOME'"
EOF
'hello /Users/mbigras'

ruby <<'EOF'
puts "'hello $HOME'"
EOF
'hello $HOME'

我不清楚发生了什么。看起来 shell 认为heredoc 是一个内容等于heredoc 值的文件。我已将这种技术用于 cat,但我仍然不确定发生了什么:

cat <<EOL
hello world
EOL
hello world

我知道cat打印文件的内容,所以推测这个heredoc是某种临时文件。

当我“将定界符传递给命令行程序”时,我对到底发生了什么感到困惑。

这是一个使用的示例ansible-剧本。我将一本剧本作为heredoc传递给该实用程序;但是它失败了,如使用所示echo $?

ansible-playbook -i localhost, -c local <<EOF &>/dev/null
---
- hosts: all
  gather_facts: false
  tasks:
    - name: Print something
      debug:
        msg: hello world
EOF
echo $?
5

但是,如果我将相同的heredoc传递给该实用程序,但在/dev/stdin它之前它会成功

ansible-playbook -i localhost, -c local /dev/stdin <<EOF &>/dev/null
---
- hosts: all
  gather_facts: false
  tasks:
    - name: Print something
      debug:
        msg: hello world
EOF
echo $?
0
  • 当一个人“将定界符作为文件传递”时到底发生了什么?
  • 为什么第一个版本ansible-playbook失败但第二个版本成功?
  • /dev/stdin通过heredoc有什么意义?
  • 为什么其他实用程序喜欢rubyawk不需要/dev/stdinheredoc之前的?

答案1

当一个人“将定界文档作为文件传递”时到底发生了什么?

你不是。Here-documents提供标准输入,就像一根管子。你的例子

awk '{ ... }' <<EOF
foo bar baz
EOF

完全等于

echo foo bar baz | awk '{ ... }'

awkcat、 以及ruby所有从标准输入读取的内容(如果未在命令行上提供可读取的文件名)。这是一个实施选择。

为什么带有 anisble-playbook 的第一个版本失败但第二个版本成功?

ansible-playbook默认情况下不从标准输入读取,而是需要文件路径。这是一个设计选择。

/dev/stdin很可能是 的符号链接/dev/fd/0,这是谈论当前进程的一种方式文件描述符#0(标准输入)。这是你的内核(或系统库)公开的东西。该ansible-playbook命令/dev/stdin像常规文件系统文件一样打开,并最终读取其自己的标准输入,否则该输入将被忽略。

您可能还拥有FD 1 和 2 的/dev/stdout链接/dev/stderr,如果您要告诉某些内容将其输出放在何处,也可以使用它们。

在定界符之前传递 /dev/stdin 有什么意义?

它是命令的一个参数ansible-playbook

为什么像 ruby​​ 或 awk 这样的其他实用程序在heredoc之前不需要 /dev/stdin ?

默认情况下,它们从标准输入读取作为设计选择,因为它们是为在管道中使用而设计的。他们出于同样的原因写入标准输出。

答案2

Here-docs 究竟发生了什么取决于 shell 如何实现 here-doc:它可以在内部使用管道(如 )dash或临时文件描述符(如bash.因此,在一种情况下可能不可能lseek(),但在另一种情况下 - 可以(对于普通用户来说,这意味着您可以跳过此处文档的内容)。看相关答案

至于两个 ansible-playbook 命令的情况,它还取决于命令是如何实现的(所以除非你阅读源代码,否则你实际上不会知道)。有些命令只是检查是否提供了文件,不支持stdin.其他命令如awkruby- 它们被设计为期望stdin或 在命令行上指定的文件。

但是,您可以尝试做的是,如果您使用的是 Linux,请运行strace ansible-playbook ...<other args>并查看它尝试打开哪些内容、发生哪些系统调用等。例如,您将看到使用strace -e open tail /dev/stdin <<< "Jello World"tail 命令实际上会尝试打开/dev/stdin为文件,而trace -e open tail没有。

答案3

此处文档是重定向到命令的标准输入,就像<.这意味着,在任何您可能使用<重定向文件内容的地方,您都可以重定向此处文档的内容。 POSIX 标准列出此处文档以及其他重定向运算符

在您的 Ansible 示例中,ansible-playbook默认情况下不会从其标准输入流读取,因为它需要文件名。通过将其/dev/stdin作为文件名,然后在标准输入上提供此处文档,您可以绕过实用程序中的此限制。 “文件/dev/stdin”将始终包含当前进程的标准输入数据流。

ruby以及awk许多其他实用程序将从标准输入读取除非文件名在命令行上提供。

所以,你是技术上当你说“似乎shell认为heredoc是一个内容等于heredoc值的文件”时,这是错误的。它的行为不像文件(就具有文件名和可查找而言),而是作为标准输入上的数据流。至少从效用的角度来看是这样。

差异与之间相同

cat file

cat <file

在第一个实例中,cat打开文件file,但在第二个实例中(这也是此处文档发生的情况),因为没有将文件名作为参数给出catcat所以只读取其标准输入流(以及打开文件,或在实用程序的标准输入上提供此处文档)。该实用程序不需要知道所提供的数据是否来自文件、管道、此处文档或其他数据源。

shell 如何实现此处文档在某种程度上并不重要,但它可能是通过使用 FIFO 或实际上使用临时文件来实现的。

相关内容