如何将 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 文档中的文本的不良影响。在这种情况下,请使用 &lt;&lt;-(后跟破折号)禁用前导标签(注意,要对此进行测试,您需要将前导空格替换为制表符 ,因为我无法在此处打印实际的制表符。)

#!/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 中。 &lt;&lt;&lt; 呢,他们叫什么? @PineappleUndertheSea &lt;&lt;&lt; 被称为“这里的字符串”。像tr a-z A-Z &lt;&lt;&lt; 'one two three' 这样的代码将产生字符串ONE TWO THREE。更多信息en.wikipedia.org/wiki/Here_document#Here_strings 最后的 EOF 之后也不应该有任何空格。至少在 bash 上,这会导致它无法识别为分隔符 由于这个特殊的heredoc 是文字内容,而不是包含替换,它应该是&lt;&lt;'EOF' 而不是&lt;&lt;EOF【参考方案2】:

使用 tee 代替 cat 和 I/O 重定向可能会有用:

tee newfile <<EOF
line 1
line 2
line 3
EOF

它更简洁,而且与重定向运算符不同,如果您需要写入具有 root 权限的文件,它可以与 sudo 结合使用。

【讨论】:

我建议在第一行的末尾添加&gt; /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 &lt;&lt; '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而不是&gt;

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 &gt; 而不是| 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 &lt;&lt; END &gt; 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 语法

睡眠后如何使用heredoc发送一些输入?

在heredocs中着色,bash

Bash Heredoc 格式在 ) 末尾添加新行;

bash中的heredoc里面的范围,我的$ PORT变量不起作用

如何使 cat 和 grep 在 c 中的第一个和第二个管道中工作,就像 bash 中的 heredoc <<