2026年7月27日
2026年7月27日
bashのヒアドキュメントとヒアストリングの使い方|変数展開とクォートの違い
ヒアドキュメント(<<EOF)は複数行のテキストをそのままコマンドの標準入力に渡す書き方で、ヒアストリング(<<<)は1つの文字列を標準入力に渡す書き方です。設定ファイルの生成、リモートサーバーでのコマンド実行、SQLの投入、変数の中身をコマンドに渡す処理などで日常的に使います。この記事では、区切り文字をクォートするかどうかで変わる変数展開の挙動、sudo でファイルに書き込むときの書き方、ssh との組み合わせ、<<- によるインデント、そして「区切り文字が見つからない」警告の原因までを解説します。
動作は(2026年10月現在)一般的な Linux の bash 5 系を前提にしています。ヒアドキュメントは POSIX sh でも使えますが、ヒアストリングは bash・zsh・ksh の拡張機能です。
ヒアドキュメントの基本
コマンド <<区切り文字 と書くと、次の行から区切り文字だけの行が現れるまでの内容が、そのコマンドの標準入力になります。区切り文字は EOF が慣例ですが、本文に出てこない文字列なら何でもかまいません。
cat <<EOF
こんにちは、$USER さん
今日は $(date +%Y-%m-%d) です
EOF
区切り文字の行は、行頭から始まり、前後に余計な文字(空白を含む)があってはいけません。インデントした位置に EOF を書くと終端として認識されません。
ファイルへ書き出す
出力をリダイレクトすれば、そのままファイルを作れます。
cat <<'EOF' > /tmp/hello.txt
1行目
2行目
EOF
> は上書き、>> は追記です。既存の設定ファイルを上書きする前には、cp -p 元ファイル 元ファイル.bak などでバックアップを取っておいてください。
クォートの有無で展開が変わる
ヒアドキュメントでもっとも重要なのが、区切り文字をクォートするかどうかです。
<<EOF(クォートなし):本文中の$変数、$(コマンド)、$((計算))が展開され、\が一部の文字のエスケープとして働く<<'EOF'や<<"EOF"、<<\EOF(どこか一部でもクォート):本文は一切展開されず、書いたとおりに渡される
この違いが特に問題になるのが、本文自体に $ を含む設定ファイルやスクリプトを生成する場面です。たとえば nginx の設定では $uri や $args が nginx 側の変数として使われます。
# クォートした区切り文字: $uri などがそのまま書き込まれる
sudo tee /etc/nginx/snippets/wp-try-files.conf > /dev/null <<'EOF'
location / {
try_files $uri $uri/ /index.php?$args;
}
EOF
sudo nginx -t
これをクォートなしの <<EOF で書くと、シェルが $uri を空の変数として展開してしまい、try_files / /index.php?; という壊れた設定が書き込まれます。書き込んだ後に nginx -t で構文を確認する習慣も付けておくと安心です。
シェルの変数と設定側の変数を混ぜる
ドメイン名だけはシェルの変数から埋め込み、nginx の変数はそのまま残したい場合は、クォートなしにして残したい側の $ を \$ とエスケープします。
domain="example.com"
cat <<EOF > "/tmp/${domain}.conf"
server {
server_name ${domain};
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
}
EOF
エスケープが多くなると読みにくく、漏れも起きやすくなります。その場合は、クォートしたヒアドキュメントで __DOMAIN__ のような目印を書き、後から sed で置き換える方法も検討してください。
sudoでファイルに書き込む
sudo cat <<'EOF' > /etc/xxx.conf は、多くの場合「Permission denied」になります。リダイレクト(>)は sudo の中ではなく、呼び出し元の一般ユーザーのシェルが処理するためです。管理者権限で書き込むには、上の例のように sudo tee ファイル > /dev/null に標準入力として渡します。追記なら sudo tee -a です。> /dev/null は、tee が画面にも同じ内容を出力するのを抑えるためのものです。
sshでリモートにコマンドを渡す
複数のコマンドをまとめてリモートで実行するときにも使えます。
target_dir="/var/www/html"
ssh deploy@web01 "bash -s -- $(printf '%q' "$target_dir")" <<'EOF'
set -euo pipefail
cd "$1"
git status --short
df -h .
EOF
区切り文字をクォートしているので、本文の $1 はローカルではなくリモートの bash で評価されます。ローカルの値は bash -s -- 引数 の形で位置パラメーターとして渡し、printf '%q' で空白や記号を含む値もリモート側で1つの引数として扱われるようにしています。クォートなしで書くとローカルで先に展開されるため、どちらで評価したいのかを意識して選びます。
WP-CLIやMySQLにSQLを渡す
SQLを複数行で渡す場合も、展開を防ぐためにクォートした区切り文字が向いています。wp db query は標準入力からSQLを読み込めます。
wp db query <<'SQL'
SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');
SQL
テーブル接頭辞が wp_ 以外のサイトでは書き換えてください。更新や削除を含むSQLを流す前には、wp db export でバックアップを取り、まずステージング環境で試すのが安全です。mysql コマンドを直接使う場合、パスワードをコマンドラインやスクリプト本文に書くと、プロセス一覧やシェル履歴から読み取られるおそれがあります。権限を絞ったオプションファイル(~/.my.cnf など)を使ってください。
<<- でインデントする
関数や if の中でヒアドキュメントを使うと、終端の EOF を行頭に置く必要があるため、インデントが崩れて見えます。<<-EOF と書くと、本文と終端行の行頭にあるタブ文字が取り除かれます。
取り除かれるのはタブだけで、空白によるインデントは取り除かれません。エディターが「タブを空白に変換する」設定になっていると、<<- を使っていても終端が見つからなくなります。チームでエディター設定がそろっていない場合は、<<- に頼らず、ヒアドキュメントの部分だけ行頭に寄せて書く方が安全です。
ヒアドキュメントを変数に入れる
コマンド置換で受け取るのが分かりやすい方法です。
message=$(cat <<'EOF'
バックアップが完了しました。
対象: /var/www/html
EOF
)
echo "$message"
コマンド置換は末尾の改行をすべて取り除く点に注意してください。read -r -d '' message <<'EOF' という書き方もありますが、この read は終端に達すると終了ステータス1を返すため、set -e のスクリプトではそこで止まります。使う場合は || true を付けます。
ヒアストリング(<<<)の使い方
ヒアストリングは、変数や短い文字列をそのまま標準入力に渡します。echo "$var" | コマンド と似ていますが、パイプを使わないので、read で読んだ値が現在のシェルに残ります。
conn="deploy@web01"
IFS=@ read -r user host <<< "$conn"
echo "$user / $host" # deploy / web01
bc <<< "scale=2; 10 / 3" # 3.33
list=$'web01\nweb02\ndb01'
while IFS= read -r h; do
echo "check: $h"
done <<< "$list"
echo "$list" | while read ... と書くと、ループはサブシェルで動くため、ループ内で変更した変数がループの外で失われます。ヒアストリングやプロセス置換を使えばこの問題を避けられます。
ヒアストリングは渡す文字列の末尾に改行を1つ付け加えます。そのため wc -l <<< "" は0ではなく1を返します。行数やバイト数を厳密に数える場合は、printf '%s' "$var" | wc -c のように改行を付けない方法を使います。
よくあるつまずき
「here-document ... delimited by end-of-file」の警告
warning: here-document at line N delimited by end-of-file (wanted 'EOF') のような警告は、終端の行が見つからないまま、ファイルの最後まで読んでしまったことを意味します。原因として多い順に確認してください。
- 終端の
EOFの前後に空白がある、またはインデントされている。cat -A スクリプト名で行末や行頭の見えない文字を確認する - Windows で編集したため改行コードが CRLF になっている。
cat -Aで行末に^Mが見えたら、sed -i 's/\r$//' スクリプト名などで LF に変換する <<-を使っているが、インデントがタブではなく空白になっている- 区切り文字の綴りが開始と終端で違う(大文字小文字も区別される)
/bin/sh で実行すると構文エラーになる
Debian や Ubuntu では /bin/sh が dash を指しているため、sh script.sh で実行すると <<< が使えず「redirection unexpected」などのエラーになります。shebang を #!/usr/bin/env bash にし、bash script.sh または実行権限を付けて ./script.sh で実行してください。
まとめ
- ヒアドキュメントは複数行、ヒアストリングは1つの文字列を標準入力に渡す
- 区切り文字をクォートすると展開されず、クォートしないと変数やコマンドが展開される。
$を含む設定ファイルの生成はクォートが基本 - 管理者権限で書き込むときは
sudo tee ファイル > /dev/nullを使う - ssh ではクォートの有無で「ローカルで評価するかリモートで評価するか」が決まる
- 終端が見つからない警告は、空白・CRLF・タブと空白の混同・綴り違いの順に確認する