- 技術 💻
シェルスクリプトcase文の書き方|複数パターン・ワイルドカード早見表
シェルスクリプトのcase文とは、1つの文字列を上から順にパターンと照らし合わせ、最初に一致した節の処理だけを実行する分岐の構文です。 case 値 in パターン) 処理 ;; esac の形で書き、パターンには | による複数指定や、*・?・[a-z] といったワイルドカードが使えます。
この記事では、パターンの書き方を早見表にまとめ、すべての例を bash 5.2.21 で動かした実行結果つきで紹介します。bash 4 以降で使える ;& と ;;& の動き、同じ処理を if と case で並べた比較と選び方の基準、そのまま貼って使えるテンプレート4種(y/n 判定・拡張子別処理・start/stop 引数・オプション解析)、そしてパターンの順番や引用符でつまずきやすい箇所まで、1ページで確認できます。
case文の早見表
まず基本の形です。esac は case を逆から書いたもので、ここで分岐が終わります。
case "$変数" in
パターン1) 処理1 ;;
パターン2|パターン3) 処理2 ;;
*) どれにも当てはまらないときの処理 ;;
esac
パターンの書き方
| 書き方 | 意味 | 例 | 一致する値 |
|---|---|---|---|
abc |
その文字列そのもの | start) |
start |
a|b |
a または b(複数パターン) | start|begin) |
start、begin |
* |
0文字以上の任意の文字列 | *.log) |
app.log、.log |
? |
任意の1文字 | ?) |
x、5 |
[abc] |
かっこ内のどれか1文字 | [yY]) |
y、Y |
[a-z] |
範囲内の1文字 | [a-z]*) |
abc、x1 |
[!0-9] |
かっこ内「以外」の1文字 | [!0-9]*) |
Hello、-5 |
[[:upper:]] |
文字クラス(大文字1文字) | [[:digit:]]) |
5 |
"" |
空文字 | "") |
引数なし、未入力 |
"*" |
引用符つきは文字どおり | "*") |
アスタリスク1文字だけ |
*) を最後に置く |
どれにも当てはまらない場合 | *) |
すべて |
節の終わりの記号
| 記号 | 動き | 使える環境 |
|---|---|---|
;; |
その節で case を抜ける(基本はこれ) | bash、sh(dash)など |
;& |
次の節の処理を、パターンを見ずに続けて実行する | bash 4 以降 |
;;& |
次以降のパターンも引き続き判定する | bash 4 以降 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「シェルスクリプトcase文の書き方|複数パターン・ワイルドカード早見表」
この記事の確認環境
| 項目 | 内容 |
|---|---|
| シェル | GNU bash 5.2.21 |
| 比較用の sh | dash 0.5.12(Ubuntu 系で /bin/sh が指すシェル) |
| 確認方法 | 記事内のスクリプトをファイルに保存し、bash ファイル名 引数 で実行 |
| 仕様の参照元 | bash 5.2 に付属するヘルプ(help case)とマニュアル(man bash) |
help case では、case 文は次のように説明されています。
case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
Execute commands based on pattern matching.
Selectively execute COMMANDS based upon WORD matching PATTERN. The
`|' is used to separate multiple patterns.
macOS に最初から入っている bash 3.2 系での動作は今回は未実行です。;& と ;;& は bash 4.0 で追加されたことが一般に知られているため、bash 3.2 では使えないものとして読み進めてください。
case文の基本の書き方
case 文は「値」「パターン」「処理」「終わりの記号」の4つでできています。
#!/usr/bin/env bash
# os.sh : OS の種類で処理を分ける
case "$(uname -s)" in
Linux) echo "Linux です" ;;
Darwin) echo "macOS です" ;;
MINGW*|MSYS*|CYGWIN*) echo "Windows 上の bash です" ;;
*) echo "未対応: $(uname -s)" ;;
esac
実行結果(Linux 上で実行):
$ bash os.sh
Linux です
押さえておきたい決まりは次の5つです。
| 決まり | 説明 |
|---|---|
| 上から順に判定する | 最初に一致した節だけが実行され、;; で case を抜けます |
パターンは ) で閉じる |
(a|b) のように開きかっこを付けても動きます(確認済み) |
| 処理は複数行でよい | ;; が来るまでがその節の処理です |
| どれにも一致しないときは何もしない | エラーにはならず、終了コードは 0 です(確認済み) |
最後は esac |
書き忘れると構文エラーになります |
どれにも一致しない場合の動きは、次のスクリプトで確かめました。
case "$1" in
a) echo A ;;
esac
echo "rc=$?"
$ bash nm.sh z
rc=0
何も表示されず、終了コードは 0 のままです。「想定外の値が来たことに気づきたい」なら、最後に *) の節を必ず置きます。
パターンの種類を実行結果で確かめる
早見表のパターンを1つのスクリプトにまとめ、値を変えながら呼び出しました。
#!/usr/bin/env bash
# pat.sh : パターンの種類を確かめる
check() {
case "$1" in
start|begin) echo "$1 -> A: start か begin" ;;
*.log) echo "$1 -> B: .log で終わる" ;;
?) echo "$1 -> C: 1文字" ;;
[a-z]*) echo "$1 -> D: 小文字で始まる" ;;
[!0-9]*) echo "$1 -> E: 数字以外で始まる" ;;
"") echo "(空) -> F: 空文字" ;;
*) echo "$1 -> G: どれにも当てはまらない" ;;
esac
}
for v in start begin app.log x abc Hello 123 ""; do
check "$v"
done
実行結果:
$ bash pat.sh
start -> A: start か begin
begin -> A: start か begin
app.log -> B: .log で終わる
x -> C: 1文字
abc -> D: 小文字で始まる
Hello -> E: 数字以外で始まる
123 -> G: どれにも当てはまらない
(空) -> F: 空文字
読み取れるポイントを順に見ていきます。
複数パターンを縦棒で区切る
start|begin) のように | で区切ると「どれかに一致すれば実行」 になります。if 文で || をいくつも並べる代わりに、1行で書けるのが case 文の大きな利点です。区切りの前後に空白を入れても動きます。
ワイルドカードで前方一致・後方一致・部分一致を書く
| やりたいこと | パターン | 例 |
|---|---|---|
| 前方一致(〜で始まる) | abc* |
--output=*) |
| 後方一致(〜で終わる) | *abc |
*.log) |
| 部分一致(〜を含む) | *abc* |
*error*) |
| 文字数を指定する | ? を並べる |
???) は3文字 |
| 指定文字数以上 | ? のあとに * |
????????*) は8文字以上 |
case 文の * は「/」や先頭の「.」も含めてどんな文字にも一致します。 ファイル名の展開(グロブ)と記号は同じですが、case 文では実在するファイルを探しに行くことはなく、文字列どうしを比べるだけです。
文字クラスで1文字の種類をしぼる
[...] は「かっこの中のどれか1文字」です。先頭に ! を付けると「それ以外」になります。上の結果で abc は D([a-z]*)に、Hello は E([!0-9]*)に進みました。x は C と D の両方に当てはまりますが、先に書いた C で確定しています。
範囲指定は、文字の並び順の設定(ロケール)によって大文字小文字の扱いが変わることがあります。今回の環境(ロケール POSIX)では [a-z] に大文字の B を渡すと一致しませんでした。環境の違いを気にせず書きたいときは、[[:lower:]] のような名前つきの文字クラスが安心です。
case "$1" in
[[:lower:]]) echo lower ;;
[[:upper:]]) echo upper ;;
[[:digit:]]) echo digit ;;
[[:space:]]) echo space ;;
esac
$ bash cls.sh B
upper
$ bash cls.sh 5
digit
$ bash cls.sh ' '
space
| 文字クラス | 一致する文字 |
|---|---|
[[:digit:]] |
数字 |
[[:lower:]] |
小文字 |
[[:upper:]] |
大文字 |
[[:alpha:]] |
英字 |
[[:alnum:]] |
英数字 |
[[:space:]] |
空白・タブなど |
数字かどうかを判定する
「数字だけでできているか」は、「数字以外を1文字でも含むか」を先に調べると簡潔に書けます。
n="$1"
case "$n" in
''|*[!0-9]*) echo "$n: 数字ではない" ;;
[0-9]) echo "$n: 1桁" ;;
[1-9][0-9]) echo "$n: 2桁" ;;
*) echo "$n: 3桁以上" ;;
esac
$ bash num.sh 7
7: 1桁
$ bash num.sh 85
85: 2桁
$ bash num.sh 120
120: 3桁以上
$ bash num.sh 12a
12a: 数字ではない
case 文が見るのは文字の並びです。「80 以上」のような大小の比較は if 文の担当なので、数字かどうかを case で確かめ、大小は if で比べる、と役割を分けると読みやすくなります。
bash 4以降の ;& と ;;& の動き
;; の代わりに ;& や ;;& を書くと、一致したあとの進み方を変えられます。同じ値 ab を3種類の書き方に通して比べました。
#!/usr/bin/env bash
# fall.sh : ;; と ;& と ;;& の違い
echo "--- ;; (通常) ---"
case "$1" in
a*) echo "1: a で始まる" ;;
*b) echo "2: b で終わる" ;;
*) echo "3: その他" ;;
esac
echo "--- ;& (次の節を無条件に実行) ---"
case "$1" in
a*) echo "1: a で始まる" ;&
*z) echo "2: z で終わる(判定なしで実行)" ;;
*) echo "3: その他" ;;
esac
echo "--- ;;& (次以降のパターンも判定) ---"
case "$1" in
a*) echo "1: a で始まる" ;;&
*b) echo "2: b で終わる" ;;&
*z) echo "3: z で終わる" ;;&
*) echo "4: その他" ;;
esac
実行結果:
$ bash fall.sh ab
--- ;; (通常) ---
1: a で始まる
--- ;& (次の節を無条件に実行) ---
1: a で始まる
2: z で終わる(判定なしで実行)
--- ;;& (次以降のパターンも判定) ---
1: a で始まる
2: b で終わる
4: その他
| 記号 | ab を渡したときの動き |
|---|---|
;; |
1 だけ実行して終了。ab は b で終わりますが、2 は見に行きません |
;& |
1 のあと、パターンを確かめずに 2 の処理も実行します(ab は z で終わっていないのに実行される) |
;;& |
1 のあとも判定を続け、一致した 2 と 4 を実行。一致しない 3 は飛ばします |
上位の設定が下位の処理も含む形を書く
;& は、「レベル3ならレベル2と1の処理も行う」 という積み上げ型の処理に向いています。
#!/usr/bin/env bash
# level.sh : ;& で「上位は下位の処理も行う」を書く
case "$1" in
3) echo "詳細ログを出す" ;&
2) echo "通常ログを出す" ;&
1) echo "エラーログを出す" ;;
*) echo "使い方: $0 {1|2|3}" >&2; exit 1 ;;
esac
$ bash level.sh 1
エラーログを出す
$ bash level.sh 2
通常ログを出す
エラーログを出す
$ bash level.sh 3
詳細ログを出す
通常ログを出す
エラーログを出す
最後の節の手前(レベル1)は ;; で止めている点がポイントです。ここも ;& にすると、使い方の表示まで進んでしまいます。
当てはまる性質をすべて拾う
;;& は、1つの値が複数の条件に当てはまるかを順に調べたいときに使います。
#!/usr/bin/env bash
# kind.sh : ;;& で当てはまる性質をすべて表示する
case "$1" in
*[0-9]*) echo "数字を含む" ;;&
*[[:lower:]]*) echo "小文字を含む" ;;&
*[[:upper:]]*) echo "大文字を含む" ;;&
????????*) echo "8文字以上" ;;
esac
$ bash kind.sh Abc12345
数字を含む
小文字を含む
大文字を含む
8文字以上
$ bash kind.sh abc
小文字を含む
sh で動かすとどうなるか
同じ fall.sh を dash(sh)で動かすと、;& の行で構文エラーになりました。
$ sh fall.sh ab
--- ;; (通常) ---
1: a で始まる
--- ;& (次の節を無条件に実行) ---
fall.sh: 11: Syntax error: "&" unexpected
;& と ;;& を使うスクリプトは、1行目を #!/usr/bin/env bash にして bash で動かすと決めておくと迷いません。#!/bin/sh で配るスクリプトでは ;; だけで組み立てます。
ifとcaseの違いと使い分け
同じ「拡張子で種類を分ける」処理を、if と case の両方で書いて並べました。
#!/usr/bin/env bash
ext="${1##*.}"
# if で書いた場合
if [ "$ext" = "jpg" ] || [ "$ext" = "jpeg" ] || [ "$ext" = "png" ]; then
echo "if : 画像"
elif [ "$ext" = "txt" ] || [ "$ext" = "md" ]; then
echo "if : テキスト"
else
echo "if : その他"
fi
# case で書いた場合
case "$ext" in
jpg|jpeg|png) echo "case: 画像" ;;
txt|md) echo "case: テキスト" ;;
*) echo "case: その他" ;;
esac
実行結果:
$ bash ifcase.sh photo.jpeg
if : 画像
case: 画像
$ bash ifcase.sh memo.md
if : テキスト
case: テキスト
$ bash ifcase.sh a.zip
if : その他
case: その他
結果は同じですが、case のほうは変数名を1回書くだけで済み、選択肢を足すときも | でつなぐだけです。一方で、if には case にできない比較があります。
どちらを選ぶかの基準
| 判定したい内容 | 向いている構文 | 理由 |
|---|---|---|
| 1つの値を、3つ以上の候補と比べる | case | 候補を縦に並べられ、追加も1行 |
| ワイルドカードで文字列の形を見る(〜で始まる、拡張子など) | case | *.log) のようにそのまま書ける |
| 引数やオプションの種類で分ける | case | -v|--verbose) と別名を並べられる |
数値の大小を比べる(-gt、-le) |
if | case は文字列の一致だけを見る |
ファイルやフォルダの有無を調べる(-f、-d) |
if | テスト演算子は if の担当 |
| コマンドが成功したかどうかで分ける | if | if コマンド; then と書ける |
| 複数の変数を組み合わせた条件(A かつ B) | if | && や || で組み立てる |
| 正規表現で照合する | if(bash の [[ =~ ]]) |
case のパターンは正規表現ではない |
| 2択だけの単純な分岐 | if | 1行で済み、読み手に伝わりやすい |
迷ったときは、次の順に考えると決めやすくなります。
flowchart TD
A[分岐を書きたい] --> B{見るのは1つの文字列?}
B -- いいえ --> I[if を使う]
B -- はい --> C{数値の大小やファイルの有無?}
C -- はい --> I
C -- いいえ --> D{候補が3つ以上 または ワイルドカードを使う?}
D -- はい --> K[case を使う]
D -- いいえ --> I
case と if は組み合わせても構いません。「まず case で種類を分け、その節の中で if を使って細かく確かめる」 という形は、引数の処理でよく使われます。
コピペで使えるテンプレート4種
y/n判定のテンプレート
大文字・小文字と、yes・no のつづりの両方を受け付ける形です。
#!/usr/bin/env bash
# yn.sh : y/n 判定(大文字小文字と yes/no を許す)
answer="$1"
case "$answer" in
[yY]|[yY][eE][sS]) echo "はい" ;;
[nN]|[nN][oO]) echo "いいえ" ;;
"") echo "未入力(既定値を使う)" ;;
*) echo "y か n で答えてください: $answer" ;;
esac
実行結果(y、Yes、NO、空文字、maybe を順に渡した場合):
はい
はい
いいえ
未入力(既定値を使う)
y か n で答えてください: maybe
キーボードから受け取る場合は、answer="$1" の行を read -r -p "続けますか? [y/n]: " answer に置き換えます。
大文字と小文字をまとめて扱う方法は、ほかに2つあります。どちらも bash 5.2 で動作を確かめました。
# 方法1: ${変数,,} で小文字にそろえてから比べる(bash 4 以降)
case "${1,,}" in
y|yes) echo "はい" ;;
n|no) echo "いいえ" ;;
esac
# 方法2: nocasematch を有効にして、大文字小文字を区別しない
shopt -s nocasematch
case "$1" in
y|yes) echo "はい" ;;
n|no) echo "いいえ" ;;
esac
shopt -u nocasematch
$ bash lower.sh YES
はい
$ bash nocase.sh yEs
はい
| 方法 | 特徴 | 使える環境 |
|---|---|---|
[yY] と文字クラスで書く |
sh でも動く。つづりが長いと記述が増える | bash、sh |
${変数,,} で小文字化 |
パターンが短く読みやすい | bash 4 以降 |
shopt -s nocasematch |
case 全体に効く。使い終わったら -u で戻す |
bash |
拡張子別に処理を分けるテンプレート
#!/usr/bin/env bash
# ext.sh : 拡張子別に処理を分ける
for f in "$@"; do
case "$f" in
*.tar.gz|*.tgz) echo "$f: tar xzf で展開" ;;
*.gz) echo "$f: gunzip で展開" ;;
*.zip) echo "$f: unzip で展開" ;;
*.[jJ][pP][gG]|*.png) echo "$f: 画像" ;;
*.*) echo "$f: 未対応の拡張子" ;;
*) echo "$f: 拡張子なし" ;;
esac
done
実行結果:
$ bash ext.sh a.tar.gz b.gz c.zip d.JPG e.csv README
a.tar.gz: tar xzf で展開
b.gz: gunzip で展開
c.zip: unzip で展開
d.JPG: 画像
e.csv: 未対応の拡張子
README: 拡張子なし
*.tar.gz を *.gz より上に書いているのがポイントです。順番を逆にしたときの動きは、あとの「つまずきやすい箇所」で確かめます。echo の部分を実際のコマンドに置き換えれば、そのまま展開スクリプトとして使えます。
start/stop引数を受けるテンプレート
サービスの操作スクリプトでおなじみの形です。関数と組み合わせると、case 文が「目次」の役割になります。
#!/usr/bin/env bash
# svc.sh : start/stop/restart/status を引数で受ける
start() { echo "開始しました"; }
stop() { echo "停止しました"; }
status() { echo "稼働中です"; }
case "${1:-}" in
start) start ;;
stop) stop ;;
restart) stop; start ;;
status) status ;;
""|-h|--help)
echo "使い方: $0 {start|stop|restart|status}"
;;
*)
echo "不明な引数です: $1" >&2
echo "使い方: $0 {start|stop|restart|status}" >&2
exit 1
;;
esac
実行結果:
$ bash svc.sh start
開始しました
$ bash svc.sh restart
停止しました
開始しました
$ bash svc.sh
使い方: svc.sh {start|stop|restart|status}
$ bash svc.sh reload
不明な引数です: reload
使い方: svc.sh {start|stop|restart|status}
$ echo $?
1
| 工夫 | ねらい |
|---|---|
"${1:-}" |
引数なしでも空文字として扱い、set -u を付けたスクリプトでも止まらない |
"" を help と同じ節に入れる |
引数なしで使い方を表示する |
不明な引数は >&2 と exit 1 |
メッセージを標準エラーに出し、呼び出し元が異常に気づけるようにする |
オプション解析のテンプレート
while と shift を組み合わせ、引数を1つずつ case に通します。長いオプション(--output)や --output=値 の形も扱えます。
#!/usr/bin/env bash
# opt.sh : while + case + shift でオプションを解析する
verbose=0
output="out.txt"
args=()
while [ $# -gt 0 ]; do
case "$1" in
-v|--verbose) verbose=1 ;;
-o|--output)
[ $# -ge 2 ] || { echo "$1 には値が必要です" >&2; exit 1; }
output="$2"; shift ;;
--output=*) output="${1#*=}" ;;
-h|--help) echo "使い方: $0 [-v] [-o FILE] 引数..."; exit 0 ;;
--) shift; args+=("$@"); break ;;
-*) echo "不明なオプションです: $1" >&2; exit 1 ;;
*) args+=("$1") ;;
esac
shift
done
echo "verbose=$verbose output=$output args=${args[*]}"
実行結果:
$ bash opt.sh -v -o result.csv a.txt b.txt
verbose=1 output=result.csv args=a.txt b.txt
$ bash opt.sh --output=x.log -- -v c.txt
verbose=0 output=x.log args=-v c.txt
$ bash opt.sh -x
不明なオプションです: -x
$ bash opt.sh -o
-o には値が必要です
| 節 | 役割 |
|---|---|
-v|--verbose) |
短い名前と長い名前を1つの節で受ける |
-o|--output) |
次の引数を値として読み、shift を1回多く行う |
--output=*) |
${1#*=} で「=」より後ろを取り出す |
--) |
これ以降はオプションとして扱わない(-v という名前のファイルも渡せる) |
-*) |
知らないオプションを受け止める。*) より上に置く |
*) |
オプション以外の引数を配列にためる |
短いオプションだけでよければ、bash 組み込みの getopts と case の組み合わせでも書けます。
#!/usr/bin/env bash
verbose=0; output="out.txt"
while getopts "vo:h" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
h) echo "使い方: $0 [-v] [-o FILE] 引数..."; exit 0 ;;
*) exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "verbose=$verbose output=$output args=$*"
$ bash getopts.sh -v -o r.csv a.txt
verbose=1 output=r.csv args=a.txt
$ bash getopts.sh -x
getopts.sh: illegal option -- x
| 方法 | 向いている場面 |
|---|---|
while + case + shift |
--verbose のような長いオプションや --name=値 も受けたい |
getopts + case |
短いオプションだけで足りる。-vo file のようなまとめ書きやエラー表示を任せたい |
つまずきやすい箇所と直し方
どれにも当てはまらない節は最後に置く
case 文は上から順に判定します。*) を先頭に書くと、どんな値もそこで確定します。
# order_ng.sh : 「その他」の節を先頭に置いた場合
case "$1" in
*) echo "その他" ;;
start) echo "開始します" ;;
esac
$ bash order_ng.sh start
その他
start を渡しても「その他」になりました。エラーは出ないので、気づきにくい箇所です。*) は必ず最後に置きます。
同じ理由で、範囲の広いパターンは範囲の狭いパターンより下に書きます。
# order2.sh : 広いパターンを上に置いた場合
case "$1" in
*.gz) echo "gzip" ;;
*.tar.gz) echo "tar+gzip" ;;
esac
$ bash order2.sh a.tar.gz
gzip
| 直す前 | 直した後 |
|---|---|
*.gz) → *.tar.gz) の順 |
*.tar.gz) → *.gz) の順 |
*) → start) の順 |
start) → *) の順 |
*) → -*) の順 |
-*) → *) の順 |
引用符で囲んだパターンは文字どおりに比べられる
パターン側を引用符で囲むと、* や | は特別な意味を持たなくなります。
# quote.sh
v="$1"
case "$v" in
"*") echo "アスタリスク1文字" ;;
"a|b") echo "a|b という文字列" ;;
a|b) echo "a または b" ;;
*) echo "その他" ;;
esac
$ bash quote.sh '*'
アスタリスク1文字
$ bash quote.sh 'a|b'
a|b という文字列
$ bash quote.sh a
a または b
$ bash quote.sh abc
その他
変数をパターンに使うときも同じです。引用符の有無で「文字どおり」と「パターンとして」が切り替わります。
# varpat.sh
pat='a*'
case "$1" in
"$pat") echo "引用符あり: 文字どおり a* と一致" ;;
$pat) echo "引用符なし: パターンとして一致" ;;
*) echo "その他" ;;
esac
$ bash varpat.sh 'a*'
引用符あり: 文字どおり a* と一致
$ bash varpat.sh apple
引用符なし: パターンとして一致
| 書き方 | 扱い |
|---|---|
*) |
ワイルドカード(何にでも一致) |
"*") |
アスタリスク1文字とだけ一致 |
$pat) |
変数の中身をパターンとして使う |
"$pat") |
変数の中身と文字どおり比べる |
「ユーザーが入力した文字列と完全に同じか」を見たいときは "$pat"、設定で渡したパターンに当てはめたいときは $pat、と使い分けます。
空白を含むパターンは引用符かバックスラッシュで書く
パターンに空白をそのまま書くと、構文エラーになります。
case "$1" in
hello world) echo x ;;
esac
sp.sh: line 2: syntax error near unexpected token `world'
sp.sh: line 2: ` hello world) echo x ;;'
引用符で囲むか、空白の前にバックスラッシュを置くと動きます。
case "$1" in
"hello world"|bye\ now) echo "一致" ;;
esac
$ bash sp2.sh "bye now"
一致
caseの直後の値にも引用符を付けておく
case $v in のように引用符なしで書いても、bash では値が空白で分割されたり、* がファイル名に展開されたりすることはありませんでした。
v="hello world"
case $v in
"hello world") echo "一致" ;;
*) echo "不一致" ;;
esac
f="*"
case $f in
"*") echo "アスタリスク(展開されない)" ;;
*) echo "その他" ;;
esac
一致
アスタリスク(展開されない)
動きは同じですが、if 文やコマンドの引数では引用符が必要なので、case でも "$v" と書く習慣にしておくと、書き方がそろって見直しやすくなります。
節の終わりの記号を書き忘れたとき
;; を忘れると、次の節のパターンが処理の続きとして読まれ、構文エラーになります。
case "$1" in
a) echo A
b) echo B ;;
esac
syn.sh: line 3: syntax error near unexpected token `)'
syn.sh: line 3: ` b) echo B ;;'
エラーが示す行は「書き忘れた行の次の行」です。) 付近の構文エラーが出たら、1つ上の節の終わりを確かめます。esac を忘れた場合は次の表示になります。
syn2.sh: line 3: syntax error: unexpected end of file
最後の節の ;; は省略しても動きますが、あとから節を足したときに書き忘れやすくなるため、すべての節に付けておくのがおすすめです。
チェックリスト
| 確認すること | 見るポイント |
|---|---|
*) は最後にあるか |
上にあると、それより下の節は実行されない |
| 狭いパターンが上にあるか | *.tar.gz は *.gz より上 |
想定外の値を受ける *) があるか |
ないと、何も起きずに終了コード 0 で進む |
| 文字どおり比べたい箇所に引用符があるか | "*"、"$pat" |
各節が ;; で終わっているか |
) 付近の構文エラーは1つ上の節を確認 |
;& と ;;& を使うなら bash で動かしているか |
1行目を #!/usr/bin/env bash にする |
一歩進んだ使い方:extglobでパターンを広げる
bash の shopt -s extglob を有効にすると、「1回以上のくり返し」や「〜以外」 をパターンで書けます。
shopt -s extglob
case "$1" in
+([0-9])) echo "$1: 数字だけ" ;;
?(-)+([0-9])) echo "$1: 符号つきの整数" ;;
!(*.bak)) echo "$1: .bak 以外" ;;
*) echo "$1: その他" ;;
esac
$ bash extglob.sh 2026
2026: 数字だけ
$ bash extglob.sh -15
-15: 符号つきの整数
$ bash extglob.sh a.txt
a.txt: .bak 以外
$ bash extglob.sh a.bak
a.bak: その他
| 書き方 | 意味 |
|---|---|
?(パターン) |
0回または1回 |
*(パターン) |
0回以上 |
+(パターン) |
1回以上 |
@(a|b) |
どれか1つ |
!(パターン) |
それ以外 |
extglob は bash の機能です。sh でも動かすスクリプトでは、前の章の ''|*[!0-9]*) のような基本パターンの組み合わせで書きます。
よくある質問
Q. case文で「AかつB」のような条件は書けますか?
A. case 文は1つの値とパターンの一致だけを見るため、「かつ」は直接は書けません。case "$a:$b" in start:prod) ... ;; のように2つの値を区切り文字でつないで1つの文字列にすると、組み合わせをパターンで表せます。条件が複雑なら if 文が向いています。
Q. case文で正規表現は使えますか?
A. 使えません。case 文のパターンはワイルドカード(グロブ)で、. や + は普通の文字として扱われます。正規表現が必要なときは bash の [[ "$v" =~ 正規表現 ]] を if 文で使うか、shopt -s extglob で +([0-9]) のような拡張パターンを使います。
Q. 複数パターンは何個まで並べられますか?
A. 数を気にせず並べられます。ただし | の直後でそのまま改行すると、bash 5.2 では構文エラー(syntax error near unexpected token に続いて newline と表示)になりました。行を分けたいときは、行末にバックスラッシュを置いて次の行へ続けます(動作を確認済み)。数が多いときは、*.jpg|*.jpeg|*.png のように意味のまとまりごとに節を分けると読みやすくなります。
Q. ;& と ;;& はどのシェルで使えますか?
A. 今回確認したのは bash 5.2.21(動作する)と dash 0.5.12(構文エラーになる)です。bash では 4.0 以降で使えることが一般に知られています。配布先のシェルが決まっていないスクリプトでは ;; だけで書くと、動く範囲が広がります。
まとめ
- case 文は「上から順に判定し、最初に一致した節だけを実行」 します。
*)と範囲の広いパターンは下に置きます。 - 複数パターンは
|、前方一致・後方一致は*、1文字は?、文字の種類は[a-z]や[!0-9]で書けます。 ;&は次の節を無条件に実行、;;&は次以降のパターンも判定します。どちらも bash 4 以降の機能で、dash では構文エラーになりました。- 1つの文字列を3つ以上の候補やワイルドカードで分けるなら case、数値の大小やファイルの有無は if が向いています。
- 引用符で囲んだパターンは文字どおりに比べられます。
"*"はアスタリスク1文字、"$pat"は変数の中身そのものです。 - y/n 判定・拡張子別処理・start/stop 引数・オプション解析は、この記事のテンプレートを貼って
echoの部分を置き換えれば使えます。 - 想定外の値に気づけるよう、最後に
*)の節を置く習慣が役立ちます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「シェルスクリプトcase文の書き方|複数パターン・ワイルドカード早見表」
株式会社RJCでは、シェルスクリプトの書き方やcase文の使い分けを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。