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

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

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

cat << EOF | psql ---paramsBEGIN;
`pg_dump ----something`
update table .... statement ...;
END;EOF

这将正确构造多行字符串(从BEGIN;END;,包括END;)并将其作为输入管道传输到psql

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

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

<<到底是做什么的?

有它的手册页吗?

1403081 次浏览

这称为这里文档格式以将字符串提供给标准输入。有关更多详细信息,请参阅https://en.wikipedia.org/wiki/Here_document#Unix_shells


man bash

这里文件

这种类型的重定向指示shell读取来自当前源直到一条线仅包含单词(没有尾随#36825;见

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

此处文档的格式为:

          <<[-]wordhere-documentdelimiter

没有参数扩展、命令替换、算术扩展或路径名扩展是在。如果中的任何字符是引用的分隔符上删除引号的结果,并且行在此处文档中不展开。如果未加引号,则此处文档进行参数扩展,命令代换和算术扩展。在后一种情况下,字符序列\<newline>\必须用于引用字符\$`

如果重定向运算符为<<-,则所有前导制表符从输入行和包含分隔符的行。这允许shell脚本中的here文档以自然的方式缩进。

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

Bash中cat <<EOF语法使用示例:

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

$ sql=$(cat <<EOFSELECT foo, bar FROM dbWHERE foo='baz'EOF)

$sql变量现在也包含新行字符。您可以使用echo -e "$sql"进行验证。

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

$ cat <<EOF > print.sh#!/bin/bashecho \$PWDecho $PWDEOF

print.sh文件现在包含:

#!/bin/bashecho $PWDecho /home/user

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

$ cat <<EOF | grep 'b' | tee b.txtfoobarbazEOF

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

在您的情况下,“EOF”被称为“这里标签”。基本上<<Here告诉shell您将输入多行字符串,直到“标签”Here。您可以随意命名此标签,通常是EOFSTOP

关于这里标签的一些规则:

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

POSIX 7

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

重定向运算符“<<”和“<<-”都允许将包含在shell输入文件(称为“bere-Document”)中的行重定向到命令的输入。

此处文档应被视为一个单词,从下一个<newline>之后开始,一直持续到有一行仅包含分隔符和<newline>,中间没有<blank>个字符。然后下一个此处文档开始,如果有的话。格式如下:

[n]<<wordhere-documentdelimiter

其中可选的n表示文件描述符编号。如果省略该编号,则此处文档引用标准输入(文件描述符0)。

如果引用word中的任何字符,则应通过对word进行引号删除来形成分隔符,此处文档行不得扩展。否则,分隔符应为单词本身。

如果word中没有引用任何字符,则此处文档的所有行都应展开以进行参数扩展、命令替换和算术扩展。在这种情况下,输入中的<backslash>在双引号内的行为与<backslash>相同(请参阅双引号)。但是,双引号字符('"')不应在此处文档中进行特殊处理,除非双引号出现在“$()”、“”或“${}”中。

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

当从终端设备读取here文档并且shell是交互式的时,它应该在读取每一行输入之前将变量PS2的内容(按照Shell变量中的描述进行处理)写入标准错误,直到分隔符被识别为止。

示例

一些例子还没有给出。

引号防止参数扩展

没有引号:

a=0cat <<EOF$aEOF

输出:

0

引用:

a=0cat <<'EOF'$aEOF

或(丑陋但有效):

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

产出:

$a

连字符删除了前导制表符

无连字符:

cat <<EOF<tab>aEOF

其中<tab>是文字选项卡,可以与Ctrl + V <tab>一起插入

输出:

<tab>a

连字符:

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

输出:

a

当然,这是为了让您可以像周围的代码一样缩进cat,这更容易阅读和维护。例如:

if true; thencat <<-EOFaEOFfi

不幸的是,这对空格字符不起作用:POSIX在这里喜欢tab缩进。

用T恤代替猫

不完全是对原始问题的回答,但我还是想分享这个:我需要在需要root权限的目录中创建一个配置文件。

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

$ sudo cat <<EOF >/etc/somedir/foo.conf# my config filefoo=barEOF

因为重定向是在sudo上下文之外处理的。

我最终使用了这个:

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

这不一定是对最初问题的回答,而是分享我自己测试的一些结果。这:

<<test > print.sh#!/bin/bashecho \$PWDecho $PWDtest

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

cat <<test > print.sh#!/bin/bashecho \$PWDecho $PWDtest

所以,我看不出使用cat命令的意义。

值得注意的是,这里的文档也可以在bash循环中工作。这个例子展示了如何获取表的列列表:

export postgres_db_name='my_db'export table_name='my_table_name'
# start copywhile 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_nameFROM information_schema.columnsWHERE 1=1AND 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_nameFROM information_schema.columnsWHERE 1=1AND 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

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

cat <<EOF >> /etc/fstabdata_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfsdata_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfsEOF

这扩展了您的fstab,而无需担心意外修改其任何内容。

长话短说,EOF标记(但也可以使用不同的文字)是一种heldoc格式,允许您以多行形式提供输入。很多困惑来自于cat实际上是如何工作的。您可以将cat>>>一起使用,如下所示:

$ cat >> temp.txtline 1line 2

虽然cat可以在手动写入控制台时以这种方式使用,但如果我想以更具声明性的方式提供输入,以便它可以被工具重用并保留缩进,空格等,那就不方便了。Heredoc允许定义您的整个输入,就好像您不使用stdin而是在单独的文本编辑器中输入一样。这就是维基百科文章的意思:

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

需要注意的是cat<<\EOT(见反斜杠)不会展开内部的任何变量,而cat<

例子:

FOO="bar"
cat << \EOT > foobar.txtecho "$FOO"EOT

将输出:$FOO

虽然:

FOO="bar"
cat << EOT > foobar.txtecho "$FOO"EOT

将输出:回显"bar"

<< EoF表示:

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

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

正如其他答案所解释的那样,多行输入称为这里文档

这里文档通常用于生成要传递给后续进程的输出。例如,cat << EoF可用于使用这里文档生成所需的输出。

以下是使用here Document动态创建文本文档的示例:

cat << EoF > ./my-document.txtHello worldHave a nice dayEoF

创建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:::*"]}]}