Shell Here Document详解:多行文本重定向与脚本实战

发布时间:2026/9/15 4:46:33

Shell Here Document详解:多行文本重定向与脚本实战 你肯定在别人的脚本里见过这种写法cat EOF 文件或者一行写得密密麻麻最后用两个大写的EOF收尾。我第一次看到时也愣了一下这是什么魔法后来搞明白它就是今天要聊的Shell的Here Document语法——一种把多行文本原样喂给命令的输入方式。很多Shell脚本入门的人会绕开它但实际写自动化脚本、生成配置文件、调远程机器时用好了它代码能干净一大截。这篇就专注讲透Here Document基本写法、定界符和变量展开的细节、真实场景里的用法以及我踩过的一些坑。无论你是刚学shell脚本还是已经写了一段时间脚本但没细抠过这里应该都能拿到点能立刻用的东西。1. Here Document是什么一个被低估的多行文本神器1.1 一次看懂基本写法Here Document的本质并不复杂它不是命令而是Shell提供的一种“重定向方式”。普通重定向是把文件内容喂给命令Here Document则是直接把写在脚本里的多行文本喂给命令。它的基本结构是命令 定界符 文本内容 定界符定界符随手就能取大家最常见的叫EOFEnd Of File的缩写。比如这条cat EOF Hello, world This is a test EOF执行效果等价于echo -e Hello, world\nThis is a test输出两行文本。你可能会问这有什么特别的关键点在于这个写法可以从一个命令开始中间夹任意多行内容不需要每行都加echo也不用费心处理转义。而且这个内容会作为标准输入传给左侧的命令所以不只是cat任何能接收标准输入的命令都可以这样喂数据。这里还有一个经常被误解的点和EOF之间可以有空格定界符本身也可以有引号后面细讲。先记住这句判断“这个命令做什么”的直觉分号右边是命令分号左边是它要读取的文本流。1.2 为什么它能替代一堆echo写配置文件或临时脚本时最容易碰到的旧写法就是连环echo。比如你要生成一个两行的文本文件echo first line /tmp/a.txt echo second line /tmp/a.txt echo third line /tmp/a.txt如果内容变多行数一长满屏都是echo和引号根本没法看。更麻烦的是如果内容里还包含双引号、反引号、$符号得逐层做转义一个不小心就会出错。如果用Here Document同样的需求变成cat /tmp/a.txt EOF first line second line third line EOF一眼扫过去文本还是文本结构清清楚楚。这里的cat 文件意思是把标准输入写入文件 EOF作为输入来源。写在中间的内容保持原始排版缩进、空格、空行都能保留。这是Here Document最核心的价值——它让“多行文本”在Shell脚本里变得像普通文本一样直观而不是被各种引号和echo硬凑出来。2. 语法细节拆解定界符、变量展开与控制符2.1 定界符怎么选说白了就是个“结束标记”定界符没有语法限制字母、数字、下划线都行甚至中文也行但我们一般用大写的描述性单词。EOF最常见也有人用END、EOC、EOI。它唯一的作用就是告诉Shell从这一行开始一直到“独立的定界符行”之间全部都是要喂给命令的文本。这里有个极其重要的细节定界符必须单独出现在一行的行首。在它前面不能有缩进空格除非后面讲到的-连字符场景且缩进用的是Tab后面也不能再跟其他内容。否则Shell就不认得这个结束标记会继续等待输入很多脚本卡住或者内容被截断就是这个问题。选定界符的时候还要注意别和内容里的关键词冲突。比如内容里恰好出现了一行EOF那么Shell会认为文本到那里就结束了后面的内容会变成新的命令或直接语法报错。遇到这种情况换一个更独特的定界符就行比如_EOF_、END_OF_FILE。用一句不严谨但好记的话来说定界符就是个围栏围栏内外得分明。2.2 展开还是不展开引号的三种姿势这是Here Document最容易“玄学”的地方。同样的内容定界符加不加引号结果完全不同。先看默认情况也就是定界符不加引号nameTom cat EOF Hello, $name Today is $(date %Y-%m-%d) EOF这会先让Shell对Here Document里的内容做参数展开、命令替换和算术展开。所以输出里$name会变成Tom$(date ...)也会去执行命令拿到日期。这在该场景下常常正是我们想要的动态生成内容。但反过来如果希望文本里保留原始的$符号不展开、不执行那就给定界符加上单引号cat EOF Hello, $name Today is $(date %Y-%m-%d) EOF这个写法的输出就是原封不动的两行$name和$(date ...)都保持原样。这在生成脚本、写模板、传代码片段时太有用了因为你往往希望变量等到目标环境再去展开而不是在本机就被“多管闲事”地替换掉。至于双引号EOF和反斜杠\EOF在禁止展开这件事上效果和单引号一样。常见的误解是双引号会展开其实不会只要定界符上有任何一层引号包裹Shell就不处理内部文本。经验之谈不确定的时候默认用单引号EOF至少不会让变量被意外展开。2.3-连字符只认Tab的制表符杀手在脚本的循环或函数定义里写Here Document会遇到一个很现实的问题为了代码可读性我们会把整体缩进几层但如果中间的文本也跟着缩进输出就会带上这些前导空格这往往不是我们想要的。Shell为此提供了-连字符写法它会自动把行首的Tab字符全部去掉。注意是Tab字符不是空格。很多人在Markdown编辑或网页里粘贴时容易被“看起来一样的缩进”骗了实际那些缩进是空格-对空格视而不见。这也是一个高频翻车点。比如if [ true ]; then cat - EOF line1 line2 EOF fi如果上面的缩进全是Tab输出就是干净的line1、line2不会带前导Tabs。但如果混入了空格空格会原样保留。检查方法很简单用cat -A查看文件行首到底是以^I开头的Tab还是直接的空格。这里也顺带引出一个小习惯我写脚本时宁可在脚本内部多套几层if、for也不愿意把循环体里的文本内容用-之外的方式硬写到行首因为那会让整体结构乱到没法维护。3. 实操场景与核心环节实现3.1 一条命令生成多行配置文件最经典的使用场景是生成配置文件。配置和代码不同往往成段出现注释、空行、键值对混在一起。用cat搭配Here Document写文件几乎成了运维脚本里的标准动作。cat /tmp/app.conf EOF # 服务端口 server.port8080 server.host0.0.0.0 # 日志级别 log.levelINFO log.path/var/log/app EOF这里的要点是如果配置内容里没有需要本机展开的变量那就用单引号EOF。我就是在这个场景踩过坑早期用无引号EOF写配置结果配置里有个${JAVA_HOME}占位符被本机当场展开成了别的路径配置到目标机器时直接失效。后来统一用EOF这个坑再没出现过。再看一个实际点儿的写法用条件判断决定是否写文件同时生成日志if [ ! -f /etc/myapp/settings.ini ]; then mkdir -p /etc/myapp cat /etc/myapp/settings.ini EOF [common] enable yes retry 3 EOF echo $(date) settings.ini created /var/log/myapp-init.log fi这样一次安装脚本下来文件生成得干净利索日志也有记录。下次跑脚本即使重复执行也不会覆盖掉已经存在的配置因为外层有-f判断兜底。3.2 通过ssh远程执行一块脚本我一直觉得Here Document在远程操作上的价值比写文件更大。如果你要在一台远程主机上执行一段比较长的命令序列最朴素的方法是把每条命令都写成ssh userhost cmd但命令一多引号嵌套就成了噩梦。更优雅的姿势是本地直接喂一个脚本块过去ssh userremote cat /tmp/deploy.sh bash /tmp/deploy.sh EOF #!/bin/bash set -e cd /opt/app git pull origin master chmod x run.sh ./run.sh restart EOF这段代码的核心逻辑是本地的Here Document内容会通过标准输入传给远程的cat先写到远程脚本文件再执行。注意定界符我用了单引号EOF。为什么因为如果不用引号本机会先把这块内容里的$、反引号等先展开一遍远程拿到的脚本就“变了味”。用了单引号内容原封不动送到远程所有变量展开都在远程Shell里做语义更符合直觉。还有个常见组合是直接在ssh上执行一个bash指令流ssh userremote bash EOF uptime free -h df -h | head -5 EOF这种“ssh进远程当面跑一段”的写法排查问题时很顺手。相当于把本地写好的多行命令一次性贴给远程的Shell而且不需要反复往返。3.3 把JSON、SQL、文本喂给API和数据库命令行的世界里很多程序都支持从标准输入读取数据。Here Document正好能把一串结构化内容直接喂进去。比如用curl发送JSON请求体用-d -表示从标准输入读取curl -s -X POST http://localhost:8080/api/create \ -H Content-Type: application/json \ -d - EOF { name: test-app, replicas: 3, tags: [backend, api] } EOF这里我需要稍微留心引号的问题JSON里本身有双引号如果再用双引号做整个命令的外壳转义会变得很痛苦。Here Document直接避开这个麻烦让JSON以原始姿态出现可读性好一个维度。注意如果想保留JSON里的$开头的字符串有些API模板会有${...}记得用单引号EOF。数据库操作也类似。像这样批量执行SQLmysql -u root -p数据库密码 库名 SQL SELECT id, name FROM users WHERE status 1; UPDATE users SET login_count login_count 1 WHERE id 1001; SQL这种方式特别适合在脚本里做数据库初始化、批量迁移等操作。一个小提醒不要在命令行明文写数据库密码如果非要自动交互可以用配置文件或环境变量这样至少不会直接出现在历史记录里。Here Document只是输入方式它不会帮你解决凭据安全问题凭据还是得藏好。3.4 在循环里动态生成N个文件用for循环配合Here Document可以批量生成一群内容相似但参数不同的文件。举个例子我要给三个服务分别生成各自的Nginx配置端口不同其他配置相同for port in 8081 8082 8083; do cat /etc/nginx/conf.d/service-$port.conf EOF server { listen $port; location / { proxy_pass http://127.0.0.1:$port; } } EOF done当定界符不加引号时循环变量$port会在拼接文件内容时被替换成当前值最终得到3个配置文件内容里的端口分别是8081、8082、8083。这比手敲3个文件或者通过sed事后替换省很多事。不过这里有个细节要注意循环体里的EOF所在行的缩进如果用了空格EOF就不被识别为结束标记如果用了Tab并且配合-则可以缩进。建议保持“顶格EOF”这个习惯配合外层循环缩进时把Here Document用- Tab来写会更整齐。很多编辑器会自动把Tab转成空格这也是新手一跑就报“这里没结束”的高发原因之一。4. 常见问题与排查技巧实录4.1 内容里的“EOF”被当成结尾输出被拦腰截断这是最常遇到的问题。内容里恰好有一行文本和定界符完全一样Shell不会管你的主观意愿它看到独立成行的EOF就直接判定结束。比如你想用脚本生成一个帮助文档文档里反复出现“EOF”这个字样cat EOF /tmp/help.txt 使用说明 在终端输入 EOF 表示输入结束 EOF EOF这里的第二行EOF会被当成定界符后面的内容全部作废甚至可能报语法错误。解决办法就是换一个和内容不冲突的定界符比如HELP_TEXT、_MY_END_。这是最简单可靠的方案不用对内容本身做转义。写长文本时我干脆从一开始就养成了“定界符越罕见越好”的习惯比如__DOC_END__这样基本不会翻车。4.2 变量不展开或者不该展开却展开了还是引号的问题。定界符加单引号所有变量都原样保留不加引号所有变量都展开。很多人一开始记不住就在两种模式里反复横跳。我的建议是列一个小速查逻辑先问自己“这段文本最终在哪一端的Shell里被解释”。如果希望在本机展开就不加引号如果希望原样传到远程或写进文件里就加单引号。一旦确认了方向就不会犹豫。另外内容里如果只是想保留某一个$其他都正常展开可以给那个$加反斜杠转义cat EOF 当前用户: $USER 显示字面量: \$USER EOF这样第一行会显示当前用户名第二行则原样输出$USER。转义符号在混用场景下挺好用但一行里混两三种语义时建议还是拆成两个Here Document或者考虑用模板后再替换。人一旦写复杂了自己也容易晕。4.3 Windows编辑器留下的换行符CRLF问题你在Windows上编辑好脚本传到Linux上一跑经常会遇到一种奇怪现象命令行提示符后面莫名其妙多了\r或者脚本里输出内容末尾出现^M。这是因为Windows的换行是\r\n而Linux的换行是\n。CRLF的\r在Shell里算作一个字符。这个坑在Here Document里尤其隐蔽因为你看到的是多行文本但每一行末尾都藏着\r。如果这个文本还要被当作脚本执行第一行的#!/bin/bash\r可能会让系统找不到解释器直接报错。排查方法很简单cat -A /tmp/script.sh如果每行结尾显示为^M$那就是CRLF没跑。修复方式用dos2unix /tmp/script.sh或在编辑器里统一改成LF换行。给同行排错时他看到这种问题我说别慌先用file命令看一眼文件类型如果输出里有“CRLF”字样直接换算结束。4.4 脚本权限和退出状态的小陷阱还有一个很容易被忽略的点Here Document本身不是命令它是一个输入重定向所以整条语句的退出状态取决于左边命令的退出状态。比如cat EOF /tmp/x就算成功返回0如果cat写文件失败比如目标目录没有权限返回非0。很多脚本在生成完文件后不检查结果导致后面用到文件时就崩。建议在生成文件后加一个简单判断cat /var/lib/app/data.json EOF {mode:init} EOF if [ $? -ne 0 ]; then echo write failed 2 exit 1 fi另外一个让人一头雾水的场景用sh script.sh和bash script.sh执行同一个含Here Document的脚本可能出现不一样的行为。因为sh在某些Unix系统上是POSIX Shell的别名和Bash比对、数组这类扩展的支持不同。所以如果你的脚本用了Here String把解释器固定为#!/usr/bin/env bash更稳妥。5. 进阶Here Document与数据处理的组合拳5.1 用heredoc驱动read逐行处理你可能会想Here Document只是喂数据给命令还能有什么进阶玩法其实它还可以反过来当数据源配合read循环做逐行处理。比如脚本里需要“模拟一个文件内容”而不想真的在磁盘上建临时文件while IFS read -r line; do echo 处理行: $line done EOF apple banana cherry EOF这个写法的妙处在于省去了临时文件这一步数据直接作为输入流进入循环。在这个例子里IFS是为了防止行首行尾空格被修剪-r防止反斜杠被转义两个都是处理文本时的标配。如果你有一段小数据要反复测试逻辑完全可以在脚本里用Here Document模拟标准输入测完再换成真实文件。我用这个技巧写过不少用于演示的小工具逻辑一气呵成。5.2 Here String和Here Document怎么选提到Here Document就绕不开它的“小弟弟”Here String写法是把一个带引号的字符串或变量作为标准输入grep abc one abc two这个语法主要用来给命令喂一个短字符串。和Here Document的最大差异就是“体量”不想为一个单词或一行字符串开一个完整的Here Document块时更紧凑。比如测试某个命令是否读标准输入或者快速给md5sum喂数据md5sum hello printf hello | md5sum这两种结果并不同会在字符串后面自动补一个换行符而printf不加这又是另一个抠细节的点。总之短输入选Here String长文本、多行、带结构的内容选Here Document。5.3 我长期使用的几条心得文章的最后分享几个我在真实项目里沉淀出的习惯算不上什么高深理论但确实帮我少踩了很多坑。第一条心得定界符习惯性带引号写EOF。默认按不展开处理只有确认需要展开的地方再改成无引号。安全边界比便利性更重要因为脚本里一旦出现意外展开的变量排查成本远高于多敲两个引号。第二条心得生成稍微复杂的文件时先输出到临时文件再mv。比如先cat /tmp/app.conf.tmp EOF确认内容无误后再mv /tmp/app.conf.tmp /etc/app/app.conf。这样即使脚本中途因为权限或路径问题失败也不会把半截配置留在生产目录里。反复改配置文件的时候这招尤其安心。第三条心得写长文本内容时尽量在脚本里保持顶格的状态。粘贴代码进编辑器时我很注意编辑器会不会自动把Tab转成空格。一旦这块内容要被-处理混入空格就前功尽弃。帮别人排查时我经常一看代码缩进就知道问题出在哪该用Tab的地方用了空格定界符被当成普通内容文本直接断掉。Here Document本身并不难难的是各种细节和习惯。把这些细节理顺后你会发现Shell脚本里那些“魔法般”的多行生成、远程执行、配置写入原理其实都很直白。以后在别人的脚本里再看到cat EOF你能一眼看穿它在做什么也能自信地根据自己的需求调整定界符的引号、变量展开行为和缩进处理。这就是这篇长文想帮你打通的一小段任督二脉。
延伸阅读

