Skip to content

原文(日本語訳)

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 がそれを変数展開として解釈し、エラーが発生していました。

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 の変数展開として解釈される

修正後の動作

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 コードを含むファイルを作成できます。

bash
claude
> JavaScript ファイルを作成してください。テンプレートリテラルを使用します。

Claude は以下のような Bash コマンドを実行できるようになりました:

bash
cat > example.js << 'EOF'
function renderList(items) {
  return items.map((item, index) =>
    `<li>${index + 1}. ${item}</li>`
  ).join('\n');
}
EOF

実践例

React コンポーネントの作成

テンプレートリテラルを含む React コンポーネントを heredoc で作成:

bash
claude
> React コンポーネントを作成してください
bash
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>
  );
};
EOF

Node.js スクリプトの生成

複雑な文字列操作を含むスクリプト:

bash
claude
> ログフォーマッターのスクリプトを作成
bash
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();
EOF

TypeScript の型定義ファイル作成

ジェネリック型とテンプレートリテラルを含む型定義:

bash
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 テストコードの作成:

bash
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);
    });
  });
});
EOF

SQL クエリジェネレーターの作成

動的 SQL を生成する JavaScript コード:

bash
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;
EOF

HTML テンプレートの生成

テンプレートリテラルを使った HTML 生成コード:

bash
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 };
EOF

heredoc の引用符の重要性

この修正により、引用符なしの heredoc でも動作しますが、ベストプラクティスとして引用符付きの heredoc を使用することが推奨されます:

推奨される方法(引用符付き)

bash
cat > file.js << 'EOF'
console.log(`Value: ${x}`);
EOF

'EOF' のように引用符で囲むことで、Bash の変数展開を完全に無効化します。

引用符なし(修正後は動作するが推奨されない)

bash
cat > file.js << EOF
console.log(`Value: ${x}`);
EOF

注意点

  • 引用符の使用: heredoc では << 'EOF' のように引用符を使用することで、意図しない変数展開を防げます
  • エスケープ: 引用符なしの heredoc を使用する場合、Bash 変数との競合に注意が必要です
  • コード生成: Claude が JavaScript/TypeScript コードを生成する際、自動的に適切な heredoc 形式を選択します
  • 既存のワークフロー: この修正により、以前エラーになっていたコマンドが正常に動作するようになります
  • バックスラッシュエスケープ: 必要に応じて \${...} のようにエスケープすることも可能です

トラブルシューティング

まだエラーが発生する場合

bash
# Claude Code のバージョンを確認
claude --version
# 2.1.32 以降であることを確認

# heredoc に引用符を使用
cat > file.js << 'EOF'
# コンテンツ
EOF

意図的に Bash 変数を展開したい場合

bash
# 引用符なしの heredoc を使用し、Bash 変数のみを展開
NAME="World"
cat > greeting.js << EOF
console.log("Hello, ${NAME}");  // Bash 変数が展開される
console.log(\`Count: \${count}\`);  // JavaScript テンプレートリテラルはエスケープ
EOF

ベストプラクティス

JavaScript/TypeScript ファイルの作成

常に引用符付き heredoc を使用:

bash
cat > component.tsx << 'EOF'
export const Component = ({ name, count }) => (
  <div>{`${name}: ${count}`}</div>
);
EOF

複数ファイルの一括作成

bash
# 複数の 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

関連情報