■ はじめに
普段何気なく使っているアスタリスク記号 * ですが、いくつか種類が存在しているようで混乱してきたので整理してみようと思います。
例えば、以下 ①②③ のコマンドでは同じ * という記号が使われていますが、それぞれ若干動作が異なります。
① echo test*
② find . -name "*.txt"
③ ls | grep 'ab*'
結論から言うと以下の違いがあります。
① シェルが * をワイルドカードとして解釈する
② コマンド (find) が * をワイルドカードとして解釈する
③ コマンド (grep) が * を正規表現のメタ文字として解釈する
つまり * を解釈する時の「主体」と「* をなんの記号として使用するか」の 2 点がそれぞれで異なる、ということになります。
では具体的に何がどう違うのか?なぜ違うのか?順を追って見ていきましょう。
■ クイズ
順を追ってと言いましたがいきなりクイズです。
Linux サーバのカレントディレクトリに、以下のようなファイル名を持つ 12 個のテキストファイルがあるとします。
| No. | ファイル名 | No. | ファイル名 |
|---|---|---|---|
| 1 | 123 | 7 | abc |
| 2 | 123.txt | 8 | abc.txt |
| 3 | a | 9 | ab.txt |
| 4 | ab | 10 | a.txt |
| 5 | abb | 11 | test |
| 6 | abbb | 12 | test.txt |
さて、以下コマンドを実行した時に、マッチするファイルはどれになるでしょうか?
① echo test*
② find . -name "*.txt"
③ ls | grep 'ab*'
■ クイズの答え
正解は以下になります。
| No. | ファイル名 | コマンド ① | コマンド ② | コマンド ③ |
|---|---|---|---|---|
| 1 | 123 | |||
| 2 | 123.txt | ○ | ||
| 3 | a | ○ | ||
| 4 | ab | ○ | ||
| 5 | abb | ○ | ||
| 6 | abbb | ○ | ||
| 7 | abc | ○ | ||
| 8 | abc.txt | ○ | ○ | |
| 9 | ab.txt | ○ | ○ | |
| 10 | a.txt | ○ | ○ | |
| 11 | test | ○ | ||
| 12 | test.txt | ○ | ○ |
# echo test*
test test.txt
# find . -name "*.txt"
./test.txt
./a.txt
./ab.txt
./abc.txt
./123.txt
# ls | grep 'ab*'
a
ab
abb
abbb
abc
abc.txt
ab.txt
a.txt
■ 考察
さて、それでは各コマンド実行にあたり * がどのように解釈されるのか見ていきましょう。
以下に私なりに調べた結果を整理しております。
生成 AI で内容の妥当性チェックはしておりますが、もし変な箇所があったらコメントで指摘していただけると助かります m(_ _)m
表で整理
| コマンド ① | コマンド ② | コマンド ③ | |
|---|---|---|---|
| コマンド例 | # echo test* | # find . -name "*.txt" | # ls | grep 'ab*' |
| 「*」の種類 (長い ver) | シェルのパス名展開で使われる glob パターン のワイルドカード「*」 | コマンドが解釈する glob パターンのワイルドカード「*」 | 正規表現のメタ文字「*」 |
| 「*」の種類 (短い ver) | ワイルドカード | ワイルドカード | 正規表現のメタ文字 |
| 「*」の意味 | 0 文字以上の任意の文字列 | 0 文字以上の任意の文字列 | 直前の文字の 0 回以上の繰り返し |
| 「*」を誰が解釈するのか? | シェル (bash 等) | コマンド (find 等) | コマンド (grep 等) |
| マッチする文字列の例 | test test.txt | test.txt abc.txt 123.txt | a ab abb abc a.txt |
コマンド実行時の動作について補足説明
コマンド ①
コマンド実行前にシェルがワイルドカード * を解釈し、パターン test* にマッチするファイルをパス名展開します。パス名展開により、実行されるコマンドは以下のように変化します。
(パス名展開前)
# echo test*
(パス名展開後)
# echo test test.txt
(実際の実行結果)
# echo test*
test test.txt
上記パス名展開はコマンド実行前に行われます。
そのため echo コマンドから見ると元々の引数が test* だったことを知らない、という点がポイントになります。
コマンド ②
ダブルクォート " に囲まれている部分はシェルによってパス名展開されません (後述で補足あり)。
そのため、*.txt の部分がそのまま find コマンドに渡されます。
find コマンドは、カレントディレクトリ配下の *.txt のパターンに一致するファイル名を検索します。
今回は以下のファイルがパターンに一致して表示されました。
# find . -name "*.txt"
./test.txt
./a.txt
./ab.txt
./abc.txt
./123.txt
では、仮にダブルクォート " をつけ忘れてしまった場合はどうなるでしょうか?
# find . -name *.txt
この場合、シェルによって * がパス名展開されることになります。
つまり、コマンド ① の時と同じ動作になるわけです。
パス名展開によりコマンドは実行前に以下に変換されます。
(パス名展開前)
# find . -name *.txt
(パス名展開後)
# find . -name test.txt a.txt ab.txt abc.txt 123.txt
(実際の実行結果)
# find . -name *.txt
find: paths must precede expression: abc.txt
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
find コマンドは (正確に言うと -name オプションが) 引数を 1 つしか持てません。
しかしながらパス名展開により引数が合計 5 個あるという不適切な状態 (構文) となってしまい、コマンド実行結果は構文エラーになります。
ダブルクォート " をつけることの重要性が分かりますね。
もう少し補足すると、構文エラーになるのはカレントディレクトリにマッチするファイルが複数 (2 個以上) 存在する場合になります。仮にマッチするファイルが 1 個のみの場合は、結果的にエラーにならないので、パス名展開されてしまっていることに気付くことができない、という点も注意が必要です。
ワイルドカードを使う際 (且つシェルではなくコマンドで解釈させたい場合) は、ダブルクォート " やシングルクォート ' で囲う癖をつけると良いと思われます。
コマンド ③
シングルクォート ' に囲まれている部分はシェルによってパス名展開されません (後述で補足あり)。
そのため、'ab*' の部分がそのまま grep コマンドに渡されます。
grep コマンドは、'ab*' のパターンに一致する文字列でフィルタします。
具体的な条件としては、まず a の 1 文字にマッチし、続いて b の文字に 0 回以上マッチする (= b*) となります。(ab から始まる文字列、という意味ではないので注意)
今回は以下のファイルがパターンに一致して表示されました。
# ls | grep 'ab*'
a
ab
abb
abbb
abc
abc.txt
ab.txt
a.txt
b の文字が入っていない a や a.txt といったファイル名もマッチしている点がポイントですね。
また abc や abc.txt もマッチしています。これは正規表現 'ab*' が ab にマッチし、且つ grep コマンドがデフォルトで部分一致検索を行うためです。
■ おまけ (ダブルクォート " とシングルクォート ' について補足)
コマンド ② とコマンド ③ で使われていたダブルクォート " とシングルクォート ' は、それぞれ以下の意味を持ちます。
- シングルクォート
'に囲まれた文字を展開しない - ダブルクォート
"に囲まれた「!」「$」「`」は展開するが、それ以外の文字は展開しない
「展開」とは、パス名展開、チルダ展開、ブレース展開などのシェルによる展開処理を指します
今回のコマンド ② とコマンド ③ では展開され得る要素は * のみであるため、シングルクォート ' でもダブルクォート " でもどちらを使っても結果は変わりません。
■ おわりに
今回は Linux コマンドでよく使われるアスタリスク記号 * について整理してみましたが、いかがだったでしょうか。同じ記号なのに使う場面によって意味が変わるのは混乱しますよね。このブログが理解の一助になれば嬉しいです。
