【Bash】ヒアドキュメントの使い方まとめ|複数行出力・変数展開の制御・sudo tee連携を徹底解説

スクリプト設計

シェルスクリプト(Bash)で設定ファイルを生成したり、複数行のSQLやスクリプトを外部コマンドに流し込みたいときに欠かせないのがヒアドキュメント(Here Document / <<EOFです。

ヒアドキュメントを使うことで、echo コマンドを何行も書き連ねることなく、直感的で視認性の高い複数行テキスト出力を記述できます。しかし、実務では「意図せず変数が展開されて中身が壊れる」「インデント用のスペースが出力に混ざる」「sudo で権限昇格してファイル保存しようとしたら Permission denied になる」といったトラブルに直面することが多々あります。

この記事では、ヒアドキュメントの基本構文から、クォートによる変数展開の制御(<<'EOF')、行頭タブの自動削除(<<-EOF)、sudo tee を組み合わせた安全なrootファイル作成、さらにヒアストリング(<<<)との使い分けまで、実務で即コピペできる実例付きで徹底解説します。

  1. 【早見表】ヒアドキュメント記法チートシート
  2. 1. ヒアドキュメントの基本構文と仕組み
    1. 基本の書き方(cat <<EOF)
    2. ファイルへ書き込む(リダイレクトとの併用)
    3. 終了識別子の命名ルール
  3. 2. 変数展開・コマンド置換の制御(<<EOF vs <<‘EOF’)
    1. クォートなし(<<EOF):変数を展開する
    2. クォートあり(<<‘EOF’ または <<“EOF”):変数を展開しない(リテラル出力)
    3. 一部の変数だけを展開したい場合(エスケープ)
  4. 3. 行頭タブを無視してインデントを整える(<<-EOF)
    1. 問題:通常のヒアドキュメントでのインデント崩れ
    2. 解決策:ハイフン付きの「<<-EOF」を使用する
  5. 4. sudo権限でファイルを作成・編集する(sudo tee 連携)
    1. なぜ「sudo cat <<EOF > /etc/file」は失敗するのか?
    2. 正解:sudo tee コマンドと組み合わせる
    3. 既存ファイルへの追記(sudo tee -a)
  6. 5. 変数への代入・パイプライン・関数での活用
    1. 複数行テキストを変数に直接格納する
    2. ヒアドキュメントから直接パイプで別コマンドへ渡す
  7. 6. ヒアストリング(Here String <<<)との使い分け
    1. ヒアストリングの基本構文
    2. ヒアドキュメントとヒアストリングの使い分け
  8. 7. 実務でよくある落とし穴とアンチパターン
    1. 落とし穴1: 終了識別子(EOF)の前後に余分な空白がある
    2. 落とし穴2: echo を何行も連打するアンチパターン
  9. 8. 実務で即コピペできる実践スクリプト例
    1. 実践例1: systemdのUnitファイルを安全に自動生成
    2. 実践例2: 複数行のSQLスクリプトを一括実行
    3. 実践例3: SSH経由でリモートサーバー上で複数コマンドを一括実行
  10. まとめ
  11. 関連おすすめ記事
  12. 関連記事

【早見表】ヒアドキュメント記法チートシート

日常の開発・運用でよく使うヒアドキュメントの記法と使い分けの早見表です。

記法 動作・特徴 主な用途・実例
cat <<EOF 変数展開・コマンド置換あり(デフォルト) 動的なテンプレート出力、環境変数を埋め込んだ設定ファイル生成
cat <<'EOF'
cat <<"EOF"
変数展開なし(すべてリテラルとして出力) スクリプト自動生成、正規表現や $ を含むコード、SQL文の出力
cat <<-EOF 行頭のタブ文字(Tab)を自動削除 if 文や関数内でインデントを綺麗に整えて記述したい場合
sudo tee file <<'EOF' > /dev/null sudo権限で新規ファイル作成・上書き /etc/ 配下の設定ファイル(Nginx, systemdなど)の安全な自動生成
sudo tee -a file <<'EOF' > /dev/null sudo権限でファイル末尾に追記 既存設定ファイルや /etc/hosts 等へのブロック追記
VAR=$(cat <<'EOF' ... EOF) 複数行テキストを変数に直接代入 複数行のメッセージ、JSON、プロンプトを変数に保持する
ssh host <<'EOF' リモートサーバーで複数行コマンドを一括実行 SSH経由のデプロイ、初期設定自動化バッチ
cmd <<< "$VAR" ヒアストリング(単一行・変数を標準入力へ) echo "$VAR" | cmd の代替(サブシェルなしで高速・安全)

1. ヒアドキュメントの基本構文と仕組み

ヒアドキュメントは、指定した「終了識別子(終端文字列)」が出現するまでの複数行テキストを、コマンドの標準入力(stdin)として直接渡すシェルの組み込み機能です。

基本の書き方(cat <<EOF)

最も一般的なのは、標準入力をそのまま出力する cat コマンドと組み合わせるパターンです。

#!/usr/bin/env bash

# 標準出力へ複数行を出力
cat <<EOF
========================================
 システム初期化スクリプト
 実行日時: $(date '+%Y-%m-%d %H:%M:%S')
========================================
EOF

実行結果:

========================================
 システム初期化スクリプト
 実行日時: 2026-08-26 17:23:00
========================================

ファイルへ書き込む(リダイレクトとの併用)

出力をファイルに保存したい場合は、通常のファイルリダイレクト(> または >>)を組み合わせます。

# 新規作成・上書き(>)
cat <<EOF > /tmp/welcome.txt
ようこそ Linux サーバーへ!
ホスト名: $(hostname)
EOF

# 既存ファイルの末尾に追記(>>)
cat <<EOF >> /tmp/welcome.txt
追記行: $(date)
EOF

終了識別子の命名ルール

終了識別子には一般的に EOF(End of File)や EOT(End of Transmission)が使われますが、任意の英数字(大文字推奨)が使用可能です。内容に応じて SQLCONFJSON などの名前にすると可読性が向上します。

# SQL用の終了識別子を使った例
mysql -u root -p"secret" <<SQL
CREATE DATABASE IF NOT EXISTS app_db;
USE app_db;
SHOW TABLES;
SQL

2. 変数展開・コマンド置換の制御(<<EOF vs <<‘EOF’)

ヒアドキュメントを扱う上で最も重要な分岐点が「終了識別子をクォートするかどうか」です。

クォートなし(<<EOF):変数を展開する

終了識別子をクォートしない場合、本文中の $VAR(変数)や $(command)(コマンド置換)、算術式 $(( ... ))実行時に展開されて出力されます。

#!/usr/bin/env bash
USER_NAME="alice"
PORT=8080

cat <<EOF
ユーザー: ${USER_NAME}
ポート番号: ${PORT}
ホームディレクトリ: ${HOME}
EOF

出力結果:

ユーザー: alice
ポート番号: 8080
ホームディレクトリ: /home/alice

クォートあり(<<‘EOF’ または <<“EOF”):変数を展開しない(リテラル出力)

終了識別子をシングルクォート 'EOF' またはダブルクォート "EOF"、あるいはバックスラッシュ \EOF で囲むと、本文中の変数や記号が一切展開されず、そのままの文字列として出力されます。

#!/usr/bin/env bash
USER_NAME="alice"

# シングルクォートで囲むと展開が無効化される
cat <<'EOF'
ユーザー: ${USER_NAME}
日付: $(date)
特殊変数: $1, $2, $$
EOF

出力結果:

ユーザー: ${USER_NAME}
日付: $(date)
特殊変数: $1, $2, $$

💡 実務のベストプラクティス

別のシェルスクリプト、Nginx設定($host$request_uri)、正規表現を含むスクリプトを自動生成する場合は、必ず <<'EOF'(クォート付き)を使用してください。クォートを忘れると、意図しない変数展開によってスクリプトが破損したり、セキュリティ上のリスクが生じます。

一部の変数だけを展開したい場合(エスケープ)

「全体の変数展開は有効(<<EOF)にしつつ、特定の $ だけを展開させずに残したい」という場合は、\$ のようにバックスラッシュでエスケープします。

#!/usr/bin/env bash
TARGET_DIR="/var/www/html"

cat <<EOF > /tmp/deploy.sh
#!/usr/bin/env bash
# TARGET_DIR は展開され、$1 や $$ はエスケープによりそのまま残る
DEST="${TARGET_DIR}"
echo "Deploying \$1 to \$DEST (PID: \$\$)..."
EOF

3. 行頭タブを無視してインデントを整える(<<-EOF)

スクリプト内で if 文や関数の中にヒアドキュメントを書く際、コードのインデントに合わせて字下げを行うと、出力されるテキストの行頭にも余計な空白が含まれてしまうという問題が発生します。

問題:通常のヒアドキュメントでのインデント崩れ

print_config() {
    if [ "$1" = "production" ]; then
        cat <<EOF
        ENV=production
        DEBUG=false
EOF
    fi
}

上記を実行すると、行頭の空白がそのまま出力に含まれてしまいます。また、閉じタグの EOF をインデントするとエラーになります。

解決策:ハイフン付きの「<<-EOF」を使用する

<<-EOF のようにハイフン(-)を付けると、各行の先頭にある「タブ文字(Tab)」が自動的に削除されます。さらに、閉じタグの EOF の前にもタブ文字を置くことが可能になります。

#!/usr/bin/env bash

create_service_config() {
	local env_name="$1"
	# <<-'EOF' で行頭のタブを自動除去(変数展開も無効化)
	cat <<-'EOF'
		[Unit]
		Description=My Application
		After=network.target

		[Service]
		Type=simple
		ExecStart=/usr/local/bin/myapp
		Restart=always

		[Install]
		WantedBy=multi-user.target
	EOF
}

create_service_config "prod"

⚠️ 超重要:スペース(半角空白)は削除されません!

<<-EOF が削除するのは「タブ文字(\t)」のみです。VS Codeなどのエディタで「タブ入力をスペースに自動変換(Insert Spaces)」が有効になっていると、行頭が半角スペースで埋まり、インデントが削除されずにエラーや意図しない出力の原因となります。エディタの設定でインデントにタブ文字が使われているか確認してください。

4. sudo権限でファイルを作成・編集する(sudo tee 連携)

Linuxサーバーの構築や運用自動化スクリプトで最も頻出するイディオムが、ヒアドキュメントと sudo tee の連携です。

なぜ「sudo cat <<EOF > /etc/file」は失敗するのか?

初心者が最も引っかかりやすいのが以下の書き方です。

# ❌ 失敗する例(Permission denied になる)
sudo cat <<'EOF' > /etc/nginx/conf.d/app.conf
server {
    listen 80;
    server_name example.com;
}
EOF

失敗する理由:
sudo が適用されるのは cat コマンド本体のみであり、リダイレクト(> /etc/...)を実行しているのは一般ユーザー権限で動作している現在のシェルだからです。一般ユーザーには /etc/ 配下への書き込み権限がないため、Permission denied エラーが発生します。

正解:sudo tee コマンドと組み合わせる

標準入力(stdin)からデータを受け取ってファイルに書き込む tee コマンドを sudo で実行することで、root権限で安全にファイルを書き出せます。余計な標準出力を画面に出さないよう > /dev/null を末尾に付与するのが実務の鉄則です。

#!/usr/bin/env bash
set -euo pipefail

# ✅ 正解:sudo tee でファイルを新規作成・上書き
sudo tee /etc/nginx/conf.d/app.conf <<'EOF' > /dev/null
server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}
EOF

