一、为什么自动化脚本离不开 Heredoc?
在编写 Shell 自动化安装、Docker 容器编排或 CI/CD 流水线脚本时,我们经常需要一次性动态生成几十行甚至数百行的配置文件(如 docker-compose.yml、nginx.conf、supervisord.conf 或 SQL 初始化脚本)。
如果采用传统的 echo 命令逐行拼接:
# 🔴 极其冗长繁琐、不易阅读的写法:
echo "version: '3.8'" > docker-compose.yml
echo "services:" >> docker-compose.yml
echo " web:" >> docker-compose.yml
echo " image: nginx:latest" >> docker-compose.yml不仅排版丑陋,而且如果文本中包含大量引号、反斜杠或换行符,需要写数不清的转义符号,极易出错。
Heredoc(文档嵌入语法,常表现为 cat <<EOF) 允许我们在 Shell 脚本中以最直观的原生格式定义大段多行文本,并作为标准输入流(stdin)直接喂给下游命令。
二、基础语法:覆盖写入 vs 追加写入
Shell 遇到 << 时,会将紧随其后的字符串作为结束分界标识符(业界习惯使用 EOF,即 End Of File,亦可自定义为 END 或任意字符串):
1. 覆盖写入模式(cat > file <<EOF)
cat > /etc/resolv.conf <<EOF
nameserver 223.5.5.5
nameserver 119.29.29.29
EOF每次执行该命令都会清空原目标文件,重新写入两行 DNS 解析配置。
2. 追加写入模式(cat >> file <<EOF)
cat >> /etc/hosts <<EOF
10.0.1.100 k8s-master
10.0.1.101 k8s-node1
EOF保留原文件内容,在末尾紧接着追加两行内容。
三、生产最核心陷阱:<<EOF vs <<'EOF' 变量解释区别
很多工程师在生成自动化配置时,常遭遇“变量莫名其妙变为空值”或“特殊字符直接报错”的灾难。这是因为没有选对 EOF 的引用模式:
1. 不带引号的 <<EOF(开启变量插值计算)
内部如果存在 $VAR、$(command) 或反引号,Shell 在写入前会强行将它们计算并替换为当前环境变量的值:
current_user="admin"
cat > user_info.txt <<EOF
Hello, your account is: $current_user
Current date: $(date +%Y-%m-%d)
EOF
# 最终文件内容:
# Hello, your account is: admin
# Current date: 2026-09-042. 带单引号或双引号的 <<'EOF'(纯字面量原样输出,强力推荐)
如果用单引号将结束符包起来:<<'EOF' 或 <<"EOF",Shell 会完全关闭变量替换与命令替换,内部的所有字符百分之百原样写入:
# 生成包含 Bash 原生语法的脚本(不希望 $1, $? 被父级 Shell 提前解析):
cat > /opt/script/clean.sh <<'EOF'
#!/bin/bash
target_dir=$1
if [ -z "$target_dir" ]; then
echo "Error: param missing!"
exit 1
fi
rm -rf "$target_dir"/*
echo "Done with exit code: $?"
EOF加上引号后,$1、$target_dir、$? 不会被当前执行环境偷吃掉,完完整整地保留在生成的目标脚本中。
四、代码美化必备技巧:<<-EOF 去除前导制表符
在函数或 if 分支内嵌套书写 Heredoc 时,如果不做处理,直接缩进会导致生成的文件开头多出一堆无用的空格或制表符。
使用带有减号的 <<-EOF,Shell 会在输出时自动剥离每行开头的物理制表符(Tab 键,不包含空格),同时允许结束标志符 EOF 也可以保持缩进:
generate_nginx_conf() {
# 注意每行开头使用 Tab 缩进
cat <<-EOF > /etc/nginx/conf.d/portal.conf
server {
listen 80;
server_name portal.internal;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
EOF
}这使得大型 Shell 自动化运维脚本在保持严格代码格式缩进美感的同时,生成的配置文件完全顶格纯净。