"cat << EOF" 在 bash 中是如何工作的?

1007

我需要编写一个脚本来向程序(psql)输入多行内容。

经过一番搜索,我发现以下语法是可行的:

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

这段代码正确地构造了一个多行字符串(从 BEGIN;END;,包括两端),并将其作为输入管道传递给 psql

但是我不知道它是如何工作的,有人能解释一下吗?

我的问题主要指向 cat << EOF,我知道 > 输出到文件,>> 追加到文件,< 从文件中读取输入。

那么 << 究竟是什么呢?

它是否有一个 man 页面?


42
这可能是对cat命令的无用使用。尝试使用psql ... << EOF ...来代替。另请参考"here strings"。http://mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings - Dennis Williamson
3
我很惊讶它可以使用cat但不能使用echo。cat应该期望一个文件名作为stdin,而不是一个字符字符串。psql << EOF听起来很合理,但其他情况则不然。使用cat可以正常工作,但使用echo却出现了奇怪的行为。对此有什么线索吗? - Alex
回答自己:不带参数的cat命令会执行并复制通过输入(stdin)发送的任何内容到输出,因此可以使用其输出通过>来填充文件。实际上,作为参数读取的文件名不是stdin流。 - Alex
@Alex echo 只是打印其命令行参数,而 cat 则读取标准输入(当通过管道传递给它时)或读取与其命令行参数对应的文件。 - The-null-Pointer-
1
@DennisWilliamson,您发布的链接已更改为http://mywiki.wooledge.org/BashGuide/InputAndOutput?#Heredocs_And_Herestrings。 - lupodellasleppa
12个回答

803

cat <<EOF语法在Bash中处理多行文本时非常有用,例如用于将多行字符串分配给shell变量、文件或管道。

cat <<EOF语法在Bash中的用法示例:

1. 将多行字符串分配给shell变量

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

现在$sql变量中也包含换行符。你可以使用echo -e "$sql"进行验证。

2. 在Bash中将多行字符串传递给文件

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

print.sh文件现在包含:

#!/bin/bash
echo $PWD
echo /home/user

3. 在Bash中向管道传递多行字符串

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

b.txt文件包含barbaz两行。相同的输出被打印到stdout


5
  1. 可以不用猫完成1和3;
  2. 示例1可以使用简单的多行字符串完成。
- Daniel Alder
2
为什么不使用IFS='' read -r -d,而要创建另一个'cat'进程呢? - Joseph Van Riper
1
值得注意的是,当使用tee而不是cat时,可以使用sudo将文件写入受限位置。例如sudo tee /etc/somepath/file > /dev/null <<EOF ... - Wolfson
我认为 cat 只适用于带有 here-document 的完整 bash 命令,如果没有 cat 或其他命令,则以符号 << 开始的 here-document 无法回显到 stdout,并且第一行剩余部分的 "grep 'b' | tee b.txt" 也无法获得输入。 - Yu Chai
23
这个答案为什么会有任何赞成票?你完全忽略了问题,那就是“它是如何/为什么工作的”。没有人要求列出随机示例,但它比(正确地)被接受的答案获得更多的赞成票。 - EntangledLoops
8
当我添加这些额外的例子时,已经有一个被接受的答案了。基于点赞数量,结果发现它们非常有用。和平。 - Vojtech Vitek - golang.cz

721

这被称为heredoc格式,以便将一个字符串提供到stdin中。有关详细信息,请参见https://en.wikipedia.org/wiki/Here_document#Unix_shells


man bash

  

Here Documents

     

此类重定向指示shell从当前源读取输入,直到看到仅包含单词的行(无尾随空格)。

     

然后使用到该点读取的所有行作为命令的标准输入。

     

here-documents的格式为:

          <<[-]word
                  here-document
          delimiter

