我很好奇如何将 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是某种临时文件。
当我“将heredoc传递给命令行程序”时,我对到底发生了什么感到困惑。
这是一个使用ansible-playbook的示例。我将实用程序作为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
- 当一个人“将heredoc作为文件传递”时到底发生了什么?
- 为什么第一个版本
ansible-playbook
失败但第二个版本成功? /dev/stdin
在heredoc之前通过有什么意义?- 为什么其他实用程序喜欢
ruby
或awk
不需要/dev/stdin
heredoc之前的?
你不是。Here-documents 提供标准输入,就像管道一样。你的例子
完全等同于
awk
,cat
, 和ruby
所有从标准输入读取的,如果它们没有在命令行上给出一个文件名来读取。那是一种实施选择。ansible-playbook
默认情况下不从标准输入读取,而是需要文件路径。这是一种设计选择。/dev/stdin
很可能是指向的符号链接/dev/fd/0
,这是谈论当前进程的文件描述符#0(标准输入)的一种方式。这是您的内核(或系统库)公开的东西。该ansible-playbook
命令/dev/stdin
像常规文件系统文件一样打开,并最终读取其自己的标准输入,否则会被忽略。您可能还拥有指向 FD 1
/dev/stdout
和/dev/stderr
2 的链接,如果您要告诉某些东西将其输出放在哪里,您也可以使用它们。它是
ansible-playbook
命令的参数。默认情况下,它们从标准输入中读取作为设计选择,因为它们是为在管道中使用而设计的。他们出于同样的原因写入标准输出。
here-document 是对命令标准输入的重定向,就像
<
. 这意味着在您可以<
用来重定向文件内容的任何地方,您都可以重定向 here-document 的内容。POSIX 标准列出了 here-documents 以及其他重定向操作符。在您的 Ansible 示例中,
ansible-playbook
默认情况下不会从其标准输入流中读取,因为它需要文件名。通过将其/dev/stdin
作为文件名,然后在标准输入中提供 here-document,您可以绕过实用程序中的此限制。“/dev/stdin
文件”将始终包含当前进程的标准输入数据流。ruby
以及awk
许多其他实用程序将从标准输入中读取,除非在命令行上提供了文件名。因此,当您说“看起来 shell 认为 heredoc 是一个内容等于 heredoc 的值的文件”时,您在技术上是错误的。它不像文件(关于具有文件名和可搜索),而是作为标准输入上的数据流。至少从实用程序的角度来看。
之间的区别是一样的
和
在第一种情况下,
cat
打开文件file
,但在第二种情况下(这也是 here-document 发生的情况),因为没有将文件名作为参数提供给cat
,所以cat
只读取其标准输入流(并且shell打开文件,或在实用程序的标准输入上提供此处文档)。该实用程序不需要知道所提供的数据是来自文件、管道、此处文档还是其他一些数据源。shell 如何实现 here-documents 在某种程度上并不重要,但它可能是通过使用 FIFO 或实际上是通过临时文件实现的。
here-docs 到底发生了什么取决于 shell 如何实现 here-doc:它可以在内部使用管道
dash
(如bash
. 因此,在一种情况下,它可能是不可能的lseek()
,但在另一种情况下 - 它可以(对于普通用户来说,这意味着您可以跳过 here-doc 的内容)。请参阅相关答案。至于两个 ansible-playbook 命令的情况,还取决于命令是如何实现的(所以除非您阅读源代码,否则您实际上不会知道)。有些命令只是检查是否提供了文件,不支持
stdin
. 其他命令,如awk
和ruby
- 它们旨在预期stdin
或在命令行上指定的文件。但是,您可以尝试做的是,如果您使用的是 Linux,运行
strace ansible-playbook ...<other args>
并查看它尝试打开的内容、发生的系统调用等。例如,您会看到strace -e open tail /dev/stdin <<< "Jello World"
tail 命令实际上会尝试打开/dev/stdin
为文件,而trace -e open tail
没有。