“cat<< EOF”如何在bash中运行?

时间:2010-03-23 13:57:36

标签: linux bash scripting heredoc

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

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

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

这正确地构造了多行字符串(从BEGIN;END;,包括在内)并将其作为psql的输入进行管道。

但我不知道它是如何工作的,为什么它可以解释?

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

<<到底做了什么?

是否有针对它的手册页?

9 个答案:

答案 0 :(得分:437)

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


来自man bash

  

此处文件

     

这种重定向指示shell从中读取输入   直到一条线的电流源   只包含单词(没有尾随   看到空白。

     

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

     

here-documents的格式为:

          <<[-]word
                  here-document
          delimiter
     

无参数扩展,命令替换,算术扩展或   执行路径名扩展   的即可。如果单词中有任何字符   引用,   分隔符上的引号删除和行的结果   在 here-document 中未展开。   如果 word 不加引号,则所有行    here-document 受制于参数扩展命令   替换和算术   扩张。在后一种情况下,   字符序列\<newline>是   已忽略,\必须用于引用字符\$`

     

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

答案 1 :(得分:399)

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

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

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

答案 2 :(得分:197)

在您的情况下,“EOF”被称为“Here Tag”。基本上<<Here告诉shell你要输入一个多行字符串,直到“tag”Here。您可以根据需要为此标记命名,通常是EOFSTOP

有关Here标签的一些规则:

  1. 标签可以是任何字符串,大写或小写,但大多数人按惯例使用大写字母。
  2. 如果该行中还有其他字词,则不会将该标记视为Here标记。在这种情况下,它将仅被视为字符串的一部分。标签应该单独在一条线上,作为标签。
  3. 标记应该没有该行中的前导或尾随空格才能被视为标记。否则它将被视为字符串的一部分。
  4. 示例:

    $ 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
    

答案 3 :(得分:59)

POSIX 7

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

  

重定向运算符“&lt;&lt;”并且“&lt;&lt; - ”都允许将shell输入文件中包含的行(称为“here-document”)重定向到命令的输入。

     

here-document应被视为一个单词,从下一个开始,并继续直到有一行只包含分隔符和a,其间没有字符。然后下一个here-document开始,如果有的话。格式如下:

[n]<<word
    here-document
delimiter
     

其中可选n表示文件描述符编号。如果省略该数字,则here-document引用标准输入(文件描述符0)。

     

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

     

如果引用单词中没有字符,则应扩展here-document的所有行以进行参数扩展,命令替换和算术扩展。在这种情况下,输入中的行为与内部双引号相同(请参阅双引号)。但是,双引号字符('“')不应在here-document中特别处理,除非双引号出现在”$()“,”``“或”$ {}“中。 / p>      

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

     

当从终端设备读取here-document并且shell是交互式的时,它应该在读取每行输入之前将变量PS2的内容(如Shell变量中所述进行处理)写入标准错误,直到分隔符具有得到认可。

实施例

尚未给出的一些例子。

引号阻止参数扩展

没有引号:

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缩进。让人惊讶。

答案 4 :(得分:19)

使用tee代替猫

不完全是对原始问题的回答,但我想分享这个:我需要在需要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

答案 5 :(得分:8)

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

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

答案 6 :(得分:1)

长话短说,EOF标记(但也可以使用其他文字)是Heredoc格式,可让您以多行形式提供输入。 看来cat的实际运作方式引起了很多困惑。 您可以将cat>>>结合使用,如下所示:

$ cat >> temp.txt
line 1
line 2

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

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

答案 7 :(得分:0)

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

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

将生成与:

相同的文件
cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

所以,我没有看到使用cat命令的重点。

答案 8 :(得分: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