为什么$(sudo cat)找不到存在的文件?
for host in $(cat /etc/ansible/hosts | cut -d ' ' -f 1 | grep -P '^master-' | sort | uniq)
do
ssh ${host} /bin/bash << EOF
sudo cat /etc/origin/master/ca-bundle.crt
EOF
done
-----开始证书-----
XYZ ...
-----结束证书-----
for host in $(cat /etc/ansible/hosts | cut -d ' ' -f 1 | grep -P '^master-' | sort | uniq)
do
ssh ${host} /bin/bash << EOF
RESULT="$(sudo cat /etc/origin/master/ca-bundle.crt; echo x)"
echo "${RESULT%x}"
EOF
done
“cat:/etc/origin/master/ca-bundle.crt:没有这样的文件或目录”
根据答案,修复如下并工作。非常感谢。
#!/bin/bash
set -u
for host in $(cat /etc/ansible/hosts | cut -d ' ' -f 1 | grep -P '^master-' | sort | uniq)
do
ssh ${host} /bin/bash <<'EOF'
RESULT="$(sudo cat /etc/origin/master/ca-bundle.crt; echo x)"
echo "${RESULT%x}"
EOF
done
答案 0 :(得分:3)
问题出现了,因为命令和变量替换是在 传递给命令之前在here-documents 中执行的。因此,$(sudo cat /etc/origin/master/ca-bundle.crt; echo x)
和${RESULT%x}
都在本地计算机上进行评估(其中/etc/origin/master/ca-bundle.crt可能不存在且RESULT
不是&#39} ; t定义)。因此,这是发送到远程计算机的内容:
RESULT="x"
echo ""
这根本不是你想要的。
为避免这种情况,您可以转义here-document中的$
字符,或引用here-document分隔符(<<'EOF'
)。顺便说一句,here-document中的缩进字符将通过ssh
连接传递,这可能会产生不幸的后果(例如调用远程shell的自动完成功能)。我要么支持here-document,要么在分隔符前加上&#34; - &#34; (<<-'EOF'
)并使用制表符(而不是空格)进行缩进。
除非在远程计算机上完成的工作比问题中显示的要多,否则ssh
部分可以(并且应该)大大简化。不需要将证书文件的内容放在变量中然后回显变量,只需直接输出即可。并注意到整个问题的产生是因为处理进出变量的复杂性;如果远程部分只是sudo cat /etc/origin/master/ca-bundle.crt
,整个事情就会起作用。此外,无需显式运行远程bash shell并使用重定向为其提供运行命令,只需使用ssh ${host} sudo cat /etc/origin/master/ca-bundle.crt
。
好的,变量的东西确实做了一件事。它在证书文件的内容之后添加了一个空行。这是因为&#34; x&#34;诀窍仔细保留文件内容中的最终换行符(通常,$( )
删除尾随换行符),然后echo
添加另一个换行符,从而产生一个空白行。但如果这是故意的,只需添加另一个没有参数的echo
命令就可以更容易(也更清晰)。
列出主机名的管道要比它需要的时间长得多,但是这里需要在复杂性和可读性/清晰度之间进行权衡。 tripleee的版本要短得多,但要更好地了解它所做的事情(除非你能够流利地使用awk
),这意味着&#39;更多的错误风险,维护难度等等。但我仍然建议进行一些简化:
请勿使用cat
将单个文件送入管道。使用<filename
重定向,或者(如果命令支持它)只需给它命令文件名并让它从中读取:
cut -d ' ' -f 1 /etc/ansible/hosts | ...
cut -d ' ' -f 1 </etc/ansible/hosts | ...
这似乎不如使用cat |
那么自然,但它是最好(也是最标准)的做事方式。坦率地说,如果它看起来不自然或令人困惑,那就意味着你还没有做到这一点。所以做得更多。
您的管道中的每个命令都只执行以下操作:cut
获取第一个字段,grep
选择以&#34开头的字段;#34;,{{1按顺序放置它们,sort
删除重复项。但uniq
完全能够删除重复项,因此我使用sort
代替sort -u
。并且sort | uniq
几乎可以完成所有事情(但如果你做得太多,就很难阅读/理解)。我个人使用:
awk
(awk '$1 ~ /^master-/ {print $1}' /etc/ansible/hosts | sort -u
脚本可以读为&#34;如果第一个字段以&#39; master - &#39;开头,则打印出来。)
然后是如何迭代主机列表的问题。 awk
被广泛认为是反模式,因为可能出错的事情数量很多。它将命令的输出分解为&#34; words&#34; (可能或可能不对应于行),然后尝试将它找到的任何通配符展开到匹配文件列表中(可能具有真正奇怪的效果),然后迭代结果。
在这种特殊情况下,我认为您是安全的(除非您重新定义for var in $(somecommand)
)。您的条目中不应包含任何单词分隔符(空格,制表符和换行符),因为您已经只选择了第一个字段。条目中不应该有任何通配符,因为&#34; *&#34;,&#34;?&#34;和&#34; [&#34;通常不允许在主机名中使用。我想在&#34; [hexdigits:hexdigits :: hexdigits]&#34;中可能存在原始IPv6地址。格式 - 如果有匹配的文件, 是一个shell通配符而将导致麻烦 - 但{{1}将不会选择这些格式模式。
替代方法是使用管道列表来$IFS
命令(如在@ tripleee&#39; s答案中)或^master-
循环。这两者都有一个不同的潜在问题,如果您不小心,xargs
可以从输入流中窃取主机名。 while read
会阻止这种情况发生。
在这种情况下,我想我只是使用ssh
循环。所有这些,这是我建议的重写:
ssh -n
答案 1 :(得分:1)
你的循环驱动程序仍然有点可怕。你可以轻而易举地避免useless use of cat
,但整个事情看起来像是在乞求重构
#!/bin/bash
awk '$1 !~ /^master-/ && !a[$1] { print $1; ++a[$1] }' /etc/ansible/hosts |
xargs -r -n 1 ssh {} sudo cat /etc/origin/master/ca-bundle.crt
这消除了添加然后删除尾随x
- 我怀疑你最初是以结束了因为你正在捕获然后echo
输出而不是让cat
做它做的事 - 打印到标准输出。