如何将 heredoc 写入 Bash 脚本中的文件?
Posted
技术标签:
【中文标题】如何将 heredoc 写入 Bash 脚本中的文件?【英文标题】:How can I write a heredoc to a file in Bash script? 【发布时间】:2011-02-26 12:58:41 【问题描述】:如何在 Bash 脚本中将 here 文档写入文件?
【问题讨论】:
另见***.com/questions/22697688/… 【参考方案1】:阅读高级 Bash 脚本指南Chapter 19. Here Documents。
这是一个将内容写入/tmp/yourfilehere
的文件的示例
cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
This line is indented.
EOF
请注意,最后的 'EOF' (LimitString
) 在单词前面不应有任何空格,因为这意味着将无法识别 LimitString
。
在 shell 脚本中,您可能希望使用缩进来使代码可读,但是这可能会产生缩进 here 文档中的文本的不良影响。在这种情况下,请使用 <<-
(后跟破折号)禁用前导标签(注意,要对此进行测试,您需要将前导空格替换为制表符 ,因为我无法在此处打印实际的制表符。)
#!/usr/bin/env bash
if true ; then
cat <<- EOF > /tmp/yourfilehere
The leading tab is ignored.
EOF
fi
如果你不想解释文本中的变量,那么使用单引号:
cat << 'EOF' > /tmp/yourfilehere
The variable $FOO will not be interpreted.
EOF
通过命令管道传递heredoc:
cat <<'EOF' | sed 's/a/b/'
foo
bar
baz
EOF
输出:
foo
bbr
bbz
... 或使用sudo
将heredoc 写入文件:
cat <<'EOF' | sed 's/a/b/' | sudo tee /etc/config_file.conf
foo
bar
baz
EOF
【讨论】:
你甚至不需要 Bash,这个功能也在 Bourne/Korn/POSIX shell 中。<<<
呢,他们叫什么?
@PineappleUndertheSea <<<
被称为“这里的字符串”。像tr a-z A-Z <<< 'one two three'
这样的代码将产生字符串ONE TWO THREE
。更多信息en.wikipedia.org/wiki/Here_document#Here_strings
最后的 EOF 在之后也不应该有任何空格。至少在 bash 上,这会导致它无法识别为分隔符
由于这个特殊的heredoc 是文字内容,而不是包含替换,它应该是<<'EOF'
而不是<<EOF
。【参考方案2】:
使用 tee
代替 cat
和 I/O 重定向可能会有用:
tee newfile <<EOF
line 1
line 2
line 3
EOF
它更简洁,而且与重定向运算符不同,如果您需要写入具有 root 权限的文件,它可以与 sudo
结合使用。
【讨论】:
我建议在第一行的末尾添加> /dev/null
,以防止此处文件的内容在创建时显示到标准输出。
没错,但你的解决方案之所以吸引我,是因为它与sudo
兼容,而不是因为它的简洁性:-)
您将如何使用此方法附加到现有文件?
@MountainX 查看man tee
。使用-a
标志追加而不是覆盖。
为了在我有时需要监督的配置脚本中使用,我更喜欢这个因为它会打印内容。【参考方案3】:
注意:
以下内容浓缩并整理了本主题中的其他答案,尤其是Stefan Lasiewski 和Serge Stroobandt 的出色工作 Lasiewski 和我推荐Ch 19 (Here Documents) in the Advanced Bash-Scripting Guide问题(如何将 here 文档(又名 heredoc)写入 bash 脚本中的文件?)具有(至少)3 个主要的独立维度或子问题:
-
您要覆盖现有文件、追加到现有文件还是写入新文件?
您的用户或其他用户(例如,
root
)是否拥有该文件?
您是想按字面意思编写heredoc 的内容,还是让bash 解释heredoc 中的变量引用?
(还有其他我认为不重要的维度/子问题。考虑编辑此答案以添加它们!)以下是上面列出的问题维度的一些更重要的组合,以及各种不同的定界标识符 - -EOF
没有什么神圣之处,只要确保您用作分隔标识符的字符串不出现在您的 heredoc 中:
覆盖您拥有的现有文件(或写入新文件),替换 heredoc 中的变量引用:
cat << EOF > /path/to/your/file
This line will write to the file.
$THIS will also write to the file, with the variable contents substituted.
EOF
要附加您拥有的现有文件(或写入新文件),替换 heredoc 中的变量引用:
cat << FOE >> /path/to/your/file
This line will write to the file.
$THIS will also write to the file, with the variable contents substituted.
FOE
使用 heredoc 的文字内容覆盖您拥有的现有文件(或写入新文件):
cat << 'END_OF_FILE' > /path/to/your/file
This line will write to the file.
$THIS will also write to the file, without the variable contents substituted.
END_OF_FILE
使用 heredoc 的文字内容附加您拥有的现有文件(或写入新文件):
cat << 'eof' >> /path/to/your/file
This line will write to the file.
$THIS will also write to the file, without the variable contents substituted.
eof
要覆盖root拥有的现有文件(或写入新文件),替换heredoc中的变量引用:
cat << until_it_ends | sudo tee /path/to/your/file
This line will write to the file.
$THIS will also write to the file, with the variable contents substituted.
until_it_ends
使用 heredoc 的文字内容附加 user=foo 拥有的现有文件(或写入新文件):
cat << 'Screw_you_Foo' | sudo -u foo tee -a /path/to/your/file
This line will write to the file.
$THIS will also write to the file, without the variable contents substituted.
Screw_you_Foo
【讨论】:
#6 是最好的。但是如何用#6 覆盖现有文件的内容? @Aleksandr Makov:你如何用#6 覆盖现有文件的内容?省略-a
== --append
;即,tee -a
-> tee
。见info tee
(我会在这里引用它,但评论标记太有限了。
#6 使用 cat 和 pipe 代替 sudo tee /path/to/your/file << 'Screw_you_Foo'
是否有好处?
为什么在附加示例中使用FOE
而不是EOF
?
@becko:只是为了说明标签只是一个标签。请注意,我在每个示例中使用了不同的标签。【参考方案4】:
以@Livven 的answer 为基础,这里有一些有用的组合。
变量替换,保留前导选项卡,覆盖文件,回显到标准输出
tee /path/to/file <<EOF
$variable
EOF
无变量替换,保留前导选项卡,覆盖文件,回显到标准输出
tee /path/to/file <<'EOF'
$variable
EOF
变量替换,删除前导标签,覆盖文件,回显到标准输出
tee /path/to/file <<-EOF
$variable
EOF
变量替换,保留前导标签,附加到文件,回显到标准输出
tee -a /path/to/file <<EOF
$variable
EOF
变量替换,保留前导选项卡,覆盖文件,不回显到标准输出
tee /path/to/file <<EOF >/dev/null
$variable
EOF
上面也可以和sudo
结合
sudo -u USER tee /path/to/file <<EOF
$variable
EOF
【讨论】:
【参考方案5】:需要root权限时
当目标文件需要root权限时,使用|sudo tee
而不是>
:
cat << 'EOF' |sudo tee /tmp/yourprotectedfilehere
The variable $FOO will *not* be interpreted.
EOF
cat << "EOF" |sudo tee /tmp/yourprotectedfilehere
The variable $FOO *will* be interpreted.
EOF
【讨论】:
是否可以将变量传递给这里的文档?你怎么能得到它,所以 $FOO 被解释了? Below 我试图将这个答案与Stefan Lasiewski 的答案结合起来。 @user1527227 只是不要将 EOF 括在单引号中。然后 $FOO 将被解释。 如果您不想再次将输入打印回标准输出,您也可以使用| sudo cat >
而不是| sudo tee
。当然,现在你可能使用了两次cat
,并且双重调用了“不必要地使用猫”的模因。【参考方案6】:
对于未来可能遇到此问题的人,以下格式有效:
(cat <<- _EOF_
LogFile /var/log/clamd.log
LogTime yes
DatabaseDirectory /var/lib/clamav
LocalSocket /tmp/clamd.socket
TCPAddr 127.0.0.1
SelfCheck 1020
ScanPDF yes
_EOF_
) > /etc/clamd.conf
【讨论】:
不需要括号:cat << END > afile
后跟heredoc 效果很好。
谢谢,这实际上解决了我遇到的另一个问题。经过几个here docs后,出现了一些问题。我认为这与括号有关,就像上面的建议一样。
这行不通。输出重定向需要位于以cat
开头的行的末尾,如接受的答案所示。
@DennisWilliamson 它有效,这就是括号的用途。整个cat
在一个子shell中运行,子shell的所有输出都被重定向到文件中
@Izkata:如果您查看此答案的编辑历史记录,括号已在我发表评论之前被删除,然后再添加回来。格伦杰克曼(和我)的评论适用。【参考方案7】:
作为实例,您可以使用它:
首先(建立 ssh 连接):
while read pass port user ip files directs; do
sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
PASS PORT USER IP FILES DIRECTS
. . . . . .
. . . . . .
. . . . . .
PASS PORT USER IP FILES DIRECTS
____HERE
第二(执行命令):
while read pass port user ip; do
sshpass -p$pass ssh -p $port $user@$ip <<ENDSSH1
COMMAND 1
.
.
.
COMMAND n
ENDSSH1
done <<____HERE
PASS PORT USER IP
. . . .
. . . .
. . . .
PASS PORT USER IP
____HERE
第三(执行命令):
Script=$'
#Your commands
'
while read pass port user ip; do
sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip "$Script"
done <<___HERE
PASS PORT USER IP
. . . .
. . . .
. . . .
PASS PORT USER IP
___HERE
Forth(使用变量):
while read pass port user ip fileoutput; do
sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip fileinput=$fileinput 'bash -s'<<ENDSSH1
#Your command > $fileinput
#Your command > $fileinput
ENDSSH1
done <<____HERE
PASS PORT USER IP FILE-OUTPUT
. . . . .
. . . . .
. . . . .
PASS PORT USER IP FILE-OUTPUT
____HERE
【讨论】:
【参考方案8】:对于那些寻找纯 bash 解决方案(或需要速度)的人,这里有一个没有 cat 的简单解决方案:
# here-doc tab indented
read -r -d '' || printf >file '%s' "$REPLY"; <<-EOF
foo bar
EOF
或简单的“mycat”功能(并避免将 REPLY 留在环境中):
mycat()
local REPLY
read -r -d '' || printf '%s' "$REPLY"
mycat >file <<-EOF
foo bar
EOF
“mycat”与 OS cat 的快速速度比较(我的 OSX 笔记本电脑上 1000 个循环 >/dev/null):
mycat:
real 0m1.507s
user 0m0.108s
sys 0m0.488s
OS cat:
real 0m4.082s
user 0m0.716s
sys 0m1.808s
注意:mycat 不处理文件参数,它只是处理“将 heredoc 写入文件”的问题
【讨论】:
【参考方案9】:我喜欢这种在缩进脚本中简洁、可读性和演示的方法:
<<-End_of_file >file
→ foo bar
End_of_file
→
是 tab 字符。
【讨论】:
【参考方案10】:如果你想让 heredoc 缩进以提高可读性:
$ perl -pe 's/^\s*//' << EOF
line 1
line 2
EOF
Bash中支持缩进heredoc的内置方法只支持前导制表符,不支持空格。
Perl 可以替换为 awk 以节省一些字符,但如果您了解基本的正则表达式,Perl 可能更容易记住。
【讨论】:
以上是关于如何将 heredoc 写入 Bash 脚本中的文件?的主要内容,如果未能解决你的问题,请参考以下文章
使用来自 bash 的标准输入的多行 perl 的 heredoc 语法