对于word中的任何字符,都不会进行参数展开、命令替换、算术展开或路径名展开。如果word中有任何引号,则delimiter是在word上执行引号删除后的结果,并且here-document中的行不会被扩展。如果word未加引号,则所有here-document的行都将受到参数展开、命令替换和算术展开的影响。在后一种情况下,字符序列\<newline>将被忽略,并且必须使用\来引用字符\$`

如果重定向运算符是<<-,则从输入行和包含delimiter的行中去除了所有前导制表符。这使得shell脚本内的here-documents可以按自然方式进行缩进。


19
我曾经遇到过禁用变量/参数扩展的最困难问题。只需要使用“双引号”就解决了!感谢提供的信息! - Xeoncross
18
关于 <<-,请注意只有前导 制表符 被剥离--而非软制表符。这是少数情况之一,您实际上需要使用制表符。如果文档的其余部分使用了软制表符,请确保显示不可见字符并(例如)复制并粘贴一个制表符。如果操作正确,您的语法高亮应正确捕获结尾定界符。 - trkoch
2
我不认为这个答案比下面的更有帮助。它只是重复了可以在其他地方找到的信息(很可能已经被检查过了)。 - BrDaHa
1
@BrDaHa,也许不是。为什么会这样问呢?因为点赞吗?它曾经几年内是唯一的。通过比较日期可以看出。 - Alex Martian
@Xeoncross,你说的“双引号”是什么意思? - The Fool
显示剩余2条评论

494
在你的情况下,“EOF”被称为“Here Tag”。基本上,<<Here告诉Shell你将输入一个多行字符串,直到遇到“tag”Here。你可以随意命名此标签,通常使用EOFSTOP

这里有一些有关Here tag的规则:

  1. 标签可以是任何字符串,大写或小写,尽管按照惯例,大多数人使用大写字母。
  2. 如果该行中存在其他单词,则该标签将不被视为Here tag。在这种情况下,它将仅被视为字符串的一部分。要将其视为标签,标签应单独占用一行。
  3. 标签行中不应有前导或尾随空格,否则它将被视为字符串的一部分。

例如:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string

72
这是最佳的实际答案... 你需要定义两者并明确陈述其主要用途,而不是相关理论... 尽管理论重要但不是必须的... 谢谢 - 非常有帮助 - oemb1905
6
@edelans,你需要补充说明当使用<<-时,前导制表符不会阻止标记被识别。 - The-null-Pointer-
1
你的回答让我想到了“你将要输入一个多行字符串”。 - AbstProcDo

144

引号可以防止参数扩展

不使用引号:

a=0
cat <<EOF
$a
EOF

输出:

0

带引号:

a=0
cat <<'EOF'
$a
EOF

或者(丑陋但有效):

a=0
cat <<E"O"F
$a
EOF

输出:

$a

连字符会去除前导制表符

没有连字符:

cat <<EOF
<tab>a
EOF

其中<tab>是一个表示制表符的文字,你可以使用Ctrl + V <tab>进行插入。

输出:

<tab>a

带连字符:

cat <<-EOF
<tab>a
<tab>EOF

输出:

a

这当然存在,这样你就可以像周围的代码一样缩进你的cat,这样更容易阅读和维护。例如:

if true; then
    cat <<-EOF
    a
    EOF
fi

不幸的是,这对空格字符无效:POSIX 偏爱 tab 缩进。天哪。

POSIX 7

kennytm 引用了 man bash,但其中大部分也是 POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04

The redirection operators "<<" and "<<-" both allow redirection of lines contained in a shell input file, known as a "here-document", to the input of a command.

The here-document shall be treated as a single word that begins after the next <newline> and continues until there is a line containing only the delimiter and a <newline>, with no <blank> characters in between. Then the next here-document starts, if there is one. The format is as follows:

[n]<<word
    here-document
delimiter

where the optional n represents the file descriptor number. If the number is omitted, the here-document refers to standard input (file descriptor 0).

If any character in word is quoted, the delimiter shall be formed by performing quote removal on word, and the here-document lines shall not be expanded. Otherwise, the delimiter shall be the word itself.

If no characters in word are quoted, all lines of the here-document shall be expanded for parameter expansion, command substitution, and arithmetic expansion. In this case, the <backslash> in the input behaves as the <backslash> inside double-quotes (see Double-Quotes). However, the double-quote character ( '"' ) shall not be treated specially within a here-document, except when the double-quote appears within "$()", "``", or "${}".

If the redirection symbol is "<<-", all leading <tab> characters shall be stripped from input lines and the line containing the trailing delimiter. If more than one "<<" or "<<-" operator is specified on a line, the here-document associated with the first operator shall be supplied first by the application and shall be read first by the shell.

When a here-document is read from a terminal device and the shell is interactive, it shall write the contents of the variable PS2, processed as described in Shell Variables, to standard error before reading each line of input until the delimiter has been recognized.


1
在您最后一个例子中讨论 <<-<tab>a 时,应该注意到其目的是允许脚本中的正常缩进,同时允许传递给接收进程的heredoc文本从第0列开始。这是一个不太常见的功能,更多的上下文可能会避免很多困惑... - David C. Rankin
1
如果我的EOF标记中间的某些内容需要展开,而另一些则不需要展开,我应该如何转义展开? - Jeanmichel Cote
2
只需在 $ 前面使用反斜杠即可。 - Jeanmichel Cote
@JeanmichelCote 我看不到更好的选择 :-) 对于常规字符串,您也可以考虑混合引号,例如 "$a"'$b'"$c",但是在这里我认为没有类似的东西。 - Ciro Santilli OurBigBook.com
1
@jimmymcheung 我认为这就像双引号""字符串内部一样,除了"双引号本身不需要转义,正如“如果单词中没有引号字符”段落所述。 - Ciro Santilli OurBigBook.com
显示剩余2条评论

35

使用tee代替cat

虽然不是对原问题的直接回答,但我仍想分享一下:我需要在一个需要root权限的目录中创建配置文件。

以下方法并不能满足要求:

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF
因为重定向是在sudo上下文之外处理的,所以我最终使用了以下内容:

我最终使用了这个:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF

1
请使用以下命令将文本写入 /etc/somedir/foo.conf 文件中:sudo bash -c 'cat <<EOF >/etc/somedir/foo.conf

我的配置文件

foo=bar EOF'
- likewhoa
@likewhoa POSIX shell怎么样?有关于sudo bash -c的惯例吗? - jimmymcheung
谢谢!那解决了我的困惑!只是要指出,tee -a file << EOF会将多行文本追加到文件中,不添加>/dev/null将把多行文本打印到控制台。 - jimmymcheung

32

<< EoF基本上意味着:

<< - "从下一行开始读取多行输入,并将其视为单独文件中的代码"

EoF - "在多行输入中找到单词EoF后立即停止读取"

正如其他答案所解释的那样,多行输入被称为Here Document(即提供“内联”或“即时”的文档)。

Here Document通常用于生成要传递给后续进程的输出。例如,可以使用cat << EoF来生成所需的输出,使用Here Document。

以下是使用Here Document即时创建文本文档的示例:

cat << EoF > ./my-document.txt
Hello world
Have a nice day
EoF

13

对于以上答案的一个小扩展。末尾的>将输入重定向到文件中,覆盖已有内容。然而,双箭头>>特别方便,它会将新内容附加到文件末尾,如下所示:

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

这可以在不必担心意外修改 fstab 任何内容的情况下扩展它。


11

需要注意的是,cat << \EOT(请注意反斜杠)不会展开其中的任何变量,而cat << EOT将会展开。

示例:

FOO="bar"

cat << \EOT > foobar.txt
echo "$FOO"
EOT

将输出:

echo $FOO

而:

FOO="bar"

cat << EOT > foobar.txt
echo "$FOO"
EOT

将输出:echo "bar"


9

创建json文件的示例:

cat << EoF > ./allaccess.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}
EoF

结果如下:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

4
长话短说,EOF标记(但也可以使用不同的文字)是一种heredoc格式,允许您将输入作为多行提供。很多混淆似乎来自于cat的实际工作方式。您可以使用以下方式使用cat>>>
$ cat >> temp.txt
line 1
line 2

虽然在手动写入控制台时可以使用cat,但如果我想以更声明性的方式提供输入,以便可以被工具重复使用并保留缩进、空格等,这种方法就不太方便了。
Heredoc允许您将整个输入定义为一个独立的文本编辑器中键入的内容,而不是使用stdin。这就是Wikipedia文章所说的:

它是源代码文件的一部分,被视为一个单独的文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接