103.7 レッスン 1
Certificate: |
LPIC-1 |
---|---|
Version: |
5.0 |
Topic: |
103 GNUおよびUnixコマンド |
Objective: |
103.7 正規表現によるテキストファイルの検索 |
Lesson: |
1 of 2 |
はじめに
データを加工する作業では文字列検索アルゴリズムを多用します。そこで、どのUnix系のOSにも正規表現(Regular Expression、RE)が備わっています。正規表現は一般的なパターンを表す文字の連なりから構成されます。長いテキストの中で対応する部分を見つけ出したり変更したりするために使います。正規表現は以下のような作業で威力を発揮します。
-
表形式のテキストデータを別のフォーマットに変換するスクリプトを書く。
-
ログやジャーナル、文書などから気になる箇所を検索する。
-
HTTPサーバー(特に nginx)でリクエストを解析するルールを書く。
-
文書の構造を保持しながら、マークアップ文書のタグを変更する。
正規表現はいくつかの基本要素(アトム)を組み合わせたものです。多くの文字はそのままアトムになりますが、次のように特別な意味や働きを持ったアトムもあります。
.
(ドット)-
改行を除き、どの文字にもマッチするアトムです。
^
(キャレット)-
行頭にマッチするアトムです。
$
(ドル記号)-
行末にマッチするアトムです。
例えば、bc
という正規表現は、そのままその文字を表す b
と c
のアトムから構成されていて、abcd
という文字列にマッチしますが、a1cd
という文字列にはマッチしません。.c
という正規表現は、abcd
と a1cd
の両方の文字列にマッチします。.
はどの文字にもマッチするからです。
^
(キャレット)と $
(ドル記号)のアトムは、行頭ないし行末にだけマッチさせたいときに使います。これらは アンカー とも呼ばれます。例えば、abcd
という行に対し、cd
はマッチしますが、^cd
はマッチしません。同様に、abcd
という行に対し、ab
はマッチしますが、ab$
はマッチしません。^
は正規表現の先頭以外ではそのまま ^
を表し、$
は正規表現の末尾以外ではそのまま $
を表します。
[]で囲まれた文字クラス
[]
で囲まれた 文字クラス というアトムもあります。[]
内の複数の文字が一つのアトムとみなされます。文字クラスは、基本的に、[]
内の複数の文字をそのままマッチの候補リストだと考えます。その候補リストのいずれかの文字にマッチするアトムだということです。例えば、[1b]
という文字クラスは、abcd
にも a1cd
にもマッチします。[^1b]
のように ^
で始めると、意味が反転されて、1
と b
以外のすべての文字という意味になります。文字クラスでは文字の範囲を指定することもできます。例えば [0-9]
は0から9の数字にマッチし、[a-z]
は小文字のアルファベットにマッチします。文字の範囲や比較方法は言語設定(ロケール)によって異なるので、ASCII文字以外を使用する場合には気をつけてください。
文字クラスはクラス名で指定することもできます。次のようなクラス名があります。
[:alnum:]
-
アルファベットと数字
[:alpha:]
-
アルファベット
[:ascii:]
-
ASCII文字
[:blank:]
-
スペースとタブ
[:cntrl:]
-
制御文字
[:digit:]
-
数字(0から9)
[:graph:]
-
スペースを除く印刷可能文字
[:lower:]
-
小文字
[:print:]
-
スペースを含む印刷可能文字
[:punct:]
-
句読記号(!"#$%&'()*+,-./:;<⇒?@[\]^_`{|}~)
[:space:]
-
スペース、フォームフィード(
\f
)、改行(\n
)、キャリッジリターン(\r
)、水平タブ(\t
)、垂直タブ(\v
) [:upper:]
-
大文字
[:xdigit:]
-
16進数の数字(0からF)
クラス名は、文字や範囲と併用できますが、範囲の終わりとして使うことはできません。また、クラス名を []
の外で使うことはできず、単なる文字列として扱われます。つまり、クラス名を使用する場合には、[[:digit:]]
のように、二重の角かっこで囲むことになります。
量指定子
アトムの繰返し回数を指定する 量指定子 の概念を使うと、複雑なパータンをシンプルに指定することができます。量指定子は直前のアトム(文字そのもの、ドット、文字クラスのいずれでも)が何回連続して現れるかを指定し、条件に一致する文字列ににマッチします。マッチした部分文字列を ピース(piece) と呼ぶことがあります(訳注: 日本では意味のまま マッチ や 一致 と呼ばれることのほうが多いようです)。
POSIXで規格化されている正規表現には、 “基本” 正規表現と “拡張” 正規表現の二種類があります。Linuxのテキスト関連プログラムの多くでは拡張正規表現が使えます(オプション指定が必要なものもあります)。表記方法の違いを知っておいたほうがよいですが、拡張正規表現を覚えてしまえば、あえて基本正規表現を使用する機会はほとんどありません。
基本正規表現の量指定子では、*
(アスタリスク)のみが使えます。直前のアトム(文字そのもの、ドット、文字クラスのいずれでも)の 0回以上 の繰り返しを意味します。0回以上ですから、文字がなくてもマッチすることに気をつけてください。例えば .*
(ドット・アスタリスク)は、文字や記号が何個あってもいいし全くなくてもいいという意味になります。
拡張正規表現の量指定子では、アスタリスクに加えて +
(プラス)と ?
(クエスチョンマーク)が使えます。プラスは 1回以上 の、クエスチョンマークは 0回ないし1回 の繰り返しを意味します。
いずれも、量指定子としての特別な意味を打ち消してそのままの文字として扱いたいときは、直前に \
(バックスラッシュ)を置いてエスケープします。また、正規表現の先頭にこれらの文字が置かれた場合には、前にアトムがないのでそのままの文字として扱われます。
繰り返し指定
具体的な繰り返し回数を指定する量指定子も使えます。{i,j} (iとjは整数)と指定することで、i文字以上、j文字以下 の繰り返しを意味します。jを省略したケースを含めて、3つの指定方法があります。
{i}
-
直前のアトムをちょうど
i
回繰り返すことを示します。例えば、[[:blank:]]{2}
は2個の連続する空白文字(スペースないしタブ)にマッチします。 {i,}
-
直前のアトムを少なくとも
i
回繰り返すことを示します。例えば、[[:blank:]]{2,}
は、2個以上の連続する空白文字(スペースないしタブ)にマッチします。 {i,j}
-
直前のアトムを
i
回以上j
回以下繰り返すことを示します。(j
はi
より大きい)。例えば、xyz{2,4}
は、xy
の後にz
が2〜4個連続する場合にマッチします。
量指定子を含む正規表現では、先頭から検索を始めて、最も長い部分文字列がマッチしたと判定されます。
基本正規表現でも繰り返し指定を使うことができますが、\{
や \}
のように \
を付けなければなりません。{
や }
だけではそのまま {
や }
と解釈されます。\{
の後に数字以外の文字があると、繰り返し指定ではなく、文字 {
と解釈されます。
選択とグループ化
いくつかの文字列 のどれかとの一致を探したいときには、文字列を |
(パイプ)で区切る 選択
(ブランチと呼ぶこともある)を使います。例えば、he|him
は、対象文字列の中に he
か him
があればマッチします。なお、パイプによる選択は、基本正規表現では使えません(\|
で使えるものもあります)。
文字列の前後にさらに別の文字列や正規表現がある場合には、文字列の範囲を明示することが必要なことがあります。( )
で文字列やパターンを囲んでグループ化すると、囲まれた範囲が1つのアトムになります。例えば (AB){3}
はABABABにマッチします。また、(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[[:blank:]]\[[:digit:]]
と書くと、3文字月名のいずれかの後にスペースないしタブが1つ以上あり、その後に1つ以上の数字が続く文字列(Mar 15など)にマッチします。
( )
によるグループ化には、囲まれた範囲を1つのアトムにすることに加えて、後方参照 を可能にするという重要な意味があります。正規表現中の ( )
で囲まれたグループには、左から順に 1,2,3… と番号が振られます。そして、正規表現の後の部分で \1
, \2
, \3
と番号の前にバックスラッシュを置くと、対応する グループにマッチした文字列 と同じ文字列とのマッチを示します。例えば、([[:digit:]]{3})ABCD\1
は、3桁の数値の後にABCDがあり、その後に一致した3桁の数字が繰り返される文字列(351ABCD351など)にマッチします。
なお、基本正規表現では、単純な (
と )
ではなく、エスケープした \(
と \)
で囲まなければなりません。(
や )
はそのまま (
や )
を表します。\1
, \2
, \3
と番号の前にバックスラッシュを置く後方参照については拡張正規表現と同じです。
正規表現を用いた検索
ファイルシステムやテキスト文書を検索する際に正規表現は効力を発揮します。例えば find
コマンドに -regex
オプションを付けると、正規表現を使ってパスの探索ができます。例を見てみましょう。
$ find $HOME -regex '.*/\..*' -size +100M
このコマンドは、ファイルサイズが100メガバイト(104857600バイト)以上のファイルを探索しています。ただし、ユーザーのホームディレクトリ以下で .*/\..*
とマッチするパス、つまり /.
が含まれているパス(/.
の前後に文字があってもよい)だけを探索しています。要するに、隠しファイルか隠しディレクトリ以下のファイルだけが探索対象になるということです。.*
が最も長い文字列に一致しますから、パス中での /.
の位置は問いません。次の例では、-iregex
オプションを指定して、正規表現中で大文字と小文字を区別しないことを指定します。
$ find /usr/share/fonts -regextype posix-extended -iregex '.*(dejavu|liberation).*sans.*(italic|oblique).*' /usr/share/fonts/dejavu/DejaVuSansCondensed-BoldOblique.ttf /usr/share/fonts/dejavu/DejaVuSansCondensed-Oblique.ttf /usr/share/fonts/dejavu/DejaVuSans-BoldOblique.ttf /usr/share/fonts/dejavu/DejaVuSans-Oblique.ttf /usr/share/fonts/dejavu/DejaVuSansMono-BoldOblique.ttf /usr/share/fonts/dejavu/DejaVuSansMono-Oblique.ttf /usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf /usr/share/fonts/liberation/LiberationSans-Italic.ttf
この例では、/usr/share/fonts
ディレクトリ以下で指定のフォントファイルだけを見つけるために、拡張 正規表現のパイプによる選択を使っています。find
コマンドのデフォルトでは拡張正規表現になりませんが、-regextype posix-extended
か -regextype egrep
オプションを指定すると、拡張正規表現が使用できます。find
コマンドのデフォルトの正規表現は、findutils-default というものであり、基本正規表現とほとんど同じです。
コマンドの出力が1画面に収まりきらないときは、less
コマンドを使うのが普通です。less
を使えば、ユーザーはファイルの好きな場所に移動できますし、正規表現を使った検索をすることもできます。この機能はとても重要です。というのも、systemdジャーナルを探ったりマニュアルを参照したりするときなどには、less
がデフォルトのページャになっていることが多いからです。例えば、マニュアルを参照しているときに/(スラッシュ)を押すと検索プロンプトが表示されますが、ここで正規表現が役立ちます。あるオプションを探したいとしましょう。マニュアルの中にはオプション自体が何度も登場するのが普通ですから、例えば -o
オプションを探すのに -o
とだけ指定するといくつもマッチしてしまいます。オプション自体の説明箇所では、行頭からいくつかの空白があり、その後にオプションが書かれていることが多いので、プロンプトに ^[[:blank:]]*-o
やもっと単純に ^ *-o
と入力すれば、その行に一発で行き着けます。
演習
-
egrep
コマンドでinfo@example.org
のような一般的なメールアドレスにマッチする拡張正規表現を書いてください。 -
egrep
コマンドで192.168.15.1
のような標準的なドット区切り十進表記のIPv4アドレスにマッチする拡張正規表現を書いてください。 -
grep
コマンドで/etc/services
ファイルのコメント行(#
で始まる行)以外の中身を表示してください。 -
domains.txt
ファイルにドメイン名が一行に1つずつ書かれているとします。egrep
コマンドで.org
ドメインか.com
ドメインのものだけを表示してください。
発展演習
-
find
コマンドで拡張正規表現を使い、カレントディレクトリ以下でサフィックスを含まないファイル(ファイル名が.txt
や.c
などで終わらないファイル)を探してください。 -
less
はシェル環境で長いテキストを表示する際のデフォルトのページャです。/
を押すと、検索プロンプトに正規表現を入力でき、最初にマッチした部分にジャンプします。ジャンプせずに文書中の現在の場所のままでマッチした部分をハイライトするには、検索プロンプトでどのキーの組み合わせを入力すればよいでしょうか。 -
less
で出力をフィルタリングして正規表現にマッチした行だけ表示するにはどうすればよいでしょうか。
まとめ
このレッスンではLinuxで一般的な正規表現について学びました。テキスト関連のプログラムでは正規表現のパターンマッチ機能を使えることがほとんどです。このレッスンは以下の順で取り上げました。
-
正規表現とは
-
正規表現の主な構成要素
-
基本正規表現と拡張正規表現の違い
-
正規表現を用いたテキストやファイルの検索
演習の解答
-
egrep
コマンドでinfo@example.org
のような一般的なメールアドレスにマッチする拡張正規表現を書いてください。egrep -i "[a-z][a-z0-9_+\.\-]+@([a-z0-9][a-z0-9\-]*[a-z0-9]*\.)+[a-z]{2,}"
(訳注: メールアドレスの規格上の仕様は複雑であり、この正規表現は「正しくない」一部のメールアドレスにもマッチします。)
-
egrep
コマンドで192.168.15.1
のような標準的なドット区切り十進表記のIPv4アドレスにマッチする拡張正規表現を書いてください。egrep "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
-
grep
コマンドで/etc/services
ファイルのコメント行(#
で始まる行)以外の中身を表示してください。grep -v ^# /etc/services
-
domains.txt
ファイルにドメイン名が一行に1つずつ書かれているとします。egrep
コマンドで.org
ドメインか.com
ドメインのものだけを表示してください。egrep ".org$|.com$" domains.txt
発展演習の解答
-
find
コマンドで拡張正規表現を使い、カレントディレクトリ以下でサフィックスを含まないファイル(ファイル名が.txt
や.c
などで終わらないファイル)を探してください。find . -type f -regextype egrep -not -regex '.*\.[[:alnum:]]{1,}$'
-
less
はシェル環境で長いテキストを表示する際のデフォルトのページャです。/
を押すと、検索プロンプトに正規表現を入力でき、最初にマッチした部分にジャンプします。ジャンプせずに文書中の現在の場所のままでマッチした部分をハイライトするには、検索プロンプトでどのキーの組み合わせを入力すればよいでしょうか。/ を押してから正規表現を入力する前にCtrl+Kを押します。
-
less
で出力をフィルタリングして正規表現にマッチした行だけ表示するにはどうすればよいでしょうか。&を押してから正規表現を入力します。