echo "Nginx設定ファイルを作成しました。"

既存ファイルへの追記(sudo tee -a)

tee-a--append)オプションを付けると、既存ファイルの末尾に追記できます。

# /etc/hosts にローカルホスト定義を追記する
sudo tee -a /etc/hosts <<'EOF' > /dev/null
192.168.1.50   app-staging.local
192.168.1.51   db-staging.local
EOF

5. 変数への代入・パイプライン・関数での活用

ヒアドキュメントはファイル出力だけでなく、変数代入やパイプライン処理にも柔軟に利用できます。

複数行テキストを変数に直接格納する

コマンド置換 $( ... ) でヒアドキュメント全体を囲むことで、複数行の文字列を変数に代入できます。

#!/usr/bin/env bash

# JSONペイロードを変数に格納
JSON_PAYLOAD=$(cat <<'EOF'
{
    "service": "api-gateway",
    "status": "healthy",
    "metrics": {
        "cpu_usage": 15.4,
        "memory_mb": 512
    }
}
EOF
)

echo "格納されたJSON:"
echo "$JSON_PAYLOAD"

ヒアドキュメントから直接パイプで別コマンドへ渡す

ヒアドキュメントの出力を中間ファイルを作らずに別のコマンド(grep, sed, awk, jq, gzip など)へ渡すことができます。

