Linuxにおけるシェルスクリプトでの文字列比較の方法と応用例

Linuxにおけるシェルスクリプトでの文字列比較の方法と応用例を安全に進める結論は「単純な一致は引用した変数をtestまたは[で比較し、パターン判定はBashの[[またはcaseへ役割分担するのが安全です。数値比較の-eqと文字列比較の=を混同せず、未設定と空文字も区別します。」です。Linux上のBash 5系を基準にし、/bin/sh互換スクリプトではBash固有の[[を使わないという前提を外すと同じ入力でも結果が変わります。比較式を変更処理へ接続する前に、read-onlyのfixtureで真偽とstatusを固定します。

目次

シェル種別と比較値を可視化する

シェルの文字列比較では、空文字、空白を含む値、ワイルドカード文字を別々のテストケースとして用意します。POSIX shのtestとBashの二重角括弧では解釈が異なるため、対象シェルを確定し、変数を引用した一致・不一致の両方で終了ステータスを確認してから分岐へ組み込みます。

  • 実行シェルを readlink /proc/$$/exe と ps -p $$ -o comm= で確認する
  • bash –version とスクリプト先頭のshebangを記録する
  • 比較対象をprintfの%qで可視化し、末尾空白や改行を疑う
  • 未設定を許す変数か、空文字をエラーにする変数かを先に決める

testと二重角括弧で変わるquoteの意味

[[ の右辺を引用しない == 比較はパターン照合になります。リテラルのアスタリスクとワイルドカードを区別したい場合は右辺を引用します。正規表現演算子=~では、式全体を引用すると意図した照合にならない場合があるため、正規表現を変数へ入れて検証すると読みやすくなります。終了ステータス0が真、非0が偽というシェルの規則も、一般的な言語の真偽値と逆に見えやすい点です。

完全一致・未設定・patternを選び分ける

POSIX寄りの完全一致

left='prod'
right='prod'
if [ "$left" = "$right" ]; then
  printf '%s\n' '一致'
fi

[ は最後の ] まで含めてコマンドです。空白を省略せず、展開後に空や空白入りでも一つの引数になるよう両辺を二重引用符で囲みます。

Bashで空文字と未設定を切り分ける

if [[ -v APP_MODE ]]; then
  if [[ -z $APP_MODE ]]; then
    printf '%s\n' '設定済みだが空'
  else
    printf 'APP_MODE=%q\n' "$APP_MODE"
  fi
else
  printf '%s\n' '未設定'
fi

-vは変数が設定されたか、-zは長さが0かを確認します。秘密値を扱う変数はprintfで内容を出さず、存在と長さだけを記録してください。

候補が複数あるときはcase

case ${APP_MODE-} in
  dev|test) printf '%s\n' '非本番' ;;
  prod)     printf '%s\n' '本番' ;;
  '')       printf '%s\n' '未指定' ;;
  *)        printf '%s\n' '未知の値' >&2; exit 2 ;;
esac

caseは上から最初に一致した節を実行します。許可リストとして書けば、想定外の値を黙って本番扱いする事故を避けられます。

文字列判定で起きる五つの誤り

  • =と==の可搬性を混同する
  • [ の括弧前後の空白を欠く
  • 数値に文字列演算子を使い10と010を同一視する
  • ロケール依存の辞書順を業務判定に使う
  • set -u環境で未設定変数を直接展開する

分岐先が変更処理ならdry runから始める

比較処理自体は読み取りですが、判定後の分岐が削除、配備、サービス切替につながる場合は影響が大きくなります。最初は分岐内をprintfだけにしてテストケース表を通し、期待した節へ入ることを確認してから変更処理へ置き換えます。変更前の設定ファイルと実行ログを別名保存し、誤判定時は旧ファイルを戻して同じ入力で再試験します。認証用の平文パスワードをreadで比較する例は、履歴・画面・プロセス周辺へ露出するので採用しません。

空白とwildcardを含む回帰sample

shellcheck等の静的解析だけでなく、空、空白入り、ワイルドカード文字、改行を含む値、未設定の5種類を一時スクリプトで実行します。実運用の変更処理へ接続する前に、終了コードをecho $?ではなく if コマンド; then の形で直接評価し、テスト終了後は一時ファイルだけを削除します。

文字列比較の合否を終了statusで確定する

文字列比較は、POSIXのtest/[なら完全一致を=で評価し、Bashの[[では==右辺の引用有無でliteralとpatternを分けます。判定コマンドは真で終了status 0、偽で1を返すため、画面出力ではなくifの分岐で結果を受けます。

POSIXとBashを同じfixtureで比べる

for v in prod '' 'prod*'; do
  if [ "$v" = 'prod' ]; then printf '%s\n' "MATCH:$v"; else printf '%s\n' "NO_MATCH:$v"; fi
done

一致・不一致・構文errorを切り分ける

  • 完全一致:quoted valueが一致しifのthen節へ入り、status 0になる
  • 未設定・不一致:不一致または未設定を許容するcaseの既定節へ入り、status 1相当として扱う
  • shell構文error:構文誤り、未閉じquote、利用shell違いはstatus 2以上やstderrを伴うため判定不一致と分離する

未設定と空文字を同一にしたくない場合は[[ -v name ]]を先に評価します。/bin/shで[[を実行したり、[の引数を引用しなかったりすると、比較以前にcommand not foundや引数数errorになります。

未設定と空文字を別caseにする

bash -c 'unset APP_MODE; [[ -v APP_MODE ]]; printf "unset=%s\n" "$?"; APP_MODE=; [[ -v APP_MODE && -z $APP_MODE ]]; printf "empty=%s\n" "$?"'

prod、空文字、asteriskを含むliteral、未設定を別caseとして実行し、MATCH/NO_MATCHとstatusの対応を保存します。shellcheckの指摘だけで合否を決めず、実際に使うshebangのshellで再実行します。

運用へ組み込む前に、実際のshebangで空文字・空白・asteriskを再実行します。判定後の処理はfixtureが全件通るまでprintfのままにし、終了statusと選ばれたcaseを一緒に保存してください。

未設定・空文字・wildcardをfixtureで固定する

文字列比較のreviewでは、同じ変数に未設定、空文字、prod、prod*、改行を含む値を順に与え、[ “$v” = ‘prod’ ]と[[ $v == ‘prod’ ]]の差を実行結果で残します。POSIX testでは展開後の引数数が変わらないよう変数を引用し、Bashの[[ ]]では右辺をliteralとして扱うのかpatternとして扱うのかを明記します。set -uを使う場合は${name-}または[[ -v name ]]で未設定参照を避けます。

合格条件は完全一致だけがstatus 0、空文字・未設定・wildcard文字を含むliteralはstatus 1、syntaxや実行shellの不一致はstatus 2以上として分離できることです。fixtureはcommand substitutionの外側で直後の$?を保存し、printfで入力値、shell、分岐名、statusを同じ行へ出します。locale依存の順序比較やregexが必要な要件は=へ押し込まず、case、=~、sortなど別の操作としてtestします。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次