Linuxシェルスクリプトでヒアドキュメントを活用する方法

ヒアドキュメントは複数行の本文をコマンドの標準入力へ渡すリダイレクトです。開始演算子と終端語の引用方法によって、本文でパラメータ展開、コマンド置換、算術展開が起きるかが変わります。見た目が似ていても、引用した終端語と引用しない終端語では安全性と出力が大きく異なるため、本文を固定文として渡すのか、変数を埋め込むのかを先に決めます。

結論は「原則は引用した終端語を選び、展開が必要な短い箇所だけを意図的に分離します。終端行は先頭から完全一致させ、余分な空白を置きません。標準入力へ渡すだけの例で挙動を確認してから、ファイル作成やSSHなど影響のある用途へ進み、候補出力と差分を検証します。」です。

目次

本文を固定するか展開するか決める

Bashの形式はcommand <<wordです。wordへの引用除去後の文字列が終端語になります。wordのどこかを引用すると本文の各行は展開されません。引用しない場合は本文でパラメータ展開、コマンド置換、算術展開が行われ、バックスラッシュと改行の組も特別に扱われます。<<-wordは本文と終端行の先頭にあるタブを除去しますが、スペースは除去しません。ヒアストリングの<<<wordは別機能で、単一wordを展開して改行を付けて標準入力へ渡します。

  • 本文は固定データか変数・コマンド結果を展開するテンプレートか
  • 終端語が本文中に単独行として現れないか
  • 終端行の前後に空白や見えない文字がないか
  • 受け手のコマンドが標準入力をデータとして扱うかコードとして扱うか
  • ファイル上書き・リモート実行・秘密情報露出の影響がないか

最初はcatへ渡して標準出力だけを比較します。set -xは展開後の秘密値を記録し得るため、ヒアドキュメントの調査目的でも安易に有効化しません。終端を認識しないエラーでは、終端語の綴り、大文字小文字、引用、インデント、CRLF、末尾空白を確認します。エディターの空白可視化やodによるバイト確認が役立ちます。本文が受け手の言語として解釈される場合は、その言語固有のエスケープや注入対策も別途必要です。

ヒアドキュメントの開始演算子、終端語の引用、終端行の綴りと空白を確認します。<<‘EOF’のように終端語を引用すると本文のshell展開を抑止し、<<EOFではparameter expansion、command substitution、arithmetic expansionが行われます。<<-EOFが除くのは先頭のtabだけでspaceではありません。CRLFや終端行末尾spaceも認識失敗の原因になります。

標準出力だけで終端語と改行を試す

固定文は<<‘END_TEXT’のように終端語を引用して、本文がそのまま届くことを確認します。展開が必要なら対象変数を検証し、引用しない終端語を使う範囲を最小にします。インデント目的の<<-はタブだけが対象だと理解し、スペース混在をテストします。ファイルへ書く必要がある場合は既存ファイルへ直接上書きせず、権限を限定した候補ファイルを作り、構文検査とdiffを通した後に承認された置換手順を使います。

固定本文をそのまま表示

cat <<'END_TEXT'
$HOME は展開されません
$(date) も文字列のままです
END_TEXT

終端語を単一引用符で囲むと、本文のドル記号やコマンド置換形式は実行されず、そのまま標準入力へ渡されます。

検証済み変数だけを展開

name=sample
cat <<END_TEXT
name=$name
END_TEXT

引用しない終端語では変数が展開されます。外部入力を使うなら許可文字と長さを先に検証します。

タブインデントを除去

cat <<-'END_TEXT'
	first line
	second line
	END_TEXT

ここでの字下げは実際のタブである必要があります。スペースを使うと除去されず、終端語も認識されない場合があります。

受け手を明示して標準入力へ渡す

while IFS= read -r line; do
  printf 'line=%s\n' "$line"
done <<'END_TEXT'
alpha beta
*.txt
END_TEXT

read -rと引用を使い、空白やワイルドカードをデータのまま扱います。本文をevalへ渡す設計は避けます。

リモート側で展開させる境界

ssh example-host 'bash -s' <<'REMOTE_SCRIPT'
printf 'remote_host=%s\n' "$HOSTNAME"
uname -a
REMOTE_SCRIPT

引用した終端語によりローカル側の展開を止め、本文はリモートBashへ渡します。実行先、認証、許可された読み取りコマンドを事前に確認します。

最初はcatへ渡すだけの固定本文をquoted delimiterで作り、ドル記号、backslash、空行、終端語に似た行がそのまま出るか確認します。展開が必要なら入力値を検証し、unquoted delimiterの範囲を最小にします。file生成は既存fileへ直接redirectせず、permissionを限定した候補fileへ出し、構文検査とdiffを通してから承認されたatomicな置換手順を使います。

