在 Bash 脚本中,“here document”(简称“heredoc”)可以把多行输入重定向给某个命令或程序,就好像你直接在终端里逐行输入一样。 它是个强大的工具,能让你在脚本中嵌入多行文本,既不用创建外部文件,也不用做复杂的字符串操作。
分隔符:here document 以<<运算符开头,后面跟一个分隔词(通常称为“标记”或“终止符”)。
这个分隔词可以是你选定的任何单词,不过为了清晰,通常会用EOF、END或TEXT之类的词。
想让代码更好读,也可以用一个描述性的词作分隔符,比如END_INSTALLATION_INSTRUCTIONS。
内容:在开头的<< DELIMITER之后,写入你想重定向的内容。
内容可以是多行文本、代码,也可以是其他任何东西。
结束:当分隔词再次单独占一行出现,且行首和行尾都没有空白字符时,here document 就结束了。
command << DELIMITER
Content line 1
Content line 2
...
Content line N
DELIMITER
<< DELIMITER和结束的DELIMITER之间的所有行。cat << EOF
This is the first line.
This is the second line.
This is the third line.
EOF
输出:
This is the first line.
This is the second line.
This is the third line.
在这个示例中:
cat是命令。<< EOF用EOF作为分隔符,开启 here document。EOF结束 here document。cat输出它接收到的内容。wc -l << END
Line 1
Line 2
Line 3
END
输出:
3
这里wc -l统计行数。
here document 把这三行作为输入交给它。
脚本:
#!/usr/bin/env bash
# Script to process input
while IFS= read -r line; do
echo "Processing: $line"
done
在交互式 bash 提示符下,用 here document 调用这个脚本:
./your_script << MY_DATA
Item 1
Item 2
Item 3
MY_DATA
输出:
Processing: Item 1
Processing: Item 2
Processing: Item 3
Bash 会在 here document 内部执行变量展开、命令替换和算术展开。 从这个意义上说,here document 的行为就像双引号字符串。
cat << EOF
The value of HOME is $HOME
The current date is $(date)
Two plus two is $((2 + 2))
EOF
输出:
The value of HOME is /home/glennj
The current date is Thu Apr 24 13:47:32 EDT 2025
Two plus two is 4
如果给分隔符加上引号(单引号或双引号),这些展开就不会发生,内容会按字面处理。 这就像单引号字符串。
cat << 'EOF'
The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))
EOF
输出:
The value of $HOME is not expanded here.
The result of $(date) is not executed.
Two plus two is calculated by $((2 + 2))
如果你用<<-(末尾多一个连字符)代替<<,Bash 会去掉 here document 每一行行首的_制表符_。
这样就能在脚本里缩进 here document 的内容,又不影响输出。
# Note, the leading whitespace is tab characters only, not spaces!
# The ending delimiter can have leading tabs as well.
cat <<- END
This line has 1 leading tab.
This line has a leading tab and some spaces.
This line 2 leading tabs.
END
输出的所有行首制表符都会被去掉:
This line has 1 leading tab.
This line has a leading tab and some spaces.
This line 2 leading tabs.
作者并不推荐这种用法。 虽然它能提升脚本的可读性,但是:
一种典型的用法是提供帮助文本:
#!/usr/bin/env bash
usage() {
cat << END_USAGE
Refresh database tables.
usage: ${0##*/} [-h|--help] [-A|--no-archive]
where: --no-archive flag will _skip_ the archive jobs
END_USAGE
}
# ... parsing command line options here ...
if [[ $flag_help == "true" ]]; then
usage
exit 0
fi
和 here document 一样,here string(也叫“herestring”)也是向命令提供输入。
不过,here document 给出的是一整块文本,而 here string 给出的是单个字符串。
here string 使用<<< "text"语法。
tr 'a-z' 'A-Z' <<< "upper case this string"
输出:
UPPER CASE THIS STRING
和 here document 不同,here string 不需要结束分隔符。
可以用管道代替 here string:
echo "upper case this string" | tr 'a-z' 'A-Z'
那为什么还要用 here string 呢?
设想这样一种情况:你从一个耗时很长的计算中得到一个字符串,并想把这个结果分别喂给两个命令。 如果用管道,你就得把这个计算执行两次:
some_long_running_calculation | first_command
some_long_running_calculation | second_command
更高效的做法是先把计算的结果捕获下来(借助命令替换),再用 here string 给后面两个命令提供输入:
result=$( some_long_running_calculation )
first_command <<< "$result"
second_command <<< "$result"
下面是这种做法的一个实际应用:
# initialize the output CSV file
echo "ID,VALUE" > data.csv
url='https//example.com/api/query?page=1'
while true; do
json=$( curl "$url" )
# convert the results part of the response into CSV
jq -r '.results[] | [.id, .value] | @csv' <<< "$json"
# get the URL for the next page
url=$( jq -r '.next_url // ""' <<< "$json" )
if [[ "$url" == "" ]]; then
break
fi
done >> data.csv
注意输出重定向的位置。
while 循环的所有输出都会追加到data.csv文件里。
因为它们本质上只是重定向的几种形式,所以可以和其他重定向操作组合使用:
cat <<< END_OF_TEXT > output.txt
This is my important text.
END_OF_TEXT
awk '...' <<< "$my_var" >> result.csv
here document(也叫“heredoc”)是在 Bash 脚本中处理多行输入的灵活而方便的方式。 它们简化了把文本和数据直接嵌入脚本的过程,让脚本更自成一体,也更容易阅读。
here string(也叫“herestring”)和 here document 类似,但提供了一种更简单、更动态的语法。