更多相关文章

2026/9/15 4:46:33

MQTT Broker国产替代选型与开源许可证合规实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/15 4:46:33

19类家具全景分割数据集:从标注到训练与评估

简介:面向家庭场景家具的全景分割图像数据集,共标注十九个类别,涵盖床、椅子、橱柜、门、灯、地毯、桌子、窗户等常见物体,图像分辨率为640640,适合细粒度分割及目标检测、实例分割等深度学习任务,可供研究…

2026/9/15 4:41:32

新手如何选云服务器?从需求分析到服务商避坑全指南

刚接触云服务器的时候,十个人里有八个人会跑来问我同一个问题:到底哪个服务商靠谱?我特别理解这种迷茫——打开阿里云、腾讯云、华为云的官网,满屏都是“新用户99元一年”“2核4G限时秒杀”,还没看懂配置参数&#xff…

2026/9/15 4:56:33

WorkBuddy:面向企业协作确定性的工程化设计实践

1. 这不是又一个“技术炫技”项目,而是一次对产品本质的重新校准WorkBuddy这个词最近在开发者社区和产品团队内部高频出现,但很多人点开资料后反而更困惑——它既没有发布惊艳的算法白皮书,也没堆砌一堆“全球首创”的技术名词。我去年底开始…

2026/9/15 4:56:33

