bash中的“ cat << EOF”如何工作?


627

我需要编写脚本以将多行输入输入到程序(psql)。

经过一番谷歌搜索,我发现以下语法有效:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

这样可以正确构造多行字符串(从BEGIN;to到END;inclusive),并将其作为管道输入到psql

但是我不知道它如何/为什么起作用,请解释一下吗?

我主要是指cat << EOF,我知道>输出到文件,>>追加到文件,<从文件读取输入。

到底<<是做什么的?

并且有手册页吗?


26
可能对没用cat。尝试psql ... << EOF ... 也请参见“这里字符串”。mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
已暂停,直至另行通知。

1
我很惊讶它适用于cat但不适用于echo。cat应该以stdin作为文件名,而不是char字符串。psql << EOF听起来很合逻辑,但实际上不合逻辑。适用于cat但不适用于echo。奇怪的行为。有什么线索吗?
亚历克斯(Alex)

对自己回答:不带参数的cat执行并通过输入(stdin)发送的任何内容复制到输出,因此使用其输出通过>填充文件。实际上,作为参数读取的文件名不是stdin流。
亚历克斯(Alex)

@Alex echo会在cat读取stding(通过管道传递到它)或读取与其命令行args相对应的文件时打印它的命令行参数
The-null-Pointer-

Answers:


517

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


来自man bash

这里文件

这种重定向类型指示外壳程序从当前源读取输入,直到看到仅包含单词(无尾随空格)的行。

直至该点为止的所有行都将用作命令的标准输入。

此处文档的格式为:

          <<[-]word
                  here-document
          delimiter

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

如果重定向操作符为<<-,则从输入行和包含定界符的行中删除所有前导制表。这允许外壳脚本中的此处文档以自然方式缩进。


12
我最困难的时间是禁用变量/参数扩展。我需要做的就是使用“双引号”,然后将其修复!谢谢(你的)信息!
Xeoncross

11
关于<<-请注意,仅除去前导制表符,而不去除软制表符。这是您实际上需要使用制表符的罕见情况之一。如果文档的其余部分使用软标签,请确保显示不可见的字符,并(例如)复制和粘贴标签字符。如果操作正确,语法突出显示应正确捕获结尾的定界符。
trkoch

1
我没有看到这个答案比下面的答案更有帮助。它只是
反击

@BrDaHa,也许不是。为什么这样问?因为投票?这几年来唯一的。通过比较日期可以看到。
Alexei Martianov,

500

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

cat <<EOFBash 中语法用法的示例:

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


1. 1和3可以不用猫做;2.示例1可以使用简单的多行字符串完成
Daniel Alder

268

在您的情况下,“ EOF”被称为“此处标签”。基本上<<Here告诉外壳程序您将输入多行字符串,直到“ tag”为止Here。您可以根据需要命名此标签,通常为EOFSTOP

有关Here标记的一些规则:

  1. 标签可以是任何字符串,可以是大写或小写,尽管大多数人习惯上都使用大写。
  2. 如果该行中还有其他单词,则该标签将不被视为Here标签。在这种情况下,它将仅被视为字符串的一部分。标签本身应位于单独的行上,才能视为标签。
  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

30
这是最好的实际答案...您同时定义了两者并明确说明了使用的主要目的,而不是相关的理论...这很重要,但不是必需的...谢谢-超级有用
-oemb1905

5
@edelans,您必须补充一点,在<<-使用时,“前导”选项卡不会阻止识别标签
The-null-Pointer-

1
您的答案使我点击“您将要输入多行字符串”
微积分

79

POSIX 7

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

重定向操作符“ <<”和“ <<-”都允许将外壳程序输入文件(称为“ here-document”)中包含的行重定向到命令的输入。

此处文档应被视为一个单词,该单词从下一个单词开始,一直持续到一行仅包含定界符和a,且中间没有字符。然后,下一个此处文档开始(如果有)。格式如下:

[n]<<word
    here-document
delimiter

其中可选的n表示文件描述符号。如果省略该数字,则本文参考标准输入(文件描述符0)。

如果对单词中的任何字符加引号,则应通过对单词执行引号删除来形成定界符,并且此处文档行不得扩展。否则,定界符应为单词本身。

如果单词中没有引号,则应扩展本文档的所有行,以进行参数扩展,命令替换和算术扩展。在这种情况下,输入中的in表现为内部双引号(请参见Double-Quotes)。但是,除非在“ $()”,“``''或“ $ {}”中出现双引号,否则本文中的双引号字符('“')不应作特殊处理。

如果重定向符号为“ <<-”,<tab>则应从输入行和包含尾部定界符的行中删除所有前导字符。如果在一行上指定了多个“ <<”或“ <<-”运算符,则与第一个运算符关联的此处文档应由应用程序首先提供,并由外壳程序首先读取。

当从终端设备读取此处文档并且外壳是交互式的时,它将在读取输入的每一行之前将按外壳变量中所述进行处理的变量PS2的内容写入标准错误,直到识别出定界符为止。

例子

一些例子尚未给出。

引号防止参数扩展

不带引号:

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在此倾向于缩进。kes


在讨论<<-和的最后一个示例中<tab>a,应该注意的是,目的是允许脚本内的代码正常缩进,同时允许呈现给接收过程的Heredoc文本从第0列开始。这是一个不太常见的功能,更多的背景信息可能会阻止很多麻烦的事……
David C. Rankin

1
如果需要扩展EOF标签之间的某些内容而不必扩展某些内容,我该如何避免开支增加?
Jeanmichel Cote 2015年

2
...只是使用反斜杠在前面$
Jeanmichel科特迪瓦

@JeanmichelCote我没有更好的选择:-)使用常规字符串,您还可以考虑混合使用引号,例如"$a"'$b'"$c",但是AFAIK没有类似的引用。
西罗Santilli郝海东冠状病六四事件法轮功

25

使用tee代替cat

并非完全是对原始问题的答案,但是我还是想分享一下:我需要在需要根权限的目录中创建配置文件。

以下情况不适用于这种情况:

$ 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

在你的情况下使用sudo的庆典-c '猫<< EOF> /etc/somedir/foo.conf#我的配置文件foo =栏EOF'
likewhoa

4

对以上答案有一点扩展。尾随>将输入定向到文件中,覆盖现有内容。但是,一种特别方便的用法是>>附加的双箭头,将新内容添加到文件末尾,如下所示:

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而您不必担心意外修改其任何内容。


1

这不一定是对原始问题的答案,而是分享了我自己测试中的一些结果。这个:

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

将产生与以下文件相同的文件:

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

因此,我看不到使用cat命令的意义。


2
哪个壳?我在Ubuntu 18.04上使用bash 4.4以及在OSX上使用bash 3.2进行了测试。当<<test不使用时,两者都创建了一个空文件cat <<test
wisbucky

这在zsh的LInux Mint 19 Tara上为我工作
Geoff Langenderfer

0

值得注意的是,这里的文档也可以在bash循环中使用。此示例显示了如何获取表的列列表:

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

甚至没有换行

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner
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.