Linux系统输入重定向:Bash Here-Document 详解:`<<TAG` 到底是什么
2026/7/4 8:48:04 网站建设 项目流程

摘要:Here-Document 是 Shell 脚本中非常常用的输入重定向方式。它可以把脚本中的多行内联文本直接作为标准输入传递给命令,避免额外创建临时文件。本文围绕command < filecommand <<TAGcommand < <(command)的区别展开,系统说明 Here-Document 的基本语法、结束标识符、变量展开规则、缩进写法以及实际使用场景。

一、概述:为什么需要 Here-Document?

Here-Document,是指在 Shell 脚本中把一段内联多行文本作为标准输入(Standard Input, stdin)传递给命令的语法。简单来说,它解决的是“我不想单独准备一个文件,但又想给命令传入多行内容”的问题。

在 Linux 或 Unix Shell 中,很多命令都可以从 stdin 读取内容,例如catgrepwcmysqlsshftp等。传统做法通常是先准备一个文件,再使用输入重定向:

command<input.txt

但如果输入内容很短,或者这段内容只在当前脚本中使用一次,单独创建文件就显得不够简洁。此时 Here-Document 就很适合:

command<<TAG 多行文本内容 TAG

Here-Document 的核心价值,是把“外部文件输入”变成“脚本内联输入”。它让脚本更集中、更易读,也更适合自动化场景。

二、先理解三种常见输入方式

在正式理解<<TAG之前,需要先区分三种容易混淆的写法:< file<<TAG< <(...)

写法名称输入来源典型用途
command < file普通输入重定向外部文件把文件内容作为 stdin
command <<TAGHere-Document脚本内联文本把多行文本直接写在脚本中
command < <(other-command)进程替换其他命令的输出把命令输出伪装成文件输入

2.1< file:从文件读取标准输入

< file是最基础的输入重定向方式,它会把指定文件内容交给命令作为 stdin

例如:

cat</etc/passwd

这条命令的含义是:让cat/etc/passwd文件读取输入,而不是从键盘读取。

它适合输入内容已经存在于磁盘文件中的场景,例如读取配置文件、日志文件、数据文件等。

2.2<<TAG:从脚本内联文本读取标准输入

<<TAG不依赖外部文件,而是把脚本中两行 TAG 之间的内容作为 stdin

例如:

cat<<TAG Hello, world! This is a here-document example. TAG

这段脚本会把中间两行文本传给cat,最终输出:

Hello, world! This is a here-document example.

这里的TAG只是一个结束标识符,可以换成EOFENDDATA等任意不容易和正文冲突的字符串。

2.3< <(...):把命令输出当成输入

< <(...)是进程替换(Process Substitution),它会把另一个命令的输出转换成当前命令可读取的输入流

例如:

wc-l<<(printf"a\nb\nc\n")

这条命令的含义是:先执行printf "a\nb\nc\n",再把它的输出作为wc -l的 stdin。

它和 Here-Document 的区别在于:Here-Document 的输入内容写在脚本里,而进程替换的输入内容来自另一个命令的运行结果。

三、Here-Document 的基本语法

Here-Document 的基本格式如下:

command<<TAG 多行文本内容 TAG

其中:

组成部分含义
command接收 stdin 的命令,例如catgrepwc
<<启动 Here-Document
TAG结束标识符,由用户自定义
中间文本实际传给命令的标准输入内容

结束标识符不是命令,也不是关键字,它只是告诉 Shell:多行输入到这里结束

例如:

cat<<EOF 第一行内容 第二行内容 EOF

Shell 会把EOF之前的两行文本作为 stdin 传给cat,而EOF本身不会出现在输出中。

四、结束标识符 TAG 的定义规则

Here-Document 看起来简单,但很多错误都出在结束标识符上。尤其是空格、缩进和大小写问题,容易导致脚本无法正常结束输入。

4.1 TAG 可以是任意字符串

TAG 本身没有特殊语义,只要起始标识符和结束标识符完全一致即可

常见写法包括:

cat<<EOF 内容 EOF
cat<<END 内容 END
cat<<DATA 内容 DATA

实际开发中,建议使用不容易和正文冲突的标识符。例如,正文中可能出现EOF,就可以改用MY_CONFIG_END

4.2 结束标识符必须独占一行

结束标识符所在行只能写标识符本身,前后不能有空格或其他字符

正确写法:

cat<<TAG 内容 TAG

错误写法:

cat<<TAG 内容 TAG

上面错误的原因是:结束行前面多了一个空格,Shell 不会把它识别为真正的结束标识符。

同样,下面这种也不正确:

cat<<TAG 内容 TAG

因为TAG后面多了一个空格,也会导致匹配失败。

