Треки
/
Bash
Bash
/
Салабус
/
Here-документи
He

Here-документи у Bash

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Here-документи

У сценаріях Bash «документ тут» (або «heredoc») перенаправляє до команди чи програми кілька рядків введення, ніби ми набираємо їх безпосередньо в терміналі. Це потужний інструмент для вбудовування багаторядкового тексту у свої скрипти без потреби у зовнішніх файлах чи складній роботі з рядками тексту (англ. string).

Ключові особливості та синтаксис

  1. Роздільник: документ тут починається з оператора <<, після якого йде слово-роздільник (його часто називають «маркером» або «термінатором»). Цим роздільником може бути будь-яке слово, яке ми виберемо, але для ясності зазвичай беруть щось на кшталт EOF, END чи TEXT. Щоб код було легше читати, можна взяти описовий роздільник, наприклад END_INSTALLATION_INSTRUCTIONS.

  2. Вміст: після початкового << DELIMITER ми пишемо вміст, який хочемо перенаправити. Це може бути кілька рядків тексту, коду чи будь-чого іншого.

  3. Завершення: документ тут закінчується тоді, коли слово-роздільник знову стоїть окремим рядком, без пробілів на початку чи в кінці.

Базовий синтаксис

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 як роздільник.
  • саме ці три рядки тексту і є вмістом.
  • EOF в окремому рядку завершує документ тут.
  • потім cat виводить вміст, який отримав.

Приклад 2: використання з wc (підрахунок слів)

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

Вивід:

3

Тут wc -l рахує кількість рядків. Документ тут подає ці три рядки як введення.

Приклад 3: передавання даних скрипту

Скрипт:

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

Автор не радить так робити. Хоча це може поліпшити читабельність скрипта,

  1. символи табуляції легко випадково замінити пробілами (редактор може зробити це автоматично), і
  2. різницю між пробілами й табуляцією важко помітити.

Коли використовувати документи тут

  • Багаторядкове введення: коли треба передати команді кілька рядків тексту.
  • Файли конфігурації: вбудовування невеликих фрагментів конфігурації у скрипт.
  • Генерування коду: створення коду на льоту всередині скрипта.
  • Сценарії взаємодії: імітація введення користувача для інтерактивних програм.
  • Уникнення зовнішніх файлів: коли не хочеться створювати тимчасові файли.

Типовим прикладом може бути передавання довідкового тексту:

#!/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"

Ось реальний приклад застосування цієї ідеї:

  • зберегти відповідь JSON на запит до REST API (який розбито на сторінки),
  • передати дані 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.

Документи тут і рядки тут як перенаправлення

Оскільки це лише форми перенаправлення, їх можна поєднувати з іншими операціями перенаправлення:

cat <<< END_OF_TEXT > output.txt
This is my important text.
END_OF_TEXT

awk '...' <<< "$my_var" >> result.csv

Підсумок

Документи тут (або «heredoc») - це гнучкий і зручний спосіб керувати багаторядковим введенням у скриптах Bash. Вони спрощують вбудовування тексту й даних безпосередньо у скрипти, роблячи їх самодостатнішими й легшими для читання.

Рядки тут (або «herestring») схожі на документи тут, але мають простіший і динамічніший синтаксис.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці