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

Posted

技术标签:

【中文标题】“cat << EOF”如何在 bash 中工作?【英文标题】:How does "cat << EOF" work in bash? 【发布时间】:2011-01-30 19:43:51 【问题描述】:

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

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

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

这会正确构造多行字符串(从BEGIN;END;,包括在内)并将其作为输入传递给psql

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

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

&lt;&lt; 到底是做什么的?

还有手册页吗?

【问题讨论】:

这可能是cat 的无用用法。试试psql ... &lt;&lt; EOF ... 另见“这里的字符串”。 mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings 我很惊讶它适用于 cat 但不适用于 echo。 cat 应该期望文件名作为标准输入,而不是 char 字符串。 psql 回答我自己:不带参数的 cat 执行并复制到输出,无论通过输入(stdin)发送什么,因此使用它的输出通过 > 填充文件。事实上,作为参数读取的文件名不是标准输入流。 @Alex echo 只打印它的命令行参数,而cat 读取标准输入(当管道传输到它时)或读取与其命令行参数对应的文件 【参考方案1】:

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

cat &lt;&lt;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

【讨论】:

1. 1和3不用cat也可以; 2.示例1可以用一个简单的多行字符串来完成 与其用 'cat' 创建另一个进程,不如使用 IFS='' read -r -d 来代替? 值得注意的是,当使用tee 代替cat 时,sudo 可用于在受限位置写入文件。就像例如sudo tee /etc/somepath/file &gt; /dev/null &lt;&lt;EOF ... 我认为cat仅适用于带有here-document的完整bash命令,如果没有cat或其他命令,则以符号&lt;&lt;开头的here-document无法回显到@987654341 @,而第一行“grep 'b' | tee b.txt”的其余部分无法获得输入。【参考方案2】:

这被称为 heredoc 格式来提供一个字符串到标准输入。详情请见https://en.wikipedia.org/wiki/Here_document#Unix_shells。


来自man bash

这里的文档

这种类型的重定向指示 shell 从 当前源直到一行 仅包含单词(没有尾随 空白)可见。

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

here-documents的格式为:

          <<[-]word
                  here-document
          delimiter