4.3 TAG 大小写敏感

Here-Document 的结束标识符是大小写敏感的,TAGtag不是同一个标识符

错误示例:

cat<<TAG 内容 tag

这段脚本不会在tag处结束,因为起始标识符是TAG,结束标识符也必须是TAG

4.4<<-TAG支持 TAB 缩进

普通<<TAG要求结束标识符顶格书写,但脚本中有时为了排版,会希望内容跟随代码块缩进。此时可以使用<<-TAG

<<-TAG会忽略行首的 TAB 字符,但不会忽略普通空格

例如:

cat<<-TAG 这里前面是 TAB 缩进 TAG

需要注意的是,<<-TAG只能处理 TAB 缩进,不能处理空格缩进。如果编辑器把 TAB 自动转换成空格,就可能达不到预期效果。

五、变量展开与原样输出

Here-Document 最重要的细节之一,是是否进行变量展开和命令替换。

5.1 默认写法会展开变量和命令

默认的<<TAG会在命令执行前进行变量展开、命令替换和部分转义处理

例如:

NAME="孙总"cat<<TAG Hello,$NAME! 当前时间:$(date+%H:%M)TAG

这段脚本中:

内容执行结果
$NAME会被替换成变量值
$(date +%H:%M)会执行命令并替换成命令输出

因此,实际输出可能是:

Hello, 孙总! 当前时间:21:30

默认展开适合生成动态文本,例如配置文件、SQL 语句、提示信息和自动化命令输入

5.2 单引号写法禁止展开

如果希望 Here-Document 中的内容完全原样输出,可以给 TAG 加单引号。

NAME="孙总"cat<<'TAG' Hello, $NAME! 当前时间:$(date +%H:%M) TAG

输出结果是:

Hello, $NAME! 当前时间:$(date +%H:%M)

这里$NAME不会被替换,$(date +%H:%M)也不会被执行。

<<'TAG'适合输出模板、脚本片段、配置样例、Markdown 文本等不希望被 Shell 提前解释的内容

5.3 双引号写法通常不改变展开行为

有时也会看到这种写法:

cat<<"TAG" Hello, $USER! TAG

在常见 Shell 中,只要结束标识符被引号包裹,Here-Document 正文通常会按照“引用标识符”的规则处理,即不执行变量展开。为了避免误解,实际写脚本时更推荐明确使用:

cat<<'TAG' 原样文本 TAG

想要原样输出,就优先使用<<'TAG';想要变量替换,就使用<<TAG。这样最清晰,也最不容易出错。

六、Here-Document 与重定向的关系

6.1 本质上都是 stdin 重定向

command < filecommand <<TAG的最终目标一样,都是给 command 提供 stdin

区别只在于输入来源不同:

写法输入来源是否需要外部文件
command < file文件内容需要
command <<TAG脚本内联文本不需要

例如:

grepfoo<<END foo bar foobar END

它等价于把下面三行内容交给grep foo

foo bar foobar

因此输出结果是:

foo foobar

6.2 Here-Document 会先准备输入,再执行命令

Shell 执行 Here-Document 时,大体可以理解为以下过程:

未引用

被引用

Shell 读取脚本

发现 Here-Document 语法

读取起始 TAG 与结束 TAG 之间的文本

TAG 是否被引用

执行变量展开和命令替换

保留原始文本

构造临时输入流

把输入流作为 stdin 传给 command

执行 command

这个流程说明了一个关键点:Here-Document 不是在命令运行过程中一行一行临时输入,而是 Shell 先构造好输入流,再交给命令执行

6.3 stdin 与 stdout 可以同时重定向

Here-Document 负责 stdin,普通输出重定向负责标准输出(Standard Output, stdout)。二者互不冲突。

例如:

cat<<TAG>out.txtToday is$(date). TAG

这条命令的执行逻辑是:

步骤动作
第一步Shell 处理 Here-Document,生成 stdin
第二步cat读取 stdin
第三步cat的 stdout 被写入out.txt

如果还需要处理标准错误(Standard Error, stderr),也可以继续追加错误重定向:

some_command<<TAG>out.txt2>error.loginput text TAG

其中> out.txt处理 stdout,2> error.log处理 stderr,<<TAG处理 stdin。

七、典型使用示例

7.1 输出多行文本

最简单的使用场景,是直接输出多行文本

cat<<TAG Hello, world! This is a here-document example. TAG

输出结果:

Hello, world! This is a here-document example.

这个例子中,cat接收了两行 stdin,并把它们原样输出到终端。

7.2 给 grep 提供临时文本

Here-Document 很适合给命令临时提供一小段测试数据

grepfoo<<ENDMARK foo bar foobar ENDMARK

