一般的なBash構文エラーの解決:実践ガイド

引用符、括弧、変数、リダイレクト、コマンド検索の問題に関する例を用いて、一般的なBash構文エラーを修正します。

一般的なBash構文エラーの解決:実践ガイド

Bashの構文エラーは、たいてい小さなミスから発生します。引用符の欠落、括弧の誤り、リダイレクトの位置間違い、あるいは変数が予期しない値に展開されることなどです。Bashスクリプトが syntax error near unexpected token で停止した場合、まずBashが報告した行の1つ前を確認し、その後スクリプトを構文チェックにかけてください。

サーバー上で変更したスクリプトを実行する前に、以下のクイックチェックを使用してください。

bash -n script.sh

bash -n はコマンドを実行せずにファイルを解析します。すべての論理バグを捕捉できるわけではありませんが、多くの壊れた引用符、欠落した fi 文、および不正なループを捕捉します。

引用符の欠落

閉じられていない引用符は、パーサーを混乱させる最も早い原因の一つです。

name="deploy
printf 'Deploying %s\n' "$name"

Bashは閉じる " を探し続けるため、後続の行を読み続けます。引用符を閉じ、スペースを含む可能性のある変数展開を引用符で囲むことで修正します。

name="deploy"
printf 'Deploying %s\n' "$name"

変数を展開したい場合は二重引用符を使用し、リテラルテキストが必要な場合は一重引用符を使用します。

printf 'HOME stays literal: $HOME\n'
printf "HOME expands: %s\n" "$HOME"

不正な ifforwhile ブロック

すべての複合コマンドには、対応する終了キーワードが必要です。これが欠落すると、多くの場合、ファイルの終わり近くでエラーが報告されます。

if systemctl is-active --quiet nginx; then
  echo "nginx is running"
# missing fi

正しいバージョン:

if systemctl is-active --quiet nginx; then
  echo "nginx is running"
fi

同じパターンがループやcase文にも適用されます。

for host in web1 web2 web3; do
  ssh "$host" uptime
done

case "$env" in
  prod) echo "production" ;;
  dev) echo "development" ;;
  *) echo "unknown" ;;
esac

括弧とテストの間違い

[ コマンドは実際のコマンドであるため、引数の周りと閉じ括弧の前にスペースが必要です。

壊れた例:

if [$count -gt 5]; then
  echo "too many"
fi

修正後:

if [ "$count" -gt 5 ]; then
  echo "too many"
fi

Bash固有のスクリプトでは、[[ ... ]] は文字列テストにおいてより安全です。空の変数をより適切に処理し、パターンマッチングをサポートします。

if [[ "$file" == *.log ]]; then
  gzip "$file"
fi

数値には数値演算子を、テキストには文字列演算子を使用します。

[[ "$status" == "ready" ]]   # 文字列比較
[[ "$retries" -lt 3 ]]        # 数値比較

変数展開の問題

よくある間違いは、代入時に = の周りにスペースを入れることです。Bashはそれを変数代入ではなくコマンドとして扱います。

壊れた例:

backup_dir = /var/backups

修正後:

backup_dir=/var/backups

変数名が他のテキストに接する場合は、中括弧を使用します。

service="nginx"
log="/var/log/${service}.log"

中括弧がないと、Bashは意図したよりも長い変数名を読み取る可能性があります。

command not found エラー

command not found は必ずしも構文エラーではありません。通常、Bashがその名前の実行可能ファイルを見つけられなかったことを意味します。

まずタイプミスを確認します。

systemctl status nginx

次に、コマンドが存在し、PATH 内にあるかを確認します。

command -v systemctl
printf '%s\n' "$PATH"

スクリプトがcron、systemd、またはCIジョブで実行される場合、PATH はインタラクティブシェルよりも短い可能性があります。重要なコマンドには絶対パスを使用するか、スクリプトの先頭付近で PATH を設定します。

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH

リダイレクトとパイプのエラー

リダイレクトの順序は重要です。以下のコマンドは、stdoutを app.log に書き込み、その後stderrを同じ場所に送ります。

./deploy.sh >app.log 2>&1

以下のコマンドは異なります。stdoutがリダイレクトされる前に、stderrが古いstdoutにコピーされるためです。

./deploy.sh 2>&1 >app.log

パイプラインの場合、Bashは通常、最後のコマンドの終了コードを返します。パイプライン内の失敗したコマンドがパイプライン全体を失敗させる必要がある場合は、pipefail を使用します。

set -o pipefail
kubectl get pods | grep CrashLoopBackOff

実用的なデバッグフロー

まず構文チェックから始めます。

bash -n script.sh

構文が正しいが動作がおかしい場合は、トレース付きで実行します。

bash -x script.sh

より安全な本番スクリプトの場合は、厳格モードを意図的に有効にし、変更のたびにスクリプトをテストします。

set -euo pipefail

set -e は多くのコマンド失敗時に終了し、set -u は未設定の変数をエラーとして扱い、pipefail はパイプライン内の失敗を捕捉します。これらのオプションは便利ですが、スクリプトの動作を変更する可能性があるため、テストせずに古いスクリプトに追加するのではなく、意図的に有効にしてください。

まとめ

ほとんどのBash構文エラーは、引用符、終了キーワード、括弧のスペース、変数代入、リダイレクトをこの順序で確認することで簡単に解決できます。bash -nbash -x を通常のワークフローに組み込み、本番環境で実行されるのと同じシェルと環境でスクリプトをテストしてください。