没有参数扩展、命令替换、算术扩展,或 路径名扩展在 单词。如果 word 中的任何字符是 引述 delimiterword 上的引号删除的结果,并且行 here-document 中的内容未展开。 如果 word 没有被引用,则 here-document 进行参数扩展,命令 代换和算术 扩张。在后一种情况下, 字符序列\&lt;newline&gt; 是 忽略,\ 必须用于引用字符 \$`

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

【讨论】:

我在禁用变量/参数扩展时遇到了最困难的情况。我需要做的就是使用“双引号”并修复它!感谢您的信息! 关于&lt;&lt;-,请注意只有前导制表符字符会被去除——而不是软制表符。这是您真正需要制表符的罕见情况之一。如果文档的其余部分使用软制表符,请确保显示不可见字符并(例如)复制和粘贴制表符字符。如果你做得对,你的语法高亮应该正确地捕捉到结束分隔符。 我看不出这个答案比下面的答案更有帮助。它只是反刍可以在其他地方找到的信息(可能已经检查过) @BrDaHa,也许不是。为什么这样问?因为点赞?它几年来唯一的一个。通过比较日期可以看出。 这个手册页的摘录教会了我如何编写漂亮而易于理解的文档。太棒了!【参考方案3】:

在您的情况下,“EOF”被称为“Here Tag”。基本上&lt;&lt;Here 告诉shell 你要输入一个多行字符串,直到“标签”Here。你可以随意命名这个标签,通常是EOFSTOP

关于Here标签的一些规则:

    标签可以是任何字符串,大写或小写,尽管大多数人习惯使用大写。 如果该行中有其他单词,则该标签不会被视为Here标签。在这种情况下,它只会被视为字符串的一部分。标记应单独位于单独的行上,才能被视为标记。 标签在该行中不应有前导或尾随空格,才能被视为标签。否则,它将被视为字符串的一部分。

示例:

$ 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

【讨论】:

这是最好的实际答案......您定义了两者并清楚地说明了使用的主要目的而不是相关理论......这很重要但没有必要......谢谢 - 非常有帮助 @edelans 你必须补充一点,当使用&lt;&lt;- 时,前导标签不会阻止标签被识别 你的回答让我点击了“你要输入一个多行字符串”【参考方案4】:

POSIX 7

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

重定向运算符“

here-document 应被视为在下一个 &lt;newline&gt; 之后开始的单个单词,并一直持续到有一行仅包含分隔符和 &lt;newline&gt;,中间没有 &lt;blank&gt; 字符。然后下一个 here-document 开始,如果有的话。格式如下:

[n]<<word
    here-document
delimiter

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

如果word中的任何字符被引用,则应通过对word进行引号去除形成分隔符,并且不应扩展here-document行。否则,分隔符应为单词本身。

如果word中没有字符被引用,则here-document的所有行都将被扩展以进行参数扩展、命令替换和算术扩展。在这种情况下,输入中的&lt;backslash&gt; 表现为双引号内的&lt;backslash&gt;(请参阅双引号)。但是,双引号字符 ('"' ) 不应在 here-document 中特殊处理,除非双引号出现在 "$()"、"``" 或 "$" 中。

如果重定向符号为“<tab> 字符应从输入行和包含尾随分隔符的行中去除。如果在一行中指定了多个“

当从终端设备读取 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

其中&lt;tab&gt; 是文字制表符,可以使用Ctrl + V &lt;tab&gt; 插入

输出:

<tab>a

带连字符:

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

输出:

a

这当然存在,因此您可以像周围的代码一样缩进您的cat,这样更易​​于阅读和维护。例如:

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

不幸的是,这不适用于空格字符:POSIX 支持tab 缩进。哎呀。

【讨论】:

在您讨论&lt;&lt;-&lt;tab&gt;a 的最后一个示例中,应该注意的是,其目的是允许脚本中的代码正常缩进,同时允许呈现给接收进程的heredoc 文本开始于第 0 列。这是一个不太常见的功能,更多的上下文可能会阻止很多令人头疼的问题...... 如果我的 EOF 标签之间的某些内容需要扩展而有些不需要,我应该如何避免扩展? ...只需在$前面使用反斜杠 @JeanmichelCote 我没有看到更好的选择 :-) 对于常规字符串,您还可以考虑将 "$a"'$b'"$c" 之类的引号混合在一起,但 AFAIK 没有类似的。【参考方案5】:

使用 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

【讨论】:

在你的情况下使用 sudo bash -c 'cat /etc/somedir/foo.conf #我的配置文件 foo=bar EOF'【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

$ cat >> temp.txt
line 1
line 2

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

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

【讨论】:

【参考方案8】:

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

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

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

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

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

【讨论】:

哪个外壳?我在 Ubuntu 18.04 上使用 bash 4.4 以及在 OSX 上使用 bash 3.2 进行了测试。两者都在仅使用 &lt;&lt;test 而没有 cat &lt;&lt;test 时创建了一个空文件。【参考方案9】:

请注意,cat

例子:

FOO="bar"

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

将输出: 回声 $FOO

同时:

FOO="bar"

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

将输出: 回声“酒吧”

【讨论】:

【参考方案10】:

值得注意的是,这里的文档也可以在 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

【讨论】:

以上是关于“cat << EOF”如何在 bash 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

“cat > filename << EOF”和“cat << EOF > filename”是不是等效? [复制]

cat <<EOF用法

cat <<EOF

shell用法 (cat << EOF)

linux shell “cat <<EOF“的作用,用于连续输出多行文本(连续打印)(文本块)

linux shell “cat <<EOF“的作用,用于连续输出多行文本(连续打印)(文本块)