我很好奇如何将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有什么意义?- 为什么其他实用程序喜欢
ruby
或awk
不需要/dev/stdin
heredoc之前的?
答案1
当一个人“将定界文档作为文件传递”时到底发生了什么?
你不是。Here-documents提供标准输入,就像一根管子。你的例子
awk '{ ... }' <<EOF
foo bar baz
EOF
完全等于
echo foo bar baz | awk '{ ... }'
awk
、cat
、 以及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
.其他命令如awk
和ruby
- 它们被设计为期望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
,但在第二个实例中(这也是此处文档发生的情况),因为没有将文件名作为参数给出cat
,cat
所以只读取其标准输入流(以及壳打开文件,或在实用程序的标准输入上提供此处文档)。该实用程序不需要知道所提供的数据是否来自文件、管道、此处文档或其他数据源。
shell 如何实现此处文档在某种程度上并不重要,但它可能是通过使用 FIFO 或实际上使用临时文件来实现的。