CLAUDE LABEN
PRICING — 本日9月1日は Sonnet 5 の値上げ予定日でしたが、引き上げは実施されませんでした。導入価格の $2/$10 per MTok がそのまま標準価格として確定していますPARTNER — Salesforce と Anthropic が拡張提携「Claudeforce」を発表しました。Salesforce in Claude プラグインは商談準備やパイプライン管理など37のセールススキルを同梱しますTRUST — Claudeforce の推論は Amazon Bedrock 経由で Salesforce Trust Boundary の内側に留まります。データが境界の外へ出ない構成が、規制業種への回答になっていますBETA — Salesforce in Claude は現在は選抜パイロット顧客向けです。9月中のオープンベータ移行が予告されていますLIMITS — 週次上限の50%増は9月13日までです。9月14日からはプロモ前比+25%が恒久水準になります。今日の水準と比べると約17%の削減にあたりますRELEASE — Claude Code は v2.1.251(8月28日)から新しいリリースが出ていません。約0.8日に1本というペースからすると、4日の間隔は最長クラスですPRICING — 本日9月1日は Sonnet 5 の値上げ予定日でしたが、引き上げは実施されませんでした。導入価格の $2/$10 per MTok がそのまま標準価格として確定していますPARTNER — Salesforce と Anthropic が拡張提携「Claudeforce」を発表しました。Salesforce in Claude プラグインは商談準備やパイプライン管理など37のセールススキルを同梱しますTRUST — Claudeforce の推論は Amazon Bedrock 経由で Salesforce Trust Boundary の内側に留まります。データが境界の外へ出ない構成が、規制業種への回答になっていますBETA — Salesforce in Claude は現在は選抜パイロット顧客向けです。9月中のオープンベータ移行が予告されていますLIMITS — 週次上限の50%増は9月13日までです。9月14日からはプロモ前比+25%が恒久水準になります。今日の水準と比べると約17%の削減にあたりますRELEASE — Claude Code は v2.1.251(8月28日)から新しいリリースが出ていません。約0.8日に1本というペースからすると、4日の間隔は最長クラスです
記事一覧/Claude Code
Claude Code/2026-09-01中級

heredoc のクォートを1つ落とすと、ログに書いた金額や日付が別物になります

未クォートの heredoc は本文の変数とバックティックを実行します。実測した4通りの書き換わり方、3種類のクォート記法の比較、プレースホルダと sed で値を安全に埋める手順、既存スクリプトを棚卸しする検出コードまでをまとめました。

Claude Code244シェルスクリプト2heredoc自動化84トラブルシューティング42

実行ログを開いたら、書いたはずの「コスト: $2 / MTok」が「コスト: / MTok」になっていました。金額だけがきれいに消えています。

隣の行では、記録用に貼っておいたコマンドの文字列が、そのコマンドの実行結果に置き換わっていました。さらに数えてみると、7行書いたはずのログが6行しかありません。

犯人はスクリプトのどこにもいませんでした。強いて言えば、cat << EOFEOF にクォートが付いていない、その一点です。

直し方だけ先に書きます

区切り語をクォートで囲みます。差分はこれだけです。

# 変更前
cat << EOF > log.txt
コスト: $2 / MTok
EOF
 
# 変更後
cat << 'EOF' > log.txt
コスト: $2 / MTok
EOF

区切り語(この例では EOF)にクォートが付いていない heredoc は、本文をシェルの展開対象として扱います。付いていれば、本文は1文字も解釈されずにそのまま渡ります。

急いでいる方はここで戻っていただいて大丈夫です。以下は、何がどう書き換わるのかを実際に測った記録と、値を埋めたい場合の書き方、既存スクリプトの棚卸し手順です。

未クォートの heredoc が、実際に何を書き換えたか

7行のログを書くつもりで、次のスクリプトを走らせました。DATE にはあらかじめ 2026-09-01 を入れてあります。

DATE="2026-09-01"
cat << EOF > a2.txt
日付: $DATE
生成コマンド: `date -u +%Y-%m-%dT%H:%M`
バックアップ先: /var/backup/$USER/
コスト: $2 / MTok
正規表現: ^\d{4}-\d{2}$
継続行: 末尾のバックスラッシュ \
次の行
EOF

出力された a2.txt は次のとおりでした。

日付: 2026-09-01
生成コマンド: 2026-09-01T06:06
バックアップ先: /var/backup/masaki/
コスト:  / MTok
正規表現: ^\d{4}-\d{2}$
継続行: 末尾のバックスラッシュ 次の行

wc -l は 6 を返しました。7行のつもりが6行です。書いた内容と出た内容を並べます。

書いた内容出力された内容起きたこと
日付: $DATE日付: 2026-09-01変数が展開された(意図どおり)
生成コマンド: バックティックで囲んだ date生成コマンド: 2026-09-01T06:06コマンドが実行され、結果に置換された
/var/backup/$USER//var/backup/masaki/環境変数が展開された
コスト: $2 / MTokコスト: / MTok位置パラメータ $2 として解釈され、空文字になった
正規表現: ^\d{4}-\d{2}$正規表現: ^\d{4}-\d{2}$そのまま残った(後述)
末尾がバックスラッシュの行+次の行1行に連結行継続として食われ、行数が1減った

個人開発で書いたスクリプトを自分でレビューしている身としては、一番怖いのは4行目です。$2 は「2ドル」ではなく「2番目の引数」として読まれ、引数がないので空文字に置き換わりました。エラーは出ません。料金の記録が静かに空欄になるだけです。私が最初に気づいたのもここでした。

2行目も性質が悪いところです。「後で見返すためにコマンドを控えておく」という、ログでは一番よくある書き方が、そのままコマンドの実行になります。書いた覚えのない値が入っているのに、スクリプトを読み返しても実行している箇所が見つかりません。

一方で5行目の \d や末尾の $ は無傷でした。未クォート heredoc でバックスラッシュが効くのは $・バックティック・バックスラッシュ・改行の直前だけで、それ以外は素通りします。「一部は壊れて一部は残る」ので、目視レビューでは見落とします。

クォートの書き方は3通りあり、結果はすべて同じでした

$HOME とバックティックを1行に入れた本文で、区切り語の書き方だけを変えて比べました。

書き方出力
<< EOF/home/masaki|RUN(展開・実行される)
<< 'EOF'そのまま(展開されない)
<< "EOF"そのまま(展開されない)
<< \EOFそのまま(展開されない)

シングルクォート・ダブルクォート・バックスラッシュのどれでも結果は同じです。区切り語の一部でもクォートされていれば、本文全体がリテラルになります。

ここは通常のシェルの感覚と食い違うところです。ダブルクォートは普段なら変数を展開しますが、heredoc の区切り語に限っては「クォートされているか否か」だけが判定されます。私はこの記法を 'EOF' に統一しています。読み手が「ここは展開されるのか」と一瞬でも考えなくて済むからです。既存コードで "EOF" を見かけても直す必要はありません。

<<- を使うとタブによる字下げを剥がせます。剥がれるのはタブだけで、スペースは残りました。エディタがタブをスペースに変換する設定になっていると、この機能は静かに無効になります。

値を埋めたいときは、プレースホルダを置いて後から差し込む

「変数を展開したいから未クォートにしている」場合もあります。ただ、埋めたい値が2つ3つあるだけなら、本文をリテラルに保ったまま後から差し込むほうが安全です。

DATE="2026-09-01"; SLUG="my-article"
 
cat << 'EOF' > tmpl.txt
日付: __DATE__ / slug: __SLUG__
コスト: $2 / MTok
EOF
 
sed -e "s/__DATE__/${DATE}/" -e "s/__SLUG__/${SLUG}/" tmpl.txt

出力は次のとおりです。

日付: 2026-09-01 / slug: my-article
コスト: $2 / MTok

埋めたい2つだけが埋まり、$2 は文字として残りました。本文に何が入っていても壊れないので、ログのように「中身が事前に読めないテキスト」を書き出すときはこの形にしています。

ただし、この方法には二次災害があります。差し込む値にスラッシュが含まれていると sed が転びます。

P="/var/log/app"
sed "s/__P__/${P}/" <<< "path: __P__"
# => sed: -e expression #1, char 10: unknown option to `s'

s コマンドの区切り文字は自由に選べるので、パスを埋めるときは | などに変えます。

sed "s|__P__|${P}|" <<< "path: __P__"
# => path: /var/log/app

パスを埋める用途では区切りを | に固定しておくのが無難です。値の側に | が来る可能性まで含めて考えるなら、sed ではなく python3awk に渡したほうが確実です。

手元のスクリプトを棚卸しする

一度でも踏んだなら、他にも同じ書き方が残っているはずです。未クォートの heredoc のうち、本文に展開対象を含むものだけを拾う小さなスクリプトを書きました。

import re, sys, pathlib
 
OPEN = re.compile(r"<<-?\s*(?P<q>['\"\\])?(?P<w>[A-Za-z_][A-Za-z0-9_]*)")
RISK = re.compile(r"`|\$\(|\$[A-Za-z_{0-9]|\\$")
 
