在 Bash 指令碼中,「here document」(或稱「heredoc」)會把多行輸入重新導向到某個指令或程式,就像你直接在終端機裡輸入這些內容一樣。 這是一項強大的工具,能讓你在指令碼中內嵌多行文字,而不需要外部檔案,也不必做複雜的字串處理。
分隔符號:heredoc 以<<運算子開頭,後面接著一個作為分隔的詞(常稱為「標記」或「終止符」)。
這個分隔詞可以是任何你選的字詞,但為了清楚起見,通常會用像EOF、END或TEXT這類詞。
想讓程式碼更好讀的話,你可以用更具描述性的分隔詞,例如END_INSTALLATION_INSTRUCTIONS。
內容:在開頭的<< DELIMITER之後,寫下你想重新導向的內容。
這可以是多行文字、程式碼,或任何其他內容。
結束:當分隔詞再次單獨出現在一行,且前後都沒有空白時,heredoc 就結束了。
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會啟動 heredoc,並以EOF作為分隔詞。EOF會結束 heredoc。cat會輸出它收到的內容。wc -l << END
Line 1
Line 2
Line 3
END
輸出:
3
這裡的wc -l會計算行數。
heredoc 則提供這三行作為輸入。
指令碼:
#!/usr/bin/env bash
# Script to process input
while IFS= read -r line; do
echo "Processing: $line"
done
在互動式的 bash 提示字元中用 heredoc 呼叫這個指令碼:
./your_script << MY_DATA
Item 1
Item 2
Item 3
MY_DATA
輸出:
Processing: Item 1
Processing: Item 2
Processing: Item 3
Bash 會在 heredoc 中執行變數展開、指令替換與算術展開。 就這點來說,heredoc 的行為就像雙引號字串。
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 會移除 heredoc 每一行開頭的定位字元。
當你想在指令碼中把 heredoc 內容縮排,又不想影響輸出時,這就很有用。
# 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」)也會把輸入提供給指令。
不過,heredoc 是以一整塊文字的形式提供,herestring 則是以單一字串的形式提供。
Here string 使用<<< "text"語法。
tr 'a-z' 'A-Z' <<< "upper case this string"
輸出:
UPPER CASE THIS STRING
和 heredoc 不同的是,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,但提供更簡單、更有彈性的語法。