在 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. 直接 readhere-string(无需 cat

利用 Bash 的 read 命令与 here-string<<<)结合:实际上更常见的做法是用 read -r 配合 << 重定向,但 read 默认只读取一行。若要读取多行,需要借助循环或 IFS 技巧。更简洁的方案是使用 mapfile(Bash 4.0+)

mapfile -t lines << "EOF"
第一行
第二行
第三行
EOF

mapfile 默认将每行存储为数组元素,若需完整字符串,可通过 printf -vIFS 拼接。不过实践中,最推荐的方法仍是第一种命令替换,因为其语义清晰。

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 语句或多行文本灵活嵌入脚本逻辑时,不妨试试这些方法。