引用・展開・タブ除去の違いを確認する

ヒアドキュメントは本文を一時ファイルへ保存する構文ではなく、コマンドの標準入力を構成するリダイレクトとして理解します。実装内部で一時的な記憶領域が使われる可能性と、利用者がファイルを作ることは別です。終端語を引用しても、受け手がSQL、シェル、設定言語などとして本文を再解釈すれば、その言語での注入や構文エラーは起こり得ます。引用はBash側の展開を止めるもので、下流のパーサーまで無害化する機能ではありません。

  • 終端語を引用するとヒアドキュメント本文のシェル展開を抑止できる
  • 引用しない本文ではパラメータ展開・コマンド置換・算術展開が行われる
  • <<-が除去する先頭文字はタブであり通常のスペースではない
  • 終端行は終端語だけを置き余分な文字を付けない
  • ヒアストリング<<<はヒアドキュメントとは異なり単一wordを改行付きで渡す

here-documentはcommandの標準入力を構成するredirectionで、利用者が明示的に永続fileを作る構文そのものではありません。delimiterの引用はBash側の展開を制御しますが、受け手がSQL、shell、YAML等として本文を再解釈すれば、その言語での注入や構文errorは起きます。quoted delimiterは下流parserまで無害化するsecurity機能ではありません。

local・remoteの展開境界と上書き・秘密混入を防ぐ

秘密値を展開した本文はプロセスの受け手、ログ、デバッグトレース、エラー出力に現れる可能性があります。認証情報は本文へ直接埋め込まず、対象ツールが提供する秘密情報入力方式を使います。cat > existing-fileのような直接上書きは、リダイレクトがコマンド実行より先にファイルを切り詰める点が危険です。SSHへ渡す本文はローカル展開とリモート展開のどちらを意図するかを固定し、ホスト名と実行権限を確認します。

  • 固定本文なのに終端語を引用せず意図しない展開を起こす
  • 終端行の末尾空白やスペース字下げで終端を認識できない
  • <<-がすべてのインデントを除くと誤解する
  • 引用しただけで下流言語への注入も防げると考える
  • 候補確認なしに既存設定ファイルへ直接リダイレクトする

secretをunquoted本文へ埋めると、受け手、error、xtrace、監査logへ展開後の値が現れます。credentialは対象toolのsecret input方式を使います。cat > existingのredirectionはcat起動前にfileをtruncateするため、commandが失敗しても原本を失う危険があります。SSHへ渡す場合はlocal展開かremote展開かをquoted REMOTE_SCRIPTで固定し、hostと権限を確認します。

生成結果を構文と差分で検証する

固定本文、展開本文、空行、ドル記号、バックスラッシュ、タブ、スペース、終端語に似た行を含む小さなfixtureで出力を比較します。ファイル生成なら候補ファイルの所有者、権限、改行形式、末尾改行、構文、差分を確認します。リモート実行なら最初はhostnameやunameなど読み取りだけに限定し、ローカル値とリモート値のどちらが表示されたかを記録します。

  1. 引用した終端語と引用しない終端語を目的別に選んだ
  2. 終端行・タブ・改行を小さな出力例で確認した
  3. 上書きやリモート実行の前に候補と影響範囲を検証した
  4. 下流コマンドの解釈と秘密情報の経路まで確認した

quoted、unquoted、<<-、space/tab混在、CRLF、空行、ドル記号、command substitution形式を含むfixtureでbyte単位の出力を比較します。候補fileならowner、mode、encoding、改行、末尾改行、構文、diffを確認します。SSH例は最初にhostnameやuname等の読み取りだけを使い、表示値がlocalではなく意図したremote側で展開されたかを記録します。

用途に合う入力・生成手段を選ぶ

数行の固定入力なら引用したヒアドキュメントが読みやすい選択です。単一の短い値ならprintfやヒアストリング、巨大データなら通常ファイルやパイプ、構造化データなら専用のserializerを検討します。複雑なテンプレートを多数の変数で組み立てる場合は、シェル展開へ依存せずテンプレートエンジンや対象形式のライブラリへ移すと検証しやすくなります。

数行の固定入力はquoted here-document、短い単一値はprintfまたはhere-string、巨大dataはfile/stream、JSONやYAMLは専用serializerを選びます。多くの変数、条件分岐、escapeを含むtemplateをshell展開で組み立てると検証困難になるため、対象形式のlibraryやtemplate engineへ移します。既存設定の更新では生成と適用を分離し、diffなしの直接上書きを避けます。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次