在 Linux/Unix 系统管理与脚本开发中,Bash 的 here document(heredoc) 是一种极为常用的输入重定向方式,它允许用户在脚本内部直接嵌入多行文本块,如同“文档”般传递给命令或写入文件。然而,许多开发者遇到的一个实际痛点却是:如何将这里文档的输出内容直接捕获并存储到 Bash 变量中? 本文将以资深编辑视角,系统梳理几种可行方案,助你精准驾驭 heredoc 变量存储。
一、问题场景:为何需要将 Heredoc 存入变量?
假设你在编写一个自动化部署脚本,需要动态生成一段配置文件或指令文本,并后续进行变量替换、逻辑判断或参数传递。例如,你希望通过 heredoc 构建一段复杂的 JSON 字符串,再将其赋给一个变量,供后续的 API 调用使用。如果直接使用 cat << EOF > file,输出只能写入文件;而若想直接赋值给变量,传统写法往往报错或达不到预期。
二、核心方法:三种主流实现路径
1. 命令替换 + cat(最直观)
将 heredoc 作为 cat 命令的输入,再利用 $() 命令替换捕获输出:
json=$(cat << EOF
{
"name": "Linux中国",
"url": "https://linux.cn"
}
EOF
)
关键点:cat 不加任何选项,只负责从 stdin 读取 heredoc 内容,然后标准输出被 $() 捕获。注意 EOF 定界符可以换成任意字符串(如 'DELIMITER'),加上单引号可防止变量展开。
2. 直接 read 与 here-string(无需 cat)
利用 Bash 的 read 命令与 here-string(<<<)结合:实际上更常见的做法是用 read -r 配合 << 重定向,但 read 默认只读取一行。若要读取多行,需要借助循环或 IFS 技巧。更简洁的方案是使用 mapfile(Bash 4.0+):
mapfile -t lines << "EOF"
第一行
第二行
第三行
EOF
但 mapfile 默认将每行存储为数组元素,若需完整字符串,可通过 printf -v 或 IFS 拼接。不过实践中,最推荐的方法仍是第一种命令替换,因为其语义清晰。
3. 结合 printf -v (直接赋值变量)
printf 的内置功能 -v 可以将格式化输出写入变量。结合 heredoc 变体 <<-(允许前导制表符缩进)可以写出整洁的代码:
printf -v myvar "%s\n" "$(cat <<- 'END'
这是一个测试
缩进自动忽略
END
)"
但此方法本质上仍是先用命令替换获得字符串,再通过 printf -v 赋值,不如直接 myvar=$(cat << ... ) 简洁。
三、进阶技巧:变量展开与定界符保护
heredoc 的一大特性是 默认进行变量替换和命令替换。若你希望保留文本中的 $ 和反引号原样,必须对定界符加引号(如 "EOF" 或 'EOF')。例如:
# 不加引号:$HOME 会被展开
text=$(cat << EOF
Home is $HOME
EOF
)
echo "$text" # 输出:Home is /home/user
# 加引号:原文保留
text=$(cat << 'EOF'
Home is $HOME
EOF
)
echo "$text" # 输出:Home is $HOME
四、常见陷阱与性能考量
- 尾随换行符:命令替换
$()会自动删除末尾所有换行符。若需要保留末尾空行,可追加一个额外标记字符再strip,或使用mapfile配合printf -v。 - 定界符冲突:若 heredoc 正文中包含与定界符相同的字符串,会导致解析提前结束。解决方案是使用更独特的定界符(如
_EOF_)。 - 性能对比:
cat是外部命令,每次执行会 fork 子进程。在循环中大量使用时,建议提升为printf -v+<<的内部重定向,或者直接使用read但需处理多行。不过对绝大多数脚本而言,清晰可读更为重要。
五、最佳实践:一行代码解决
结合以上分析,资深编辑推荐的“黄金写法”如下:
#!/bin/bash
my_var=$(cat << 'EOF'
...你的多行内容...
EOF
)
- 使用单引号定界符
'EOF'防止变量展开,除非你明确需要替换。 - 若需要缩进对齐,可使用
<<- 'EOF',但定界符必须顶格写(允许制表符缩进,空格不行)。 - 若内容包含反引号或
$,单引号定界符是安全选择。
六、真实场景示例:生成配置并传递
以下脚本演示如何在函数内动态生成一份 Apache 虚拟主机配置,并存入变量后续处理:
create_vhost_config() {
local domain=$1
local config
config=$(cat << EOF
<VirtualHost *:80>
ServerName ${domain}
DocumentRoot /var/www/${domain}
</VirtualHost>
EOF
)
# 这里 config 变量已包含完整配置文本
echo "$config" > "/etc/apache2/sites-available/${domain}.conf"
}
结语
将 heredoc 重定向并存入 Bash 变量,看似小技巧,实则打通了多行文本在脚本中的“柔顺流动”通道。从 cat 命令替换到 printf -v,从定界符保护到性能取舍,掌握这些细节,能让你的 Bash 脚本更加健壮与优雅。建议在日常开发中优先采用 变量=$(cat << '定界符') 模式,既兼容旧版 Bash,又易于阅读理解。下一次当你需要将一段长配置、SQL 语句或多行文本灵活嵌入脚本逻辑时,不妨试试这些方法。