输出结果:

foo foobar

这里grep foo会从 Here-Document 提供的三行文本中筛选包含foo的行。

7.3 禁止变量展开

当文本中包含$$(...)、反引号等 Shell 特殊内容时,应该使用<<'TAG'防止误展开

NAME="孙总"cat<<'TAG' Hello, $NAME! TAG

输出结果:

Hello, $NAME!

这里的$NAME被当作普通字符串,而不是变量。

7.4 生成配置文件

Here-Document 常用于自动生成配置文件,尤其适合部署脚本和初始化脚本

APP_NAME="demo-service"APP_PORT="8080"cat<<EOF>app.confname=$APP_NAMEport=$APP_PORTmode=production EOF

生成的app.conf内容是:

name=demo-service port=8080 mode=production

这个场景中,默认展开是有用的,因为配置文件需要写入变量的实际值。

7.5 原样生成模板文件

如果要生成的是模板文件,而不是最终配置文件,就应该禁止展开。

cat<<'EOF'>template.confname=$APP_NAME port=$APP_PORT mode=production EOF

生成的template.conf内容是:

name=$APP_NAME port=$APP_PORT mode=production

生成模板时使用<<'EOF',生成最终文件时使用<<EOF,这是非常实用的判断规则

八、常见误区与排查方法

8.1 误区一:结束标识符前后有空格

很多 Here-Document 报错都不是语法本身复杂,而是结束标识符没有精确匹配。

错误示例:

cat<<EOF hello EOF

这里EOF前面有一个空格,Shell 不会认为它是结束标识符。

正确写法:

cat<<EOF hello EOF

8.2 误区二:把空格缩进当成 TAB 缩进

<<-EOF只会去掉 TAB,不会去掉普通空格。

cat<<-EOF 这是 TAB 缩进 EOF

如果脚本中看起来缩进了,但实际是空格,<<-EOF不会按预期工作。遇到此类问题,应检查编辑器是否开启了“将 TAB 转为空格”的设置。

8.3 误区三:忘记变量会被提前展开

下面这段脚本看似是在写模板,实际会提前展开变量:

cat<<EOF 用户变量:$USEREOF

如果希望输出$USER字符串本身,应该写成:

cat<<'EOF' 用户变量:$USER EOF

判断是否加引号的关键,不是 TAG 本身,而是正文内容是否希望被 Shell 解释

8.4 误区四:把 Here-Document 当成注释块

有人会用这种方式“注释”多行内容:

:<<'COMMENT' 这里是一段不会执行的文本 COMMENT

这种写法利用了:命令什么也不做的特性,看起来像多行注释。但它并不是真正的 Shell 注释,而是把多行文本作为 stdin 传给空命令。

这种写法可以用,但不建议滥用。真正重要的说明仍然应该使用#注释,以免降低脚本可读性。

九、实践建议:什么时候该用 Here-Document?

Here-Document 适合“短文本、临时文本、模板文本、自动化输入”这几类场景

场景是否适合 Here-Document原因
临时给命令传几行测试数据适合不必创建临时文件
自动生成配置文件适合脚本和配置模板集中管理
写 SQL 初始化语句适合多行 SQL 可读性更强
写很长的大型文件不太适合会让脚本过长
需要频繁复用的内容不太适合独立文件更易维护
内容中包含大量复杂转义视情况而定推荐使用<<'EOF'原样输出

在实际脚本中,可以按下面规则选择:

需求推荐写法
从已有文件读取输入command < file
在脚本中写死多行输入command <<EOF
需要保留$VAR等原样内容command <<'EOF'
输入来自另一个命令输出command < <(other-command)
需要美观缩进且使用 TABcommand <<-EOF

十、总结

Here-Document 的本质,是一种把脚本内联多行文本重定向为 stdin 的机制。它和< file一样属于输入重定向,只是输入来源从外部文件变成了脚本中的文本块。

理解 Here-Document,需要抓住四个核心点:

第一,<<TAG表示开始读取内联文本,直到遇到独占一行的 TAG 为止

第二,TAG 是用户自定义结束标识符,没有特殊语义,但起始和结束必须完全一致

第三,默认写法<<TAG会展开变量和命令替换,而<<'TAG'会原样保留文本

第四,<<-TAG只会忽略 TAB 缩进,不会忽略普通空格

在自动化脚本、配置生成、命令批量输入和临时测试数据构造中,Here-Document 都非常实用。掌握它之后,脚本不需要依赖大量临时文件,也能清晰表达复杂的多行输入逻辑。写 Shell 脚本时,能够正确区分< file<<EOF< <(...),基本就掌握了输入重定向中最容易混淆也最实用的一组能力

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询