He

Here 文档 属于 Bash

{other: "%{count} 个练习"}

关于 Here 文档

在 Bash 脚本中,“here document”(简称“heredoc”)可以把多行输入重定向给某个命令或程序,就好像你直接在终端里逐行输入一样。 它是个强大的工具,能让你在脚本中嵌入多行文本,既不用创建外部文件,也不用做复杂的字符串操作。

主要特性与语法

  1. 分隔符:here document 以<<运算符开头,后面跟一个分隔词(通常称为“标记”或“终止符”)。 这个分隔词可以是你选定的任何单词,不过为了清晰,通常会用EOF、END或TEXT之类的词。 想让代码更好读,也可以用一个描述性的词作分隔符,比如END_INSTALLATION_INSTRUCTIONS。

  2. 内容:在开头的<< DELIMITER之后,写入你想重定向的内容。 内容可以是多行文本、代码,也可以是其他任何东西。

  3. 结束:当分隔词再次单独占一行出现,且行首和行尾都没有空白字符时,here document 就结束了。

基本语法

command << DELIMITER
  Content line 1
  Content line 2
  ...
  Content line N
DELIMITER

工作原理

  • Bash 会读取起始的<< DELIMITER和结束的DELIMITER之间的所有行。
  • Bash 把这些内容接到命令的标准输入上。
  • 命令处理这些输入,就如同它们来自键盘一样。

示例 1:简单的文本输出

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输出它接收到的内容。

示例 2:配合 wc(单词计数)

wc -l << END
Line 1
Line 2
Line 3
END

输出:

3

这里wc -l统计行数。 here document 把这三行作为输入交给它。

示例 3:把数据传给脚本

脚本:

#!/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.
Caution

作者并不推荐这种用法。 虽然它能提升脚本的可读性,但是:

  1. 很容易不小心把制表符换成空格(你的编辑器可能会自动这么做),而且
  2. 空格和制表符之间的差别很难看出来。

何时使用 here document

  • 多行输入:需要向命令提供多行文本时。
  • 配置文件:在脚本中嵌入一小段配置时。
  • 生成代码:在脚本中即时生成代码时。
  • 脚本交互:为交互式程序模拟用户输入时。
  • 避免外部文件:不想创建临时文件时。

一种典型的用法是提供帮助文本:

#!/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 document 的缩进和周围的代码比起来,会显得很突兀。

Here String

和 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?

可以用管道代替 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"

下面是这种做法的一个实际应用:

  • 捕获 REST API 查询的 JSON 响应(该查询是分页的),
  • 把 JSON 数据交给一个 jq 程序来解析结果并输出到文件,然后
  • 再把 JSON 数据交给另一个 jq 程序,确定下一次查询的 URL。
# 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文件里。

作为重定向的 Here Document 和 Here String

因为它们本质上只是重定向的几种形式,所以可以和其他重定向操作组合使用:

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 类似,但提供了一种更简单、更动态的语法。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开