はじめに
正規表現の量指定子(数量詞)であるアスタリスク(以下、「*」と表記)について、
つまずいたことを検証内容やリファレンスの引用とともに備忘録としてまとめます。
TL;DR
GNU grepコマンドで先頭に記号「*」を持つ正規表現を指定したとき、「*」は量指定子ではなく通常の文字として解釈される。
これはPOSIXに則ったもので、BREという種類の正規表現で発生する。
実行環境
- Ubuntu 22.04.1 LTS
- GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
遭遇した正規表現と生じた疑問
$ grep "*.py" -rl ./
「*」は直前の文字の0回以上の繰り返しを表すことから、直前の文字がないこの正規表現は構文エラーになるのでは、と考えた。
しかし実際に実行してみるとエラーは発生しなかったため、$ grep "*.py" -rl ./"
は、何らかの正規表現として成立している可能性がある。
オプションとして付与した-r, -lについては次の通り。
-r, --recursive
各ディレクトリの下にあるすべてのファイルを再帰的に読み込みます。 ただし、シンボリックリンクはコマンドラインで指定されたときにのみたどります。 検索対象のファイルが指定されなかった場合には grep は現在のディレクトリを探すことに注意してください。 これは -d recurse オプションと等価です。
JM Project / GREP
-l, --files-with-matches
通常の出力はしません。その代わりに、 grep を普通に実行した際に、何らかの検索結果を表示するような入力ファイルの名前を 列挙します (訳注: すなわち、-l オプションを指定すると、 -v オプションを同時に指定しない場合は、パターンにマッチする 文字列を含む行が存在するファイルの名前を列挙するということです)。 個々のファイルに対する走査は、最初のマッチで終了します。
JM Project / GREP
コマンド操作による検証
いくつかのパターンでテキストファイルを作成し、grepコマンドの実行結果を確かめた。
用意したテキストファイルと実行結果は次の通りで、先頭の「*」は量指定子ではなく通常の文字列として解釈されているようにも思える。
(文字列"*.py"を含むファイルの名前が抽出されている)
$ echo "hoge" > test1.txt
$ echo "pypy" > test2.txt
$ echo "hoge" > test3.py
$ echo "hoge*.py" > test4.txt
$ echo "hog*.pye" > test5.txt
$ echo ".py" > test6.txt
$ grep "*.py" -rl ./
./test4.txt
./test5.txt
リファレンスによる調査
GNU grepの仕様を調べる
ここでGNU grepのmanページを参照してみると、grepでは次の3種類の正規表現を利用可能で、デフォルトはBREと読める。
つまり、$ grep "*.py -rl ./"
ではBRE(標準正規表現)を利用していたことがわかる。
- BRE
- ERE
- PCRE
-E, --extended-regexp
PATTERN を拡張正規表現 (ERE) として扱います (下記参照)。
-F, --fixed-strings
PATTERN を改行で区切られた固定文字列 (正規表現のかわりに) のリストとして扱い、 その文字列のいずれかとマッチするかどうかを調べます。
-G, --basic-regexp
PATTERN を基本正規表現 (BRE) として扱います (下記参照)。これがデフォルトです。
-P, --perl-regexp
パターンを Perl 互換の正規表現 (PCRE) として扱います。 きわめて実験的なものなので、 grep -P を使うと、その機能は実装されていませんという 警告が出るかもしれません。
JM Project / GREP
POSIX正規表現には標準正規表現(BRE, Basic Regular Expression)と拡張正規表現(ERE, Extended Regular Expression)の2種類があります。
GNU grepでは標準でBREを、grepの-eオプションまたはegrepではEREを使うことができます。
正規表現技術入門――最新エンジン実装と理論的背景
PCREはシンタックス(構文)とセマンティクス(意味、振る舞い)の両面でPerlの正規表現と互換性が高い高品質な正規表現パッケージです。
正規表現技術入門――最新エンジン実装と理論的背景
BREの仕様を調べる
次に、$ grep "*.py -rl ./"
で使用された正規表現、BREに関するリファレンスを調査した。
その結果、最初の文字(^
があればその後)のアスタリスクは特殊な意味を持たない、と読み取れる記述を見つけた。
このことから、予想通り先頭の「*」は通常の文字として解釈されていたことが分かった。
*
The <asterisk> shall be special except when used:
In a bracket expressionAs the first character of an entire BRE (after an initial '^', if any)
As the first character of a subexpression (after an initial '^', if any); see BREs Matching Multiple Characters
蛇足
ERE, PCREによる検証
GNU grepコマンドはBRE, ERE, PCREの3種類が利用可能と分かったので、BRE以外の方法についても同様に検証する。
そこで、BREのときに用いたテキストファイルをもとに、ERE, PCREを試してみる。
少なくともBREとは異なる結果が出力され、特にPCREではエラーとなることが分かった。
$ echo "hoge" > test1.txt
$ echo "pypy" > test2.txt
$ echo "hoge" > test3.py
$ echo "hoge*.py" > test4.txt
$ echo "hog*.pye" > test5.txt
$ echo ".py" > test6.txt
$ grep "*.py" -Erl ./
./test6.txt
./test4.txt
./test5.txt
./test2.txt
$ grep "*.py" -Prl ./
grep: nothig to repeat
EREの仕様を調べる
BRE同様、EREについてもリファレンス調査を行っていく。
その結果、EREでは未定義な結果が生成されると読み取れるため、どのような挙動を示すのかは(リファレンスを読む限りでは)不明ということが分かった。
*+?{
The <asterisk>, <plus-sign>, <question-mark>, and <left-brace> shall be special except when used in a bracket expression (see RE Bracket Expression). Any of the following uses produce undefined results:
If these characters appear first in an ERE, or immediately following an unescaped <vertical-line>, <circumflex>, <dollar-sign>, or <left-parenthesis>If a is not part of a valid interval expression (see EREs Matching Multiple Characters)