TikTok Shop抢单系统:uniapp+PHP前后端协同架构实战

简介:这是一套面向TikTok海外运营场景的抢单系统源码,专为有PHP与uniapp开发经验的中高级开发者设计,解决跨境电商业务中订单自动分配、指定抢单与充值客服对接等核心需求。资源采用前后端分离架构,前端基于uniapp(兼容…

2026/9/15 4:56:33

Cadence Cerebrus:EDA工具链中的AI决策神经系统

1. 项目概述:这不是又一个“AI”营销话术,而是EDA工具链底层逻辑的实质性重构最近刷到“Cadence推出AI超级智能体,芯片设计效率狂提10倍,英伟达高通已上车”这条消息,不少同行第一反应是皱眉——EDA行业太熟悉这种表述…

2026/9/15 4:56:33

内容规划的核心:从场景匹配到内容体系搭建的实操指南

很多人以为做内容规划就是拿张表格把未来一个月的推送排满,我曾经也这么干过,结果看着日历上密密麻麻的选题,后台数据却一片惨淡。后来我才慢慢意识到,内容规划的本质不是"这个月发什么",而是"用户在什…

2026/9/15 4:56:33

工作流重构三张纸法则:从崩坏到可运行的实战指南

1. “Vibe时代”不是玄学,而是工作流失效的集体应激反应你有没有过这种体验:早上打开电脑,邮箱里躺着17封未读,钉钉消息99,飞书文档里3个协作任务卡在“待确认”,而你刚在Notion里建好的周计划,…

2026/9/15 4:51:33

多轮对话系统历史管理架构与优化实践

1. 多轮对话系统的核心挑战在智能交互领域,多轮对话历史管理就像一位经验丰富的谈判专家需要记住整个沟通过程中的每个细节。我经历过多个对话系统项目,最深刻的教训就是:历史管理没做好,再强大的NLU模型都会变成"金鱼记忆&q…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码