
本記事は「ものづくりのためのLinux講座」のうちの正規表現に関する内容です。
正規表現という言葉・・・正直、カタイですよね。
字面から難しそうな感じがして放置していたという方も、このページを読んでいただければ、どんなふうに活用できるのかをイメージできると思います。
端的に言うと、正規表現は「柔軟な文字列検索」のための表現方法になります。
よく使う機能を短期間で使いこなせるようになることを目指した内容になっているため、すべての機能を網羅していません。
正規表現一覧
以下に正規表現の一覧を示します。
正規表現が誕生してから、さらに便利になるように拡張されてきた歴史があるため、以下のようにコマンドによって対応/非対応が分かれています。
| 正規表現 | 説明 | grep, sed | sed -E, awk | grep -E, egrep, perl |
|---|---|---|---|---|
| . | 任意の1文字 | ○ | ○ | ○ |
| * | 直前の文字の0個以上の繰り返し | ○ | ○ | ○ |
| + | 直前の文字の1個以上の繰り返し | ー | ○ | ○ |
| ? | 直前の文字の0個または1個 | ー | ○ | ○ |
| ^ | 文字列の先頭 | ○ | ○ | ○ |
| $ | 文字列の末尾 | ○ | ○ | ○ |
| | | OR検索 | ー | ○ | ○ |
| \ | 直後の1文字を通常の文字として扱う(\d, \D, \w, \W, \s, \S, \1〜\9に機能がある場合を除く) | ○ | ○ | ○ |
| [文字列] | [〜]の中の1文字、a-cのように範囲指定可能 | ○ | ○ | ○ |
| [^文字列] | [^〜]の中の文字以外の1文字、a-cのように範囲指定可能 | ○ | ○ | ○ |
| (文字列) | 文字列をグループ化、(〜)内を|で区切ることで複数の文字列のうちいずれかという表現が可能 | ー | ○ | ○ |
| \d | 数字1文字、[0-9]と同じ | ー | ー | ○ |
| \D | 数字以外の1文字、[^0-9]と同じ | ー | ー | ○ |
| \w | 数字、アルファベット、_のいずれか1文字、[0-9a-zA-Z_]と同じ | ー | ー | ○ |
| \W | 数字、アルファベット、_以外の1文字、[^0-9a-zA-Z_]と同じ | ー | ー | ○ |
| \s | スペース、タブ、改行などの空白1文字、[ \t\n\r\f]と同じ | ー | ー | ○ |
| \S | スペース、タブ、改行などの空白以外の1文字、[^ \t\n\r\f]と同じ | ー | ー | ○ |
sed, grepで使える表現を単純正規表現、それ以外の後から追加されたものを拡張正規表現と呼ぶことがあります。
呼び名はあまり重要ではありませんが、上記の対応/非対応の違いがあることは覚えておくと、実際に使用する際に役に立ちます。
置換時に機能する表現
文字列置換の際に、マッチした文字列を利用する表現を以下に示します。
| 表現 | 説明 | sed | sed -E | awk | perl |
|---|---|---|---|---|---|
| & | マッチした文字列全体 | ○ | ○ | ー | ー |
| \1 〜 \9 | ()にマッチした文字列を置換文字列中で使用 | ○※ | ○ | ー | ○ |
| $1 〜 $9 | ()にマッチした文字列を次以降の処理で使用 | ー | ー | ー | ○ |
※sedでは ( 〜 ) ではなく、\( 〜 \) にマッチした文字列を後方参照可能。ただし、\( 〜 \) 内に | を入れてORにすることはできない。ORでマッチさせたい場合はsed -Eやperlを使うと良い。
awkでは、matchやsubstrを使ってマッチした文字列を取り出すことができます(sedの&相当の機能)が、以降に記載するawkの使用例のように、記述が煩雑で機能も限定されているため、sed -Eやperlを使う方が良いです。
使用例
grep
以下のようにして検索を行うことができます。
grep '正規表現' 入力ファイル
例えば、以下の内容のファイルがtest1.txtとして存在していた場合に、
abc abc 123 ABC
以下のコマンドを実行します。
grep '[a-cB]' test.txt
引数に与えた正規表現'[a-cB]'は、「a〜cまたはBのうちの1文字」を表しており、それが含まれる行が以下のように出力されます。
abc abc ABC
正規表現部分は、ダブルクオート”〜”よりシングルクオート’〜’でくくるのがおすすめです。
シングルクオートでくくることで、単なる文字列として扱われ、シェルのコマンドラインで!や*などが評価されて別の文字列に置き換わってしまうことを防げます。
アルファベットの大文字/小文字を区別せずに検索したい場合は、以下のように-iオプションを付けると良いです。
grep -i '[a-c]' test1.txt
上記は、大文字/小文字区別せずにa〜cの文字を検索することを表しています。
こちらの結果も以下のようになります。
abc abc ABC
egrep
以下のようにして検索を行うことができます。
egrep '正規表現' 入力ファイル
egrepは、+, ?, ()が使えます。
例えば、以下の内容のファイルがtext2.txtとして存在していた場合に、
abc def 123 df XXX
以下のコマンドを実行します。
egrep '(abc|123) +de?f' test2.txt
引数に与えた正規表現'(abc|123) +de?f'は「abcまたは123に続いて、空白が1つ以上、d、eが0個または1個、fとなる文字列」を表しています。
以下のように出力されます。
abc def 123 df
sed
sedは正規表現を使用した置換を行うコマンドです。
正規表現は置換を行うときに、より威力を発揮します。
最も基本的な使い方は以下となります。
sed 's/正規表現/置換文字列/' ファイル
例えば以下の内容のファイルがtest7.txtとして存在していた場合、
abc def xyz 123: define default ABC, DEF, GHI
次のコマンドを実行すると、
sed 's/def/@@@/' test7.txt
以下のようにdefの部分が2箇所@@@に置換されます。
abc @@@ xyz 123: @@@ine default ABC, DEF, GHI
defが存在しない行は、元のまま出力されます。
ここで、2行目のdefaultに含まれるdefの部分が置き換わっていないことにお気づきと思います。
上記のコマンドでは、行頭から検索して最初にマッチした部分だけ置き換わるためです。
1行に複数箇所マッチした場合、すべて置き換えるには以下のように最後にgを追加したコマンドにします。
sed 's/def/@@@/g' test7.txt
すると以下のように、すべて置き換えるようになります。
abc @@@ xyz 123: @@@ine @@@ault ABC, DEF, GHI
大文字のDEFも置き換えたいという場合、以下のようにiを追加したコマンドにすると、大文字小文字を区別せずに検索するようになります。
sed 's/def/@@@/gi' test7.txt
giの部分はigとしても、どちらでも大丈夫です。
出力結果は以下となり、大文字DEFの部分も置き換わります。
abc @@@ xyz 123: @@@ine @@@ault ABC, @@@, GHI
次以降、もう少し正規表現らしい置換に挑戦していきましょう。
「defが存在した行の末尾に@@@を追記する」という置換を考えてみましょう。
sed 's/^.*def.*$/&@@@/' test7.txt
上記の正規表現は、「行頭から何でも良い1文字が0個以上あり、def、さらに何でも良い1文字が0個以上続いて行末まで」という意味です。
つまり、defがある行は、defの前後に何が書かれていても行頭から行末までの1行全体がマッチすることになります。
そしてマッチした行の内容は変更せず、行末に@@@を追記したいというのが、今回やりたい内容になるので、置き換え文字列に”&”を使うことでマッチした文字列全体を再利用し、それに続けて@@@とすることで行末への追記を実現しています。
出力結果は以下になります。
abc def xyz@@@ 123: define ZZZ@@@ ABC, DEF, GHI
何か探している行にマーカーを付けたい場合などにこのような置換が役に立ちます。
もう少し違う例で正規表現を練習していきましょう!
例えば、以下の内容のファイルtest7.txtに対し、
abc def xyz 123: define default ABC, DEF, GHI
defの部分は@@@に置き換えたいけれど、defineやdefaultの部分は置き換えたくない場合、どのように正規表現を書いたら良いでしょうか?
以下はうまくいく一例です。
sed 's/def /@@@ /' test7.txt
上記の正規表現はdefに続く空白を含めて検索し、置き換える文字列にも空白を入れています。
出力結果は以下のようになり、期待通りdefの部分は置き換わり、defineとdefaultの部分はそのままです。
abc @@@ xyz 123: define default ABC, DEF, GHI
上で一例と書いたのは、これを実現するには他にも方法があるからです。
次は「defに続く文字が、アルファベット、数字、アンダースコア”_”以外のときに変換する」という正規表現にしてみましょう。
(〜)を使うので-Eオプションを使います。
sed -E 's/def([^A-Za-z0-9_]+)/@@@\1/' test7.txt
上記のコマンドは、defに続く文字がA〜Z, a〜z, 0〜9, _以外の1つ以上の連続となる箇所を検索し、defに続く文字は(〜)でくくって取り出しておき、置き換え文字列の中で\1として再利用しています。
この場合の出力も同様で以下となります。
abc @@@ xyz 123: define default ABC, DEF, GHI
上記コマンドだとdefに続く文字はカンマ”,”やセミコロン”;”などでも動作します。
defに続く文字は元のままの文字を使いたいので、(〜)を使って取り出し、置き換え時に\1で再利用するということです。
正規表現に(〜)を複数書いた場合、左から順に\1, \2, …, \9として置き換え文字列内で使用できます。
(〜)内は|で区切ってOR条件にすることもできます。
上記の変換に加えて、「abcやABCをそれに続く空白やカンマと一緒に消してしまいたい」という場合、-e コマンドを複数指定することで、複数の変換処理を指定することができます。
sed -E -e 's/def([^A-Za-z0-9_]+)/@@@\1/' -e 's/abc[ ,]+//i' test7.txt
2つ目のコマンドの方は置き換え文字列を空にすることで削除を実現しており、出力結果は以下となります。
@@@ xyz 123: define default DEF, GHI
1行目の”abc “と3行目の”ABC, “が消えていますね。
次にもう1つ、検索文字や置換対象にスラッシュ”/”が多い場合のテクニックについて説明します。
例えば、以下の内容のファイルtest8.txtが存在したとして、
1: /home/user/work/file1.txt 2: ./file2.html 3: abc/xyz/file3.c
次のコマンドを実行すると、
sed 's/\/[^\/]*\//\/@@@\//g' test8.txt
以下のように出力されます。
1: /@@@/user/@@@/file1.txt 2: ./file2.html 3: abc/@@@/file3.c
コマンドを見ると何やら複雑でわかりにくいですが、置換結果を見た方が処理内容が理解しやすいと思います。
ここでは、/〜/を/@@@/に置き換えています。
スラッシュ”/”はコマンド's/正規表現/置換文字列/'の区切り文字のため、そのまま”/”と書くことができず、直前にバックスラッシュを付けて”\/”として、区切りではなく通常の文字であることを表現する必要があります。
そして、/〜/の〜の部分はスラッシュ以外の文字としたいため、[^\/]という表現になります。
gオプションを付けてマッチした箇所すべてを置換するようにしているのに、1行目の/user/の部分が置き換わらないのはなぜ?と思った方もいらっしゃるでしょう。
これは、直前の/home/にマッチした後、次の検索はuserのuの文字から始まるためになります。
さて、やっている内容が理解できたところで、これをもっと見やすく表現する方法を紹介します。
正規表現と置換文字列の区切りは、任意の文字にすることができます。
以下は”#”を区切り文字にする例です。
sed 's#/[^/]*/#/@@@/#g' test8.txt
これでも置換結果は同じになります。
sの次に来る文字を区切り文字と認識するようになっています。
先のコマンドよりは格段に理解しやすいですよね。
このように検索対象や置換文字列にスラッシュが多い場合は、区切り文字をスラッシュ以外の文字にすると、バックスラッシュを付ける頻度が減って見やすくなります。
スラッシュ以外の区切り文字として”#”や”@”がよく使われます。
awk
awkは多機能なコマンドですが、ここでは正規表現を使用した検索と置換の機能に絞って紹介します。
以下のようにして検索を行うことができます。
awk '/正規表現/{print}' 入力ファイル
例えば、以下の内容のファイルtest2.txtが存在したとして、
abc def 123 df XXX
以下のコマンドを実行すると、
awk '/(abc|123) +de?f/{print}' test2.txt
以下のように出力されます。
abc def 123 df
続いて、awkで置換を実行する方法を紹介します。
awkでsubとgsubを使って置換ができます。
subは行頭から検索して最初にマッチしたものだけ置換、gsubはマッチするものすべてを置換します。
以下subの記述例です。(gsubも同様)
awk '{sub("正規表現","置換文字列");print}' 入力ファイル
以下の内容のファイルtest7.txtが存在したとして、
abc def xyz 123: define default ABC, DEF, GHI
defを@@@に置き換える処理は以下となります。
awk '{sub("def","@@@");print}' test7.txt
sub("正規表現","置換文字列");の部分で文字列変換を行い、続くprintでその行を出力するという内容で、出力結果は以下です。
abc @@@ xyz 123: @@@ine default ABC, DEF, GHI
1行に複数置換対象がある場合にはgsubを使います。
awk '{gsub("def","@@@");print}' test7.txt
出力結果は以下になります。
abc @@@ xyz 123: @@@ine @@@ault ABC, DEF, GHI
awkのsub, gsubでは、sedで使用できたマッチ結果の再利用ができません。
defは置き換え、defineやdefaultは置き換えたくない場合、以下のように(〜)は使えるのですが、記憶しておいて置き換え文字列に再利用することができないので、仕方なく”@@@ “(最後が空白)に置き換えています。
awk '{sub("def([^A-Za-z0-9_])", "@@@ ");print}' test7.txt
出力結果は以下となり、この入力に対してはうまくいきます。
abc @@@ xyz 123: define default ABC, DEF, GHI
しかし、仮にdefに続く1文字がセミコロンだった場合、セミコロンは空白に置き換わってしまいます。
awkで検索結果を再利用(後方参照)するためにmatchという機能が用意されています。
def直後の1文字を取り出して再利用するには以下のコマンドを実行します。
awk '{match($0,/def[^A-Za-z0-9_]/);m=substr($0,RSTART+3,RLENGTH-3);sub("def[^A-Za-z0-9_]","@@@"m);print}' test7.txt
こんなに長いの?と思いますよね・・・
出力結果は以下となります。
abc @@@ xyz 123: define default ABC, DEF, GHI
match(検索対象,/正規表現/)でマッチングを行い、マッチした最初の文字数をRSTARTに、マッチした長さをRLENGTHに格納します。
検索対象とした$0とは1行全体のことを表します。
次のsubstr(対象文字列,開始位置,文字列長さ)で部分文字列を取り出して変数mに代入するのですが、matchは(〜)を部分的に取り出すことができず、defと次の1文字がマッチします。
defに続く空白を取り出したいがために、RSTARTには+3、RLENGTHには-3をしてdefの次の1文字を取り出してmに格納しています。
その後、subで置き換え文字列にmを使って所望の機能を実現しています。
awkには検索、置換の他に様々な機能があり、それらを使ってプログラミングするようなケースでawkを使用するのが良いです。
awkを単に検索や置換に使う場合、記述が長い、後方参照機能が使いづらい、などのデメリットがあるため、egrepやsedを使う方が便利です。
perl
perlもawkと同様に多機能ですが、ここでは正規表現を利用した検索と置換の機能に絞って説明します。
以下のようにして検索を行うことができます。
perl -ne 'if(/正規表現/){print}' 入力ファイル
上記は-eオプションでコマンドラインからperlスクリプトを入力できるようにし、-nオプションでスクリプト全体をwhile(<>){ 〜 }でくくったことにする機能を使っています。
これは、入力ファイルから1行ずつ読み込んで処理を行うということになります。
例えば、以下の内容のファイルtest2.txtが存在したとして、
abc def 123 df XXX
以下のコマンドを実行すると、
perl -ne 'if(/(abc|123)\s+\w+/){print}' test2.txt
abcまたは123に続く1個以上の空白文字、さらに1個以上のワード文字(A〜Z, a〜z, 0〜9, _)が続く箇所がある行が出力されます。
abc def 123 df
次はperlの置換機能です。
perl -pe 's/正規表現/置換文字列/' 入力ファイル
上記は-eオプションでコマンドラインからperlスクリプトを入力できるようにし、-pオプションでスクリプト全体をwhile(<>){ 〜 ;print}でくくったことにする機能を使っています。
検索の実行例で使用した-neと今回の-peの違いは、1行ずつ読み込んでループする際、最後にprintするかどうかです。
コマンドの形式がほとんどsedと同じですね。
perlでは、sed -Eでも使用できない\d, \D, \w, \W, \s, \Sが使えるので、これらの表現を使って簡潔に置換を表現したい場合に便利です。
以下の内容のファイルtest7.txtが存在したとして、
abc def xyz 123: define default ABC, DEF, GHI
以下のコマンドを実行すると、
perl -pe 's/def(\s+)/@@@\1/' test7.txt
以下のように出力されます。
abc @@@ xyz 123: define default ABC, DEF, GHI
defに続く1文字以上の空白文字を探して\1に格納し、@@@と\1の内容をつなげた文字列に置き換えています。
なお、sedと同様に区切りを任意の文字に変えて's#正規表現#置換文字列#'のようにすることができます。
文献紹介
奥の深い正規表現の世界を余すところなく解説してくれる一冊で、さまざまなツールにおける使用例もあり、正規表現を極めたい方向けの書籍です。
UNIX/Linuxのコマンドラインを使いこなす上で必須となるsedとawkのノウハウが詰まった、バイブルとも言える一冊です。
定番のPerl入門書であり、実用上十分なノウハウがつまったバイブル的な一冊です。
Linuxの基本的な使い方からシェルスクリプトを使ったプログラミング、gitによるバージョン管理など、エンジニアが知っておくべき基本的な知識について解説してくれています。