如何在Bash脚本中将Heredoc写入文件?


Answers:


1070

阅读《高级Bash脚本指南》第19章

这是一个将内容写入文件的示例 /tmp/yourfilehere

cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
        This line is indented.
EOF

请注意,最后的'EOF'(The 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

11
您甚至不需要Bash,此功能也位于Bourne / Korn / POSIX shell中。
Janus Troelsen

5
<<<又叫什么呢?
尔根·保罗

18
@PineappleUndertheSea <<<被称为“这里字符串”。像这样的代码tr a-z A-Z <<< 'one two three'将导致字符串ONE TWO THREE。有关详细信息,请访问en.wikipedia.org/wiki/Here_document#Here_strings
Stefan Lasiewski 2013年

8
最终的EOF 后面也不应有任何空格。至少在bash上,这导致其未被识别为定界符
carpii

10
由于此特定Heredoc旨在作为文字内容,而不是包含替代内容,因此应为<<'EOF'而不是<<EOF
查尔斯·达菲

152

除了使用catI / O重定向之外,还可以使用以下方法tee

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

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


18
我建议> /dev/null在第一行的末尾添加,以防止在创建文件时将here文件的内容显示到stdout。
Joe Carroll 2013年

11
是的,但是您的解决方案之所以吸引我,是因为它与兼容sudo,而不是因为它的简洁:-)
Joe Carroll 2013年

2
您将如何使用此方法附加到现有文件?
MountainX

5
@MountainX签出man tee。使用该-a标志附加而不是覆盖。
利文(Livven)

3
对于有时需要监视的配置脚本,我更喜欢这一点,因为它会打印内容。
2013年

59

注意:

问题(如何在bash脚本中将here文档(aka Heredoc)写入文件?)具有(至少)3个主要的独立维度或子问题:

  1. 您要覆盖现有文件,追加到现有文件还是写入新文件?
  2. 您的用户还是其他用户(例如, root)是否拥有该文件?
  3. 您要按字面意义编写Heredoc的内容,还是让Bash解释Heredoc内部的变量引用?

(还有其他我认为不重要的维度/子问题。请考虑编辑此答案以添加它们!)以下是上述问题的维度的一些更重要的组合,带有各种不同的定界标识符-没有任何内容神圣EOF,只需确保您用作定界标识符的字符串不会出现在Heredoc中:

  1. 要覆盖您拥有的现有文件(或写入新文件),请在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
  2. 要附加您拥有的现有文件(或写入新文件),请在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
  3. 要使用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
  4. 要附加您拥有的现有文件(或写入新文件)以及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
  5. 要覆盖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
  6. 要附加用户= foo拥有的现有文件(或写入新文件),并带有heredoc的文字内容:

    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 2015年

2
@Aleksandr Makov:如何使用#6覆盖现有文件的内容?忽略-a== --append; 即tee -a-> tee。请参阅info tee(我在这里引用了它,但是评论标记太有限了
。– TomRoche

1
使用Cat和管道代替T形管,对#6有好处sudo tee /path/to/your/file << 'Screw_you_Foo'吗?
codemonkee

为什么FOE而不是EOF在附加示例中?
becko

2
@becko:只是为了说明标签只是标签。请注意,在每个示例中我都使用了不同的标签。
TomRoche

41

要以@Livven的答案为基础,以下是一些有用的组合。

  1. 变量替换,保留前导选项卡,覆盖文件,回显到stdout

    tee /path/to/file <<EOF
    ${variable}
    EOF
  2. 无变量替换,保留前导选项卡,覆盖文件,回显到stdout

    tee /path/to/file <<'EOF'
    ${variable}
    EOF
  3. 变量替换,删除前导选项卡,覆盖文件,回显到stdout

    tee /path/to/file <<-EOF
        ${variable}
    EOF
  4. 变量替换,保留前导选项卡,追加到文件,回显到stdout

    tee -a /path/to/file <<EOF
    ${variable}
    EOF
  5. 变量替换,保留前导选项卡,覆盖文件,不回显stdout

    tee /path/to/file <<EOF >/dev/null
    ${variable}
    EOF
  6. 以上可以结合sudo以及

    sudo -u USER tee /path/to/file <<EOF
    ${variable}
    EOF

16

需要root权限时

如果目标文件需要root权限,请使用|sudo tee代替>

cat << 'EOF' |sudo tee /tmp/yourprotectedfilehere
The variable $FOO will *not* be interpreted.
EOF

是否可以将变量传递到此处文档?如何获得它,以便对$ FOO进行解释?
user1527227

@ user1527227如此处文件示例所示:不会解释变量$ FOO 。
Serge Stroobandt 2014年

下面,我尝试将这个答案与Stefan Lasiewski的答案结合起来并组织起来。
TomRoche

3
@ user1527227只是不要将EOF用单引号引起来。然后,将解释$ FOO。
imnd_neel

11

对于以后可能遇到此问题的人们,以下格式适用:

(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

6
不需要括号:cat << END > afile紧随其后的Heredoc效果很好。
glenn jackman 2010年

谢谢,这实际上解决了我遇到的另一个问题。在这里找到一些文档后,出现了一些问题。我认为这与括号有关,因为上面的建议已将其修复。
约书亚·恩菲尔德

2
这行不通。输出重定向需要cat在以接受的答案所示的开头的行的末尾。
暂停,直到另行通知。

2
@DennisWilliamson它起作用了,这就是括号的用途。整个过程cat在一个子外壳中运行,并且该子外壳的所有输出都重定向到文件
Izkata

2
@Izkata:如果您查看此答案的编辑历史记录,则会在我发表评论之前将括号删除,然后再添加回去。格伦·杰克曼(和我)的评论适用。
暂停,直到另行通知。

3

例如,您可以使用它:

首先(建立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

第四(使用变量):

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

-1

我喜欢缩进脚本中的简洁,可读性和表示方式:

<<-End_of_file >file
       foo bar
End_of_file

哪里→       是一个tab字符。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.