# ヒアドキュメントから jq で特定のキーを抽出
cat <<'EOF' | jq -r '.users[].name'
{
  "users": [
    {"name": "Alice", "role": "admin"},
    {"name": "Bob", "role": "user"}
  ]
}
EOF

6. ヒアストリング(Here String <<<)との使い分け

Bashには、ヒアドキュメントの簡易版としてヒアストリング(Here String / <<<が用意されています。

ヒアストリングの基本構文

単一行の文字列や変数の中身を、コマンドの標準入力として渡したいときに使用します。

# 単一の文字列を標準入力に渡す
grep "error" <<< "2026-08-26 [error] database timeout"

# 変数の中身を標準入力に渡す
MESSAGE="hello world from bash"
tr '[:lower:]' '[:upper:]' <<< "$MESSAGE"
# 出力: HELLO WORLD FROM BASH

ヒアドキュメントとヒアストリングの使い分け

機能 構文 使いどころ
ヒアドキュメント cmd <<EOF ... EOF 設定ファイル、SQL、スクリプトなど複数行のテキストを流し込む場合
ヒアストリング cmd <<< "$VAR" 変数や1行〜数行の短い文字列を標準入力として渡す場合(echo "$VAR" | cmd の代替)

echo "$VAR" | cmd を使うとパイプラインによってサブシェル(別プロセス)が生成されますが、cmd <<< "$VAR" はサブシェルを生成しないため、わずかにオーバーヘッドが少なく安全です。

7. 実務でよくある落とし穴とアンチパターン

ヒアドキュメント使用時につまずきやすい代表的なエラーと対策をまとめました。

落とし穴1: 終了識別子(EOF)の前後に余分な空白がある

ヒアドキュメントで最も多いエラーがこれです。

syntax error: unexpected end of file
syntax error: unexpected EOF while looking for matching `EOF'

原因:
行末の EOF の後ろに半角スペースが入っていたり(EOF )、行頭にスペースが入っていると、Bashはそれを終了識別子として認識できず、ファイルの終端まで探し続けてエラーになります。
対策: 終了行は EOF のみ(または <<-EOF の場合はタブ+EOF)を正確に配置してください。

落とし穴2: echo を何行も連打するアンチパターン

以下のように echo >> file を何度も繰り返すコードは、可読性が低く、ディスクI/O的にも非効率です(ShellCheckでも SC2129 警告の対象となります)。

# ❌ アンチパターン(可読性が低く保守しづらい)
echo "server {" > /tmp/conf
echo "    listen 80;" >> /tmp/conf
echo "    server_name test.com;" >> /tmp/conf
echo "}" >> /tmp/conf

# ✅ 推奨:ヒアドキュメントで一括記述
cat <<'EOF' > /tmp/conf
server {
    listen 80;
    server_name test.com;
}
EOF

8. 実務で即コピペできる実践スクリプト例

実務の現場でそのままコピー&ペーストして使える実用的なスクリプト集です。

実践例1: systemdのUnitファイルを安全に自動生成

LinuxサーバーでNode.jsやGoアプリなどの常駐サービス設定を一発で作成します。

#!/usr/bin/env bash
set -euo pipefail

APP_NAME="api-server"
APP_USER="appuser"
APP_DIR="/opt/api-server"
EXEC_PATH="/opt/api-server/bin/server"
PORT=3000

SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service"

echo "systemd Unitファイルを作成中: ${SERVICE_FILE}"

# 変数展開ありで設定ファイルを生成し、sudo tee で配置
sudo tee "${SERVICE_FILE}" <<EOF > /dev/null
[Unit]
Description=${APP_NAME} Application Service
After=network.target

[Service]
Type=simple
User=${APP_USER}
WorkingDirectory=${APP_DIR}
Environment="PORT=${PORT}"
Environment="NODE_ENV=production"
ExecStart=${EXEC_PATH}
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

# ユニットの再読み込み
sudo systemctl daemon-reload
echo "作成完了: sudo systemctl enable --now ${APP_NAME} で起動できます。"

実践例2: 複数行のSQLスクリプトを一括実行

DBマイグレーションや初期データ投入スクリプトのテンプレートです。

#!/usr/bin/env bash
set -euo pipefail

DB_HOST="127.0.0.1"
DB_USER="dbadmin"
DB_NAME="production_db"
DB_PASS="MySecurePassword123"

# クォート付きヒアドキュメントで安全にSQLを実行
mysql -h "${DB_HOST}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" <<'SQL'
START TRANSACTION;

CREATE TABLE IF NOT EXISTS system_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    event_type VARCHAR(50) NOT NULL,
    message TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO system_logs (event_type, message) 
VALUES ('INITIALIZATION', 'System successfully initialized');

COMMIT;
SQL

echo "SQLの実行が正常に完了しました。"

実践例3: SSH経由でリモートサーバー上で複数コマンドを一括実行

踏み台サーバーやローカルPCから、リモートホスト上で複数行の初期化コマンドを実行します。

#!/usr/bin/env bash
set -euo pipefail

REMOTE_SERVER="ubuntu@192.168.1.100"

echo "リモートサーバー (${REMOTE_SERVER}) のセットアップを開始..."

# リモート先で実行するコマンド群(変数展開を無効化して安全に転送)
ssh -T "${REMOTE_SERVER}" <<'EOF'
set -euo pipefail

echo "=== ホスト名確認 ==="
hostname -f

echo "=== ディスク使用量 ==="
df -h /

echo "=== 不要パッケージのクリーンアップ ==="
sudo apt-get update -qq
sudo apt-get autoremove -y -qq

echo "リモート処理が完了しました。"
EOF

まとめ

Bashのヒアドキュメントを安全・効率的に使いこなすための重要ポイントを振り返ります。

  • 基本: cat <<EOF で複数行を標準出力やファイルへ流し込む
  • 変数展開の抑制: スクリプトや設定ファイル生成には必ず cat <<'EOF'(クォート付き) を使う
  • インデント整形: <<-EOF を使うことで、行頭のタブ文字を自動的に除去できる
  • root権限での保存: リダイレクト失敗(Permission denied)を防ぐため sudo tee file <<'EOF' > /dev/null を使う
  • 単一行・変数: echo | cmd の代わりに ヒアストリング cmd <<< "$VAR" を活用する
  • 構文エラー対策: 終了識別子(EOF)の後ろに余分なスペースを残さない

ヒアドキュメントと sudo tee をマスターすれば、サーバー構築自動化やデプロイスクリプトの記述が格段にスッキリし、保守性の高い堅牢なシェルスクリプトが書けるようになります。ぜひ本記事のテンプレートを日々の実務に役立ててください。

関連おすすめ記事

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント