設定ファイルを動的に作りたい場面でechoを連ねると、クォートのエスケープが増えて視認性が一気に落ちます。SQLを複数行まとめてMySQLに渡したいときも、変数をどこで展開すべきか悩む場面があるでしょう。
この記事では、シェルスクリプトのヒアドキュメント(here document)を使った動的生成の設計パターンを解説します。基本3構文(変数展開ON/OFF・インデント対応)から、設定ファイル生成・SQL一括実行・通知メール本文の実践パターン、よくあるトラブルシュートまでを網羅します。動作確認環境はRHEL 9.4 / Ubuntu 24.04 LTSです。
この記事のポイント
・<<EOFは変数展開あり、<<'EOF'はシングルクォートで囲んで展開なしにできる
・<<-EOFを使うとタブでインデントした行頭を揃えてコードを整理できる
・ssh・mysql・mailコマンドと組み合わせると複数行の入力を安全に渡せる
・区切り文字はEOF以外の任意の文字列でよい。ネスト時は別名を使う
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜヒアドキュメントが必要なのか(echoの限界)
設定ファイルを動的に生成するとき、echoとヒアドキュメントの書き方を比べてみましょう。どちらも同じNginxのserver{}ブロックを生成するコードです。#!/bin/bash SERVER_NAME="example.com" DOC_ROOT="/var/www/html/example" # echo版(行ごとにリダイレクトが必要でクォートのエスケープが増える) echo "server {" > /etc/nginx/conf.d/example.conf echo " listen 80;" >> /etc/nginx/conf.d/example.conf echo " server_name ${SERVER_NAME};" >> /etc/nginx/conf.d/example.conf echo " root ${DOC_ROOT};" >> /etc/nginx/conf.d/example.conf echo " index index.html index.htm;" >> /etc/nginx/conf.d/example.conf echo "}" >> /etc/nginx/conf.d/example.conf
#!/bin/bash SERVER_NAME="example.com" DOC_ROOT="/var/www/html/example" # ヒアドキュメント版(ファイルに書きたい内容をそのままスクリプト内に書ける) cat <
/etc/nginx/conf.d/example.conf server { listen 80; server_name ${SERVER_NAME}; root ${DOC_ROOT}; index index.html index.htm; } EOF
ヒアドキュメントの基本構文と3つの記法
1. 標準構文(変数展開あり)
基本形はcat <<区切り文字 です。区切り文字(よく「EOF」や「END」が使われます)は開始行と終了行が一致していれば任意の文字列を使えます。終了行の区切り文字は行頭から書く必要があります。#!/bin/bash DB_HOST="db01.internal" DB_NAME="production" # 区切り文字はEOF以外にも END や HEREDOC など何でもよい cat <
[miyazaki@web01 ~]$ bash heredoc_test.sh [database] host = db01.internal name = production port = 3306
cat <<EOF > /path/to/file のようにリダイレクトを追加します。2. 展開を止める構文('EOF'をシングルクォートで囲む)
区切り文字をシングルクォートで囲む('EOF')と、本文内の変数展開・コマンド置換・バックスラッシュ解釈がすべて無効になります。後からsedや別のツールで置換するテンプレートファイルを生成するときに使います。#!/bin/bash # 変数展開を止めたい場合は区切り文字をシングルクォートで囲む cat <<'EOF' > /tmp/template_raw.txt # このファイルは後でsedで置換する HOST=${DB_HOST} NAME=${DB_NAME} PORT=3306 EOF cat /tmp/template_raw.txt
[miyazaki@web01 ~]$ bash heredoc_noexpand.sh # このファイルは後でsedで置換する HOST=${DB_HOST} NAME=${DB_NAME} PORT=3306
3. インデントを効かせる(<<-EOF)
<<-EOF(ハイフン付き)を使うと、ヒアドキュメント本文の行頭タブ文字が取り除かれます。関数やif文の中でヒアドキュメントを書くとき、スクリプト本体のインデントに合わせてヒアドキュメントの内容も字下げできます。#!/bin/bash generate_config() { local server_name="$1" # <<-EOF で行頭のタブを取り除く(本文はタブで字下げしてある) cat <<-EOF [server] name = ${server_name} enabled = true EOF } generate_config "app01" generate_config "app02"
[miyazaki@web01 ~]$ bash heredoc_indent.sh [server] name = app01 enabled = true [server] name = app02 enabled = true
・エディタが「タブをスペースに自動変換」する設定になっている場合は動作しません
・後述のトラブルシュートでタブとスペースの確認方法を解説します
実践:設定ファイルの動的生成
1. ループで複数のバーチャルホスト設定を生成する
バーチャルホストが複数ある場合、ドメイン名・ポート番号・ドキュメントルートをループで変えながら設定ファイルを一括生成できます。#!/bin/bash # 複数バーチャルホストのNginx設定を一括生成するスクリプト # 書式: "サイト名:ポート:ドキュメントルート" SITES=( "site-a:8081:/var/www/site-a" "site-b:8082:/var/www/site-b" "site-c:8083:/var/www/site-c" ) for site_info in "${SITES[@]}"; do IFS=':' read -r name port docroot <<< "${site_info}" conf_file="/etc/nginx/conf.d/${name}.conf" cat <
"${conf_file}" server { listen ${port}; server_name ${name}.example.com; root ${docroot}; index index.html index.htm; access_log /var/log/nginx/${name}_access.log; error_log /var/log/nginx/${name}_error.log; } EOF echo "[$(date '+%Y-%m-%d %H:%M:%S')] 生成完了: ${conf_file}" done
[miyazaki@web01 ~]$ sudo bash gen_vhost.sh [2026-08-12 10:14:32] 生成完了: /etc/nginx/conf.d/site-a.conf [2026-08-12 10:14:32] 生成完了: /etc/nginx/conf.d/site-b.conf [2026-08-12 10:14:32] 生成完了: /etc/nginx/conf.d/site-c.conf
nginx -t)と再読み込みが必要です。Apacheを使っている場合はhttpdの再起動が必要になります。httpd の基本操作で起動・停止・設定テストの手順を確認できます。2. SSH経由でリモートサーバーに設定を流し込む
ヒアドキュメントはSSHコマンドと組み合わせると、リモートサーバー上で実行するコマンドをまとめて渡せます。踏み台なしでリモートに設定を反映するバッチ処理に使えます。#!/bin/bash REMOTE_HOST="app01.internal" DEPLOY_DIR="/opt/myapp" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') ssh miyazaki@${REMOTE_HOST} <
> "${DEPLOY_DIR}/logs/deploy.log" systemctl restart myapp.service systemctl is-active myapp.service && echo "起動確認OK" || echo "起動失敗" EOF
\$(date) のようなコマンド置換を使う場合はバックスラッシュでエスケープが必要です(ローカル側で展開されてしまうため)・リモートでの展開も止めたい場合は
<<'EOF' を使いますヒアドキュメントを含むシェルスクリプトの設計力を体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える設計パターンを体系的に学べます。
実践:コマンドへの複数行入力
1. MySQLにSQLをまとめて実行する
バッチ処理でMySQLにSQL文を複数渡す場面では、ヒアドキュメントをmysqlコマンドに流し込む形が実用的です。#!/bin/bash DB_USER="appuser" DB_NAME="inventory" TARGET_DATE="2026-08-01" mysql -u "${DB_USER}" -p"$(cat /etc/myapp/db_pass)" "${DB_NAME}" <
・パスワードはファイルから読み込んでスクリプト内に直書きしないようにしてください
・SQLの実行結果を確認したい場合は
mysql ... <<SQL | tee /var/log/batch_result.log でログに残せます2. mailコマンドで通知メール本文を動的生成する
cronで動くバッチスクリプトで障害を検知したとき、その場で構造化された本文を生成してメール送信できます。#!/bin/bash HOSTNAME=$(hostname) DISK_USAGE=$(df -h /var | awk 'NR==2{print $0}') TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') ALERT_MAIL="admin@example.com" send_disk_alert() { local usage_pct="$1" mail -s "[ALERT] ${HOSTNAME} ディスク使用率 ${usage_pct}% 超過" "${ALERT_MAIL}" <
トラブルシュート よくある失敗パターン
変数が展開されない(シングルクォートEOFを使ったとき)
設定ファイルに ${SERVER_NAME} という文字列がそのまま書き込まれてしまう場合、原因の大半は<<'EOF' を使ってしまったことです。#!/bin/bash SERVER_NAME="example.com" # NG: シングルクォート区切りは変数展開されない cat <<'EOF' server_name ${SERVER_NAME}; EOF # OK: クォートなし区切りは変数展開される cat <
[miyazaki@web01 ~]$ bash heredoc_ng.sh server_name ${SERVER_NAME}; server_name example.com;
<<'EOF' を使い、変数を展開したい場合は <<EOF(クォートなし)を使ってください。インデントが効かない(タブではなくスペースで字下げしている)
<<-EOF を使ってもインデントが取り除かれない場合、エディタがタブの代わりにスペースを使っている可能性があります。cat -A でタブ文字(^I)とスペースを判別できます。# cat -Aでタブ(^I)とスペースを確認する [miyazaki@web01 ~]$ cat -A heredoc_indent.sh | grep -n "name =" 7: name = ${server_name};$ # ^Iが見えればタブ(正常) 8: name = ${server_name};$ # スペースのみだと <<-EOF が効かない
^I が表示されている行はタブ文字で字下げされています。スペースが表示されている場合はエディタの設定を確認するか、ヒアドキュメント本文の字下げをスペースからタブに手動で変換してください。Vimの場合は :set noexpandtab でタブ入力に切り替えられます。本記事のまとめ
| やりたいこと | 記法・設計パターン |
|---|---|
| 変数を展開して複数行テキストを出力する | cat <<EOF ... EOF |
| 変数・コマンド置換を展開せずに生のテキストを出力する | cat <<'EOF' ... EOF |
| 関数・if文内でインデントを揃えて書く | cat <<-EOF(本文はタブで字下げ) |
| 設定ファイルを変数パラメータで動的生成する | cat <<EOF > /etc/nginx/conf.d/${name}.conf |
| SSHでリモートに複数のコマンドをまとめて渡す | ssh user@host <<EOF ... EOF |
| MySQLに複数のSQL文を一括実行する | mysql dbname <<SQL ... SQL |
| mailコマンドに動的な本文を渡す | mail -s "件名" addr <<MAIL_BODY ... MAIL_BODY |
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:シェルスクリプトの終了コード設計|exit codeを体系化して呼び出し元がエラー原因を判別できる仕組み
- 前のページへ:シェルスクリプトでディスク使用量を定期監視・自動クリーンアップする設計|閾値アラートと古いログ削除の実装パターン
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます