原文(日本語訳)
Bash ツールが heredoc 内に ${index + 1} のような JavaScript テンプレートリテラルが含まれている場合に「Bad substitution」エラーをスローしなくなりました。これにより、以前はツール実行を中断していた問題が解消されました。
原文(英語)
Fixed: Bash tool no longer throws "Bad substitution" errors when heredocs contain JavaScript template literals like ${index + 1}, which previously interrupted tool execution
概要
Bash コマンドで heredoc を使用する際、JavaScript のテンプレートリテラル(${...} 構文)が含まれていると「Bad substitution」エラーが発生していた問題が修正されました。これにより、JavaScript/TypeScript コードを含むファイルを heredoc で作成・編集する際のエラーが解消されます。
問題の詳細
修正前の問題
JavaScript や TypeScript のコードを heredoc で記述する際、テンプレートリテラルが含まれていると Bash がそれを変数展開として解釈し、エラーが発生していました。
# 修正前はエラーが発生
cat > script.js << EOF
const items = ['a', 'b', 'c'];
items.forEach((item, index) => {
console.log(`Item ${index + 1}: ${item}`);
});
EOF
# エラー: Bad substitution
# ${index + 1} と ${item} が Bash の変数展開として解釈される修正後の動作
# 修正後は正常に動作
cat > script.js << EOF
const items = ['a', 'b', 'c'];
items.forEach((item, index) => {
console.log(`Item ${index + 1}: ${item}`);
});
EOF
# 正常にファイルが作成される基本的な使い方
Claude Code の Bash ツールを使って、JavaScript/TypeScript コードを含むファイルを作成できます。
claude
> JavaScript ファイルを作成してください。テンプレートリテラルを使用します。Claude は以下のような Bash コマンドを実行できるようになりました:
cat > example.js << 'EOF'
function renderList(items) {
return items.map((item, index) =>
`<li>${index + 1}. ${item}</li>`
).join('\n');
}
EOF実践例
React コンポーネントの作成
テンプレートリテラルを含む React コンポーネントを heredoc で作成:
claude
> React コンポーネントを作成してくださいcat > Button.tsx << 'EOF'
import React from 'react';
interface ButtonProps {
label: string;
count: number;
}
export const Button: React.FC<ButtonProps> = ({ label, count }) => {
return (
<button>
{`${label} (${count})`}
</button>
);
};
EOFNode.js スクリプトの生成
複雑な文字列操作を含むスクリプト:
claude
> ログフォーマッターのスクリプトを作成cat > logger.js << 'EOF'
class Logger {
log(level, message, metadata = {}) {
const timestamp = new Date().toISOString();
const meta = Object.entries(metadata)
.map(([key, value]) => `${key}=${value}`)
.join(' ');
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message} ${meta}`);
}
}
module.exports = new Logger();
EOFTypeScript の型定義ファイル作成
ジェネリック型とテンプレートリテラルを含む型定義:
cat > types.ts << 'EOF'
type EventName = 'click' | 'hover' | 'focus';
type EventHandler<T extends EventName> = (event: ${T}Event) => void;
interface Logger {
log(message: string): void;
warn(message: string): void;
error(message: string): void;
format(template: string, ...args: any[]): string;
}
const logger: Logger = {
log: (msg) => console.log(`[LOG] ${msg}`),
warn: (msg) => console.warn(`[WARN] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`),
format: (tpl, ...args) => {
return args.reduce((acc, arg, i) =>
acc.replace(`\${${i}}`, arg), tpl
);
}
};
EOFテストファイルの生成
Jest/Vitest テストコードの作成:
cat > example.test.ts << 'EOF'
describe('Calculator', () => {
it('should format results correctly', () => {
const testCases = [
{ input: [1, 2], expected: '1 + 2 = 3' },
{ input: [5, 3], expected: '5 + 3 = 8' },
];
testCases.forEach(({ input, expected }) => {
const [a, b] = input;
const result = `${a} + ${b} = ${a + b}`;
expect(result).toBe(expected);
});
});
});
EOFSQL クエリジェネレーターの作成
動的 SQL を生成する JavaScript コード:
cat > queryBuilder.js << 'EOF'
class QueryBuilder {
constructor(table) {
this.table = table;
}
select(columns) {
this.columns = columns;
return this;
}
where(conditions) {
this.whereClause = Object.entries(conditions)
.map(([key, value]) => `${key} = '${value}'`)
.join(' AND ');
return this;
}
build() {
const cols = this.columns.join(', ');
return `SELECT ${cols} FROM ${this.table} WHERE ${this.whereClause}`;
}
}
module.exports = QueryBuilder;
EOFHTML テンプレートの生成
テンプレートリテラルを使った HTML 生成コード:
cat > template.js << 'EOF'
function generateHTML(data) {
return `
<!DOCTYPE html>
<html>
<head>
<title>${data.title}</title>
</head>
<body>
<h1>${data.heading}</h1>
<ul>
${data.items.map((item, i) =>
`<li>${i + 1}. ${item}</li>`
).join('\n ')}
</ul>
</body>
</html>
`;
}
module.exports = { generateHTML };
EOFheredoc の引用符の重要性
この修正により、引用符なしの heredoc でも動作しますが、ベストプラクティスとして引用符付きの heredoc を使用することが推奨されます:
推奨される方法(引用符付き)
cat > file.js << 'EOF'
console.log(`Value: ${x}`);
EOF'EOF' のように引用符で囲むことで、Bash の変数展開を完全に無効化します。
引用符なし(修正後は動作するが推奨されない)
cat > file.js << EOF
console.log(`Value: ${x}`);
EOF注意点
- 引用符の使用: heredoc では
<< 'EOF'のように引用符を使用することで、意図しない変数展開を防げます - エスケープ: 引用符なしの heredoc を使用する場合、Bash 変数との競合に注意が必要です
- コード生成: Claude が JavaScript/TypeScript コードを生成する際、自動的に適切な heredoc 形式を選択します
- 既存のワークフロー: この修正により、以前エラーになっていたコマンドが正常に動作するようになります
- バックスラッシュエスケープ: 必要に応じて
\${...}のようにエスケープすることも可能です
トラブルシューティング
まだエラーが発生する場合
# Claude Code のバージョンを確認
claude --version
# 2.1.32 以降であることを確認
# heredoc に引用符を使用
cat > file.js << 'EOF'
# コンテンツ
EOF意図的に Bash 変数を展開したい場合
# 引用符なしの heredoc を使用し、Bash 変数のみを展開
NAME="World"
cat > greeting.js << EOF
console.log("Hello, ${NAME}"); // Bash 変数が展開される
console.log(\`Count: \${count}\`); // JavaScript テンプレートリテラルはエスケープ
EOFベストプラクティス
JavaScript/TypeScript ファイルの作成
常に引用符付き heredoc を使用:
cat > component.tsx << 'EOF'
export const Component = ({ name, count }) => (
<div>{`${name}: ${count}`}</div>
);
EOF複数ファイルの一括作成
# 複数の JavaScript ファイルを作成
for file in utils.js helpers.js constants.js; do
cat > "$file" << 'EOF'
export const format = (str, ...args) => {
return args.reduce((acc, arg, i) =>
acc.replace(`\${${i}}`, arg), str
);
};
EOF
done