У сценаріях Bash «документ тут» (або «heredoc») перенаправляє до команди чи програми кілька рядків введення, ніби ми набираємо їх безпосередньо в терміналі. Це потужний інструмент для вбудовування багаторядкового тексту у свої скрипти без потреби у зовнішніх файлах чи складній роботі з рядками тексту (англ. string).
Роздільник: документ тут починається з оператора <<, після якого йде слово-роздільник (його часто називають «маркером» або «термінатором»).
Цим роздільником може бути будь-яке слово, яке ми виберемо, але для ясності зазвичай беруть щось на кшталт EOF, END чи TEXT.
Щоб код було легше читати, можна взяти описовий роздільник, наприклад END_INSTALLATION_INSTRUCTIONS.
Вміст: після початкового << DELIMITER ми пишемо вміст, який хочемо перенаправити.
Це може бути кілька рядків тексту, коду чи будь-чого іншого.
Завершення: документ тут закінчується тоді, коли слово-роздільник знову стоїть окремим рядком, без пробілів на початку чи в кінці.
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 як роздільник.EOF в окремому рядку завершує документ тут.cat виводить вміст, який отримав.wc -l << END
Line 1
Line 2
Line 3
END
Вивід:
3
Тут wc -l рахує кількість рядків.
Документ тут подає ці три рядки як введення.
Скрипт:
#!/usr/bin/env bash
# Script to process input
while IFS= read -r line; do
echo "Processing: $line"
done
Викличмо скрипт з інтерактивного запрошення bash за допомогою документа тут:
./your_script << MY_DATA
Item 1
Item 2
Item 3
MY_DATA
Вивід:
Processing: Item 1
Processing: Item 2
Processing: Item 3
Bash виконує розкриття змінних, підстановку команд та арифметичне розкриття всередині документа тут. У цьому сенсі документи тут поводяться як рядки в подвійних лапках.
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 прибирає всі провідні символи табуляції з кожного рядка документа тут.
Це зручно, коли треба зробити відступ для вмісту документа тут усередині скрипта, не змінюючи вивід.
# 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
Як і документи тут, рядки тут (або «herestring») передають введення команді.
Однак, тоді як документи тут задаються цілим блоком тексту, рядки тут задаються одним рядком тексту.
Рядки тут використовують синтаксис <<< "text".
tr 'a-z' 'A-Z' <<< "upper case this string"
Вивід:
UPPER CASE THIS STRING
На відміну від документів тут, кінцевий роздільник не потрібен.
Замість рядка тут можна використати конвеєр:
echo "upper case this string" | tr 'a-z' 'A-Z'
То навіщо використовувати рядок тут?
Розгляньмо випадок, коли ми отримуємо рядок тексту як результат тривалого обчислення і хочемо подати цей результат двом різним командам. Із конвеєрами доведеться виконати обчислення двічі:
some_long_running_calculation | first_command
some_long_running_calculation | second_command
Ефективніший підхід - зберегти вивід обчислення (скориставшись підстановкою команд) і подати його як введення двом наступним командам за допомогою рядків тут:
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
Документи тут (або «heredoc») - це гнучкий і зручний спосіб керувати багаторядковим введенням у скриптах Bash. Вони спрощують вбудовування тексту й даних безпосередньо у скрипти, роблячи їх самодостатнішими й легшими для читання.
Рядки тут (або «herestring») схожі на документи тут, але мають простіший і динамічніший синтаксис.