LPIC Level1 復習ラジオ 19:ワイルドカードと正規表現
音声
目次
ワイルドカードと正規表現の違い
ワイルドカードと正規表現は、どちらもパターンを使います。しかし、誰がパターンを解釈するのか、記号が何を意味するのかが異なります。
| 種類 | 主に解釈するもの | 主な対象 | 例 |
|---|---|---|---|
| シェルのワイルドカード | bashなどのシェル | ファイル名やパス名 | ls *.txt |
| 正規表現 | grep、sed、awkなど |
ファイル内部の文字列 | grep '^root:' /etc/passwd |
シェルのワイルドカードは、グロブやパス名展開とも呼ばれます。シェルがパターンに一致するファイル名を探し、コマンドを実行する前に、そのパターンを実際のファイル名へ置き換えます。
printf '%s\n' *.txt
カレントディレクトリにa.txtとb.txtがある場合、シェルは概ね次のような引数へ展開してからprintfを実行します。
printf '%s\n' a.txt b.txt
一方、次の'^root:'はgrepが解釈する正規表現です。
grep '^root:' /etc/passwd
シェルには正規表現を文字列のまま渡し、grepが各行の内容と照合します。
*によるファイル名展開
シェルのワイルドカード*は、0文字以上の任意の文字列に一致します。
ls *.txt
cp report* backup/
rm -- *.tmp
*.txtは、名前が.txtで終わるファイルに一致します。
| パターン | 一致する例 | 一致しない例 |
|---|---|---|
*.txt |
a.txt、report.txt |
report.csv |
log* |
log、log1、logs |
app.log |
*backup* |
backup、old-backup.tar |
archive.tar |
*は0文字にも一致するため、log*はlogそのものにも一致します。
ディレクトリを含むパターン
ls /var/log/*.log
cp images/*.png backup/
ls project/*/README
通常のパス名展開では、*はスラッシュを越えて複数階層へ進みません。project/*/READMEでは、projectの直下にある一階層分のディレクトリ名へ一致します。
隠しファイル
bashの通常設定では、パターンの先頭にある*は、名前がドットで始まるファイルへ一致しません。
printf '%s\n' *
printf '%s\n' .*
隠しファイルを対象にする場合は、先頭のドットをパターンへ明示します。bashではdotglobを有効にし、通常のパターンへ隠しファイルを含める方法もあります。
shopt -s dotglob
printf '%s\n' *
shopt -u dotglob
dotglobはbash固有のシェルオプションです。スクリプトで変更する場合は、後続処理への影響も考えます。
?による1文字の指定
シェルのワイルドカード?は、任意の1文字に一致します。0文字や2文字には一致しません。
ls file?.txt
ls image-??.png
cp report-202?.pdf archive/
| パターン | 一致 | 不一致 |
|---|---|---|
file?.txt |
file1.txt、fileA.txt |
file.txt、file10.txt |
??.log |
01.log、ab.log |
1.log、001.log |
例えば、file?.txtは、fileと.txtの間に、ちょうど1文字がある名前へ一致します。
*との違い
file*.txt
file?.txt
file*.txt:間に0文字以上file?.txt:間に必ず1文字
ファイル名の桁数が決まっている場合や、連番ファイルを絞り込む場合に便利です。
[]による文字の候補
角括弧[]は、括弧内に書いた候補のうち、いずれか1文字に一致します。
ls file[123].txt
ls image[ab].png
ls log[0-9].txt
| パターン | 意味 |
|---|---|
[abc] |
a、b、cのいずれか1文字 |
[0-9] |
範囲として扱われる1文字 |
[!abc] |
a、b、c以外の1文字 |
[^abc] |
bashのパターンでは、abc以外の1文字として利用できる |
file[123].txtは、file1.txt、file2.txt、file3.txtへ一致します。file12.txtには一致しません。
文字クラス
ls file[[:digit:]].txt
ls [[:upper:]]*.txt
ls *[[:space:]]*
角括弧の中では、POSIX文字クラスを利用できます。
| 文字クラス | 主な意味 |
|---|---|
[[:digit:]] |
数字 |
[[:alpha:]] |
英字などの文字 |
[[:alnum:]] |
英数字など |
[[:lower:]] |
小文字 |
[[:upper:]] |
大文字 |
[[:space:]] |
空白文字 |
範囲や文字クラスの一致結果はロケールの影響を受ける場合があります。正確なバイト順やASCII相当の動作を求める調査では、LC_ALL=Cを一時指定することがあります。
LC_ALL=C printf '%s\n' [A-Z]*
クォートとエスケープ
ワイルドカードをシェルに展開させず、記号そのものとしてコマンドへ渡したい場合は、クォートまたはバックスラッシュを使います。
printf '%s\n' "*.txt"
printf '%s\n' '*.txt'
printf '%s\n' \*.txt
これらは、*.txtという文字列をそのまま表示します。
クォートしない場合
printf '<%s>\n' *.txt
シェルが*.txtを一致するファイル名へ展開し、それぞれをprintfの引数として渡します。
正規表現をクォートする理由
grep '^root:' /etc/passwd
grep '[0-9]' data.txt
grep '.*\.log$' list.txt
grepへ渡す正規表現は、シングルクォートで囲むことが一般的です。シェルによるワイルドカード展開、変数展開、バックスラッシュの解釈などを防ぎ、パターンをそのままgrepへ渡せます。
ダブルクォートとの違い
pattern='^error'
grep "$pattern" app.log
ダブルクォート内では変数展開が行われます。変数へ保存した正規表現を渡す場合は、"$pattern"のようにダブルクォートで囲みます。
変数展開が不要な固定パターンでは、シングルクォートが分かりやすくなります。
文字どおりの検索
grep -F 'a.b*c[1]' file.txt
grep -Fは、パターンを正規表現ではなく固定文字列として扱います。正規表現の記号を多数含む文字列を、そのまま探したい場合に便利です。
ワイルドカード利用時の注意
展開結果を先に確認する
printf '%s\n' *.tmp
rm -- *.tmp
削除や移動の前に、printfなどで一致するファイル名を確認すると安全です。
オプションと誤解されるファイル名
展開されたファイル名がハイフンで始まると、コマンドのオプションとして解釈される場合があります。
rm -- *.tmp
cp -- *.txt backup/
--は、多くのコマンドでオプションの終わりを示します。以降の値をファイル名として扱わせます。
一致するファイルがない場合
bashの通常設定では、ワイルドカードに一致するファイルがない場合、パターン文字列が展開されずに残ります。
printf '<%s>\n' no-match-*.txt
一致がなければ、no-match-*.txtという文字列がそのまま渡されるのが標準的なbashの動作です。
bashでは、nullglobを有効にすると一致しないパターンを削除し、failglobを有効にするとエラーにできます。
shopt -s nullglob
files=(*.txt)
shopt -u nullglob
shopt -s failglob
printf '%s\n' no-match-*.txt
shopt -u failglob
これらはbash固有の設定であり、スクリプト全体へ影響します。必要な範囲を意識して使います。
隠しファイルと一括操作
*は通常、ドットで始まる名前へ一致しません。そのため、ディレクトリ内のすべてをコピーしたつもりでも、隠しファイルが含まれていない場合があります。
cp -a source/. destination/
ディレクトリの内容を隠しファイルも含めて複製する目的では、単純にsource/*とするより、コマンドの仕様に合う方法を使います。
パターンと実在ファイルを区別する
touch 'literal*.txt'
ls -l 'literal*.txt'
ファイル名自体に*などが含まれる場合は、クォートまたはエスケープして扱います。
grepと基本正規表現
grepは、入力からパターンに一致する行を検索します。オプションを付けない通常のgrepは、基本正規表現、BREを使います。
grep 'pattern' file.txt
grep -G 'pattern' file.txt
-Gは基本正規表現を明示します。通常は省略できます。
| 記号 | 基本正規表現での主な意味 |
|---|---|
. |
任意の1文字 |
^ |
行頭 |
$ |
行末 |
[abc] |
a、b、cのいずれか1文字 |
[^abc] |
a、b、c以外の1文字 |
* |
直前の要素を0回以上繰り返す |
\{m,n\} |
直前の要素を指定回数繰り返す |
\(...\) |
基本正規表現でのグループ化 |
拡張正規表現との違い
grep -E 'error|warning' app.log
grep -E 'colou?r' words.txt
grep -Eは拡張正規表現、EREを使います。拡張正規表現では、?、+、|、()などをバックスラッシュなしで使います。
基本正規表現では、GNU grepで同様の機能を使う場合、\?、\+、\|、\(...\)のように記述します。
位置を表す^と$
^は行頭、$は行末を表すアンカーです。文字そのものではなく、行の位置に一致します。
行頭
grep '^root:' /etc/passwd
grep '^ERROR' app.log
^root:は、行の先頭がroot:で始まる行へ一致します。
行末
grep '/bin/bash$' /etc/passwd
grep '\.conf$' file-list.txt
/bin/bash$は、行末が/bin/bashで終わる行へ一致します。
行全体
grep '^yes$' answers.txt
grep '^127\.0\.0\.1$' addresses.txt
^yes$は、行全体がちょうどyesである行だけに一致します。
空行
grep '^$' file.txt
grep -n '^$' file.txt
^$は、行頭の直後に行末があるため、空行へ一致します。
空白だけの行
grep '^[[:space:]]*$' file.txt
[[:space:]]*は空白文字が0回以上続くことを表します。空行と、空白だけの行の両方に一致します。
コメント行
grep '^[[:space:]]*#' config.conf
行頭から空白が0文字以上続き、その後に#がある行を検索します。先頭に空白があるコメント行も対象になります。
正規表現の.・[]・*
.は任意の1文字
grep 'c.t' words.txt
c.tは、cとtの間に任意の1文字がある部分へ一致します。例えば、cat、cot、cutなどです。
ドットそのものを検索する場合は、バックスラッシュで特殊な意味を打ち消します。
grep '\.txt$' file-list.txt
grep '192\.168\.' addresses.txt
[]は候補のうち1文字
grep 'gr[ae]y' words.txt
grep '[0-9]' data.txt
grep '[^0-9]' data.txt
gr[ae]y:grayまたはgreyを含む文字列[0-9]:範囲に含まれる1文字[^0-9]:数字以外の1文字
正規表現の否定は、角括弧の直後に^を書きます。シェルのワイルドカードでは[!0-9]がよく使われるため、混同しないようにします。
文字クラス
grep '[[:digit:]]' data.txt
grep '^[[:alpha:]][[:alnum:]_]*$' names.txt
grep '[[:space:]]' file.txt
文字クラスは、ロケールに応じた文字の分類を表します。単純な英数字だけでなく、実行環境の文字集合を意識した検索に利用できます。
*は直前の要素を0回以上
grep 'ab*c' words.txt
ab*cでは、bが0回以上続きます。そのため、ac、abc、abbcなどに一致します。
正規表現の*は、それ単独で任意の文字列を表すわけではありません。任意の文字列を表す組み合わせとしては、.*がよく使われます。
grep '^ERROR.*failed$' app.log
.が任意の1文字、*が直前のドットを0回以上繰り返すため、合わせて任意の文字列を表します。
繰り返し回数
grep '^[0-9]\{4\}$' years.txt
grep 'a\{2,4\}' words.txt
基本正規表現では、回数指定の波括弧をバックスラッシュでエスケープします。
\{4\}:ちょうど4回\{2,4\}:2回以上4回以下\{2,\}:2回以上
グループ化と後方参照
grep '^\(ab\)*$' words.txt
grep '^\([[:alpha:]]\+\) \1$' names.txt
基本正規表現では、\(...\)でグループ化します。\1は最初のグループと同じ文字列へ一致する後方参照です。GNU grepでは利用できますが、複雑な表現は読み間違いや処理負荷にも注意します。
実践的な検索と確認方法
1. ファイル名の展開結果を確認する
printf '<%s>\n' *.log
compgen -G '*.log'
削除や移動を行う前に、どのファイル名へ展開されるかを確認します。compgen -Gはbashでパターンに一致する名前を確認する方法の一つです。
2. ファイル内部の行頭を検索する
grep -n '^PermitRootLogin' /etc/ssh/sshd_config
-nを付けると行番号も表示されます。
3. コメントと空行を除く
grep -Ev '^[[:space:]]*(#|$)' config.conf
この例は拡張正規表現を使います。行頭から空白が続き、コメント記号または行末が続く行を除外します。
基本正規表現だけで段階的に処理する場合は、次のように分けられます。
grep -v '^[[:space:]]*#' config.conf \
| grep -v '^[[:space:]]*$'
4. 完全一致を確認する
grep -x 'enabled' settings.txt
grep '^enabled$' settings.txt
grep -xは行全体がパターンへ一致する行を選びます。単純な固定文字列なら、grep -Fx 'enabled'とすると正規表現の解釈を避けられます。
5. パターンがハイフンで始まる場合
grep -- '-error' app.log
grep -e '-error' app.log
-errorをそのまま書くと、オプションと解釈される可能性があります。--でオプションを終了するか、-eでパターンを指定します。
6. 正規表現の記号を文字どおり探す
grep -F 'a.b*c[1]' file.txt
grep 'a\.b\*c\[1\]' file.txt
固定文字列として探せる場合は、grep -Fの方が意図を表しやすくなります。
7. ロケールを固定して確認する
LC_ALL=C grep '^[A-Z]' file.txt
LC_ALL=C sort file.txt
範囲表現や文字の分類、並べ替え結果が環境によって異なる場合は、一時的にLC_ALL=Cを指定して比較します。
ワイルドカードと正規表現の比較
| 目的 | ワイルドカード | 基本正規表現 |
|---|---|---|
| 任意の文字列 | * |
.* |
| 任意の1文字 | ? |
. |
| 候補の1文字 | [abc] |
[abc] |
| 候補以外の1文字 | [!abc] |
[^abc] |
| 行頭・行末 | 通常は扱わない | ^・$ |
| 主な利用対象 | ファイル名 | 行内の文字列 |
症状別の確認
| 症状 | 主な確認点 |
|---|---|
*.txtがそのまま表示される |
一致するファイルがあるか、クォートしていないか |
| 隠しファイルが対象にならない | 先頭ドットの明示、dotglob、コマンドのコピー方法 |
grepへ複数ファイル名が渡る |
正規表現をクォートせず、シェルが展開していないか |
| ドットが任意の文字へ一致する | 文字どおりのドットなら\.またはgrep -F |
*の検索結果が想定と違う |
ワイルドカードか正規表現か、直前の要素は何か |
| 範囲の一致が環境で変わる | ロケール、文字クラス、LC_ALL=C |
30秒チェック
- シェルのワイルドカードは、コマンドの実行前に誰が展開するでしょうか。
- ワイルドカードの
*と?は、それぞれ何文字に一致するでしょうか。 file[123].txtは、どのようなファイル名に一致するでしょうか。- ワイルドカードを展開させず、
*.txtという文字列のまま渡すにはどうするでしょうか。 - 通常の
grepが使う正規表現は、基本正規表現と拡張正規表現のどちらでしょうか。 - 基本正規表現の
.と*は、それぞれ何を表すでしょうか。 - 空行へ一致する正規表現は何でしょうか。
- 正規表現の記号を解釈せず、固定文字列として検索する
grepのオプションは何でしょうか。
答えを表示
- bashなどのシェル
*は0文字以上、?はちょうど1文字file1.txt、file2.txt、file3.txt- シングルクォート、ダブルクォート、またはバックスラッシュで保護する
- 基本正規表現
.は任意の1文字、*は直前の要素を0回以上繰り返す^$-F
要約
- ワイルドカードは主にシェルがファイル名へ展開し、正規表現は
grepなどが文字列の照合に使います。 - シェルはコマンド実行前にワイルドカードを実在するファイル名へ置き換えます。
- ワイルドカードの
*は0文字以上、?は任意の1文字に一致します。 [abc]は候補のいずれか1文字、[!abc]は候補以外の1文字へ一致します。*は通常スラッシュを越えず、先頭がドットの隠しファイルにも一致しません。- ワイルドカードを文字どおり渡す場合は、クォートまたはバックスラッシュで保護します。
- 削除や移動の前には、
printfなどで展開結果を確認すると安全です。 - bashの通常設定では、一致しないワイルドカードは展開されず、そのまま残ります。
- 通常の
grepは基本正規表現、grep -Eは拡張正規表現、grep -Fは固定文字列検索です。 - 正規表現の
^は行頭、$は行末、.は任意の1文字を表します。 - 正規表現の
*は、直前の要素を0回以上繰り返します。 - 正規表現で任意の文字列を表す代表的な組み合わせは
.*です。 - 文字どおりのドットを検索する場合は
\.、固定文字列全体ならgrep -Fを使えます。 - 空行は
^$、空白だけの行も含める場合は^[[:space:]]*$で検索できます。 - 範囲や文字クラスの結果はロケールの影響を受ける場合があり、確認時に
LC_ALL=Cを使うことがあります。
参考資料
- GNU Bash Reference Manual
- GNU Bash Manual:Shell Operation
- GNU Bash Manual:Filename Expansion
- GNU Bash Manual:Pattern Matching
- GNU Bash Manual:Quoting
- GNU Bash Manual:The Shopt Builtin
- GNU Grep Manual
- GNU Grep Manual:Regular Expressions
- GNU Grep Manual:Character Classes and Bracket Expressions
- GNU Grep Manual:Anchoring
- GNU Grep Manual:Fundamental Structure
- GNU Grep Manual:Basic vs Extended Regular Expressions
- bash(1) — Linux manual page
- grep(1) — Linux manual page
- POSIX.1-2024:Shell Command Language
- POSIX.1-2024:Regular Expressions