def scan(path):
    lines = pathlib.Path(path).read_text(errors="replace").splitlines()
    hits, i = [], 0
    while i < len(lines):
        m = OPEN.search(lines[i])
        if m and m.group("q") is None:
            word, start = m.group("w"), i
            body = []
            i += 1
            while i < len(lines) and lines[i].strip() != word:
                body.append(lines[i]); i += 1
            risky = [b for b in body if RISK.search(b)]
            if risky:
                hits.append((start + 1, word, len(body), len(risky)))
        i += 1
    return hits
 
total = 0
for p in sys.argv[1:]:
    for ln, w, n, r in scan(p):
        total += 1
        print(f"{p}:{ln}: 未クォート heredoc({n}行中 {r}行に展開対象)")
print(f"検出: {total} 件")
sys.exit(1 if total else 0)

区切り語がクォートされているものは最初から除外し、本文にバックティック・$($変数・末尾バックスラッシュのいずれも含まないものも落とします。展開対象がなければ、未クォートでも実害がないためです。

このスクリプトを、手元の Linux 環境にあるシェルスクリプト272本(/usr/bin/usr/sbin/etc 配下)に当ててみました。

項目件数
走査したシェルスクリプト272 本
heredoc を含むファイル60 本
検出された未クォート heredoc138 箇所(40 ファイル)
クォート済みの heredoc9 箇所

ここで正直に書いておきたいのは、138箇所の大半はバグではないということです。設定ファイルを生成するスクリプトは変数を展開したくて未クォートにしているので、検出されて当然です。このスクリプトが出すのはバグ一覧ではなく、目を通す価値のあるレビュー対象の一覧です。

それでも役には立ちます。行番号と「本文の何行に展開対象があるか」が並ぶので、設定生成なのかログ出力なのかは開いた瞬間に判断できます。私は自動実行するスクリプトを更新するとき、この出力を一度眺めてから push するようにしました。

同じ「エラーが出ないまま結果だけが変わる」種類の落とし穴は他にもあって、settings.json のキー名を1文字間違えても、Claude Code は何も言わずに無視します で扱った設定キーの誤りも同じ構図でした。シェルの引用符が絡む失敗をもう一段深く追いたい方には、空白を含むパスで一括処理が静かに0件になる過程を測った空白ひとつで、80件の検査が0件になるが近いところにあります。

なぜ AI に書かせると未クォートで返ってくるのか

今回の元のスクリプトは、私が Claude Code に「ログを書き出す処理を足して」と頼んで返ってきたものでした。返答は素直な cat << EOF です。

理由は測った数字がそのまま示していると考えています。272本のスクリプトに含まれていたクォート済みの heredoc は9箇所、未クォートは138箇所でした。世の中のシェルスクリプトにおける多数派は未クォートで、学習した側がその比率を反映するのは自然なことです。人間が書いても同じ比率になるはずで、これはモデルの欠点というより、シェルの既定値が「展開する」側に倒れていることの帰結です。

なので、頼み方を一言変えるだけで結果が変わります。

ログを書き出す処理を足してください。
heredoc は必ず <<'EOF' の形(区切り語をクォート)で書き、
値を埋める必要がある箇所は __PLACEHOLDER__ を置いて sed で差し込んでください。

この一文を入れてからは、生成されるスクリプトの形が安定しました。プロジェクトの設定ファイルやスキルの手順書に書いておけば、毎回頼まなくても済みます。

今日できる一手は、cat << EOF で始まる行を手元のスクリプトから検索して、ログやメッセージを書き出しているものだけ 'EOF' に変えることです。設定生成のものは触らなくて構いません。それだけで、金額や日付が静かに書き換わる経路はひとつ閉じます。

私自身、この挙動は何度か読んだことがあったはずなのに、実際にログが1行短くなるまで自分の問題として認識できていませんでした。同じ場所で手を止めた方の役に立てば嬉しいです。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-08-25
settings.json のキー名を1文字間違えても、Claude Code は何も言わずに無視します
キー名を書き間違えた settings.json と正しい settings.json で claude doctor の出力を比べたところ、差分はゼロでした。何が検証され何が素通りするかを5通りで測り、書き間違いを自分で落とす検査を用意します。
Claude Code2026-08-22
長い処理を別セッションに任せて待つ — 通知が届かなかったときのための完了マーカー
Claude Code の notify_when_idle で別セッションの完了通知を受け取る手順と、通知が落ちても待ち続けないための完了マーカーの最小実装。3つの状態切り分けを実行結果つきで解説します。
Claude Code2026-07-01
Claude Code をアップデートしたら hook が発火しなくなった — ハイフン入り matcher の厳密一致化(v2.1.195)
Claude Code v2.1.195 でハイフンを含む hook matcher が部分一致から厳密一致に変わり、既存の PreToolUse フックが静かに発火しなくなりました。原因の切り分けと、壊れない matcher の書き方をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →