正規表現

正規表現とは、対象のテキストの中から目的の文字列を検索する際に用いる表現のこと。Linuxコマンドにおける正規表現は、主にログなどのテキストファイルやコマンドの出力に対して使う。


概要、使い方

    正規表現の一覧

    具体例


    正規表現とは

    正規表現とは、対象のテキストの中から目的の文字列を検索する際に用いる表現のこと。例えば(りんご|みかん)ジュースという正規表現によって、テキストの中から「りんごジュース」か「みかんジュース」を検索することができる。

    正規表現は特に、予め確定していないが一定の規則がある文字列を探すのに役立つ。例えばメールアドレスなど。

    具体的な正規表現をパターンと言ったり、それがテキストの文字列と一致することをマッチすると言う。


    行単位で検索・置換

    Linuxコマンドにおける正規表現は、主にログなどのテキストファイルやコマンドの出力に対して使う。これらは行単位に情報がまとめられており、正規表現は基本的に行単位で検索・置換を行う。


    コマンドが正規表現を処理する

    正規表現はシェルによるパス展開の表現と一部似ているが、全く別物。パス展開ではパターンを"'で囲まず、それをシェルが処理するのに対して、正規表現では囲ったものを引数として渡し(囲まなくても機能する表現が多いが)、それをコマンドが処理する。

    主に使うコマンドはgrepegrepsedコマンド。


    基本正規表現と拡張正規表現

    正規表現は基本正規表現と拡張正規表現があり、混乱しやすい。例えば基本正規表現のa\+というパターンは、拡張正規表現ではa+と書く。ここでは原則として拡張正規表現を使う

    基本正規表現のコマンドはgrepsed、拡張正規表現はegrep(grep -Eと同じ)、sed -rであるが、ここでは原則としてegrepコマンドを使う

    例外として、最短一致再利用しないグループはPerlの正規表現(grep --perl-regexp)を使っている。(man grepによると実験的というので注意)


    egrepコマンドの基本

    egrepコマンドは、ファイルやコマンド出力に対して次のように使う。

    ファイルの例

    egrep '正規表現' file

    コマンド出力の例

    echo '文字列' | egrep '正規表現'

    --only-matchingオプション

    マッチしたらその行全体が出力されるのがデフォルトだが、--only-matchingオプションはマッチした部分のみを、マッチごとに1行ずつ出力する。

    # インプット
    input="りんごジュース
    困りんご"
    
    echo "$input" | egrep --only-matching 'りんご'
    りんご
    りんご
    

    . 任意の一文字

    .は任意の一文字にマッチ。スペースやタブ一つはマッチするが、改行にはマッチしない。

    input="リンゴ
    赤い。
    このリンゴは赤いし、あのリンゴも赤い。
    そのリンゴ赤い。
    どのリンゴ 赤い。"
    
    echo "$input" | egrep --only-matching 'リンゴ.赤い'
    リンゴは赤い
    リンゴも赤い
    リンゴ 赤い
    

    [] 文字クラスの一文字

    []で囲まれた文字や文字クラス(文字の集合)のいずれか一文字にマッチ。

    echo '赤色 緑色 青色 青春' | egrep --only-matching '[赤青]色'
    赤色
    青色
    

    上記では一つずつ文字を指定したが、範囲表現角括弧表現による文字クラスも追加できる。

    文字クラスで特殊文字として意味を持つ^-]を単なる文字として入れる場合は、^は先頭以外に置くかエスケープし、-は末尾に、]は先頭に置く。

    echo '- ^ ]' | egrep '[]^-]'
    - ^ ]
    

    [^] 否定文字クラスの一文字

    [^]で囲まれた文字や文字クラス(文字の集合)のそれ以外の一文字にマッチ。

    echo '赤色 緑色 青色 緑化' | egrep --only-matching '[^赤青]色'
    緑色
    

    上記では一つずつ文字を指定したが、範囲表現角括弧表現による文字クラスも追加できる。


    - 範囲表現

    -で文字範囲を柔軟に指定でき、[](文字クラス)や[^](否定文字クラス)内に入れて使う。

    範囲表現の例
    正規表現 意味
    [0-9] 数字
    [1-3] 123のどれか
    [a-z] 英字の小文字
    [A-Z] 英字の大文字
    [a-cA-C1-3] abcABC123のどれか
    [ぁ-んァ-ヴー] ひらがな、カタカナ、ー(長音)
    echo '0aAぁあァア' | egrep --only-matching '[ぁ-ん]'
    ぁ
    あ
    

    [::] 角括弧表現

    [:文字種類:]という角括弧表現で英字・数字・記号といった文字の種類を指定でき、[](文字クラス)や[^](否定文字クラス)内に入れて使う。そのため実際は[[:文字種類:]][^[:文字種類:]]というように括弧が二重になる。

    角括弧表現の例
    正規表現(外側括弧含む) 意味
    [[:alnum:]] 英数字。[0-9A-Za-z]と同じ。
    [[:alpha:]] 英字。[A-Za-z]と同じ。
    [[:digit:]] 数字。[0-9]と同じ。
    [[:lower:]] 英字の小文字。[a-z]と同じ。
    [[:upper:]] 英字の大文字。[A-Z]と同じ。
    [[:blank:]] スペースとタブ
    [[:punct:]] 記号 ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
    [[:xdigit:]] 16進数。[0-9A-Fa-f]と同じ。

    詳しくはinfo -f 'grep' -n 'Character Classes and Bracket Expressions'のマニュアルで。

    echo '0aAぁあァア' | egrep --only-matching '[[:lower:][:digit:]]'
    0
    a
    

    否定文字クラスを使うと、

    echo '0aAぁあァア' | egrep --only-matching '[^[:lower:][:digit:]]'
    A
    ぁ
    あ
    ァ
    ア
    

    ? 直前の文字が0回または1回

    ?は、その直前文字の0回または1回出現にマッチ。

    echo '見られる 見れる' | egrep --only-matching '見ら?れる'
    見られる
    見れる
    

    * 直前の文字が0回以上

    *は、その直前文字の0回以上出現にマッチ。

    echo '嫌 嫌ぁ 嫌ぁぁ' | egrep --only-matching '嫌ぁ*'
    嫌
    嫌ぁ
    嫌ぁぁ
    

    + 直前の文字が1回以上

    +は、その直前文字の1回以上出現にマッチ。

    echo '嫌 嫌ぁ 嫌ぁぁ' | egrep --only-matching '嫌ぁ+'
    嫌ぁ
    嫌ぁぁ
    

    {} 繰り返し

    {最小,最大}という形式で0以上の数値を指定すると、その直前文字の繰り返しを柔軟に表現できる。

    繰り返し表現
    意味
    {5} 5回の繰り返し部分にマッチ
    {0,5} 0回以上5回以下の繰り返し部分にマッチ
    {5,} 5回以上の繰り返しにマッチ
    {,5} 5回以下の繰り返し部分にマッチ

    これは文字列が6回の繰り返しであってもその5回までの部分にマッチするという意味。

    echo '嫌 嫌ぁぁ 嫌ぁぁぁぁぁぁ' | egrep --only-matching '嫌ぁ{2,5}'
    嫌ぁぁ
    嫌ぁぁぁぁぁ
    

    ? 最短一致

    +*などの繰り返しの表現について、grepコマンドは最長一致(できるだけ長い文字列にマッチさせる)がデフォルト。例えば、嫌ぁ+という正規表現は「嫌ぁ」も「嫌ぁぁ」もマッチするが、なるべく長くマッチさせようとするのが最長一致。

    echo '嫌ぁぁぁぁぁぁ' | egrep --only-matching '嫌ぁ+'
    嫌ぁぁぁぁぁぁ
    

    これに対して、最短一致はなるべく短くマッチさせようとするもので、繰り返し表現の後ろに?を付加する。ただし、最短一致はegrep(grep -E)ではできず、grep --perl-regexp(Perlの正規表現)で行う。

    echo '嫌ぁぁぁぁぁぁ' | grep --perl-regexp --only-matching '嫌ぁ+?'
    嫌ぁ
    

    ^ 行頭

    ^は行頭にマッチ。行頭にある文字列にマッチさせるときに使う。

    input="インデントなし
      インデントあり"
    
    echo "$input" | egrep '^インデント'
    インデントなし
    

    $ 行末

    $は行末にマッチ。行末にある文字列にマッチさせるときに使う。

    input="これは行末
    行末じゃない"
    
    echo "$input" | egrep '行末$'
    これは行末
    

    () グループ

    ()内に入れた文字列はグループになり、グループに対する正規表現を使える。

    echo 'りんご りんごジュース' | egrep --only-matching 'りんご(ジュース)?'
    りんご
    りんごジュース
    

    ?によって直前のグループの0回または1回出現にマッチする。

    また、グループは後方参照で再利用できる。


    (?:) 再利用しないグループ (Non-capturing group)

    grep --perl-regexpで使える。(?:表現)の表現は、上のグループと違って記憶されないので後方参照には使えない。というより、括弧が増えてくると後方参照で使う番号が難解になるので、再利用しないものはこの表現を使う。

    下記で\1は二つ目の括弧の「ジョ」を指す。

    echo 'ぎょぎょとジョジョ' | grep --perl-regexp '(?:ぎょ)+と(ジョ)\1'
    ぎょぎょとジョジョ
    

    egrepではマッチせずエラーにもならないが、sedではエラーになる。

    echo 'ぎょぎょとジョジョ' | sed --regexp-extended --quiet '/(?:ぎょ)+と(ジョ)\1/ p'
    sed: -e expression #1, char 27: 無効な前方正規表現です
    

    | 選択

    |は、これで区切った文字列のいずれかにマッチする。()(グループ)と一緒に使うと柔軟な表現ができる。

    input="りんごジュース いちごジュース みかんジュース"
    
    echo "$input" | egrep --only-matching '(りんご|いちご|ミックス)ジュース'
    りんごジュース
    いちごジュース
    

    \b 単語境界

    \bは、行頭・行末・スペース・記号で区切られた単語境界にマッチ。

    例えば単語の「on」にマッチさせたいとき、それを\bで囲む。

    echo 'bone' | egrep --only-matching '\bon\b'

    上記はマッチしないが、下記はいずれにもマッチする。

    input="on 行頭
    行末 on
    単語 on 単語
    記号 on."
    
    echo "$input" | egrep --only-matching '\bon\b'
    on
    on
    on
    on
    

    \1 後方参照 (Backreferences)

    「前方」と「後方」のここでの意味: 後方(文頭方向)の文字列 → 注目している文字列 → 前方(文末方向)の文字列

    後方参照とは、後方にてあらかじめマッチ済みの表現を、前方から再利用(参照)して再マッチする仕組み。例えば「X語を話すX人」や「Y語を話すY人」にはマッチしたいが「X語を話すY人」にはマッチしたくない、といった柔軟な検索ができる。

    「あらかじめマッチ済みの表現」の正規表現は()で囲ってグループにしておく。その後で前方から後方を参照するには、\1とする。グループが複数ある場合は登場する順番が数値に対応するので、「\数値」の数値を増やしていく。

    グループが一つの後方参照

    input="日本語を話す日本人
    日本語を話す宇宙人
    宇宙語を話す宇宙人"
    
    echo "$input" | egrep '(.+)語を話す\1人'
    日本語を話す日本人
    宇宙語を話す宇宙人
    

    グループが複数の後方参照

    input="たけやぶやけた
    たけやぶもえた"
    
    echo "$input" | egrep '((.)(.)(.))ぶ\4\3\2'
    たけやぶやけた
    

    上記はグループが入れ子になっているが、一番外側の括弧が先に登場したグループになり\1で参照できる(「たけや」を指す)。


    \ エスケープ

    \は、正規表現で使う特殊文字(記号)を無効にして、文字としての記号にマッチさせるときに使う。「\記号」のように記号の直前に付加する。

    echo '* [ \' | egrep --only-matching '\* \[ \\'
    * [ \
    

    メールアドレスの正規表現

    メールアドレスだけの行にマッチさせるとしたら、例えば^[a-z0-9_.-]+@[a-z0-9-]+(\.[a-z0-9-]+)+$。(場合によってはもっと調整が必要)

    input="alice@example.com
    alice._-2@mail-1.example.com
    error@example_.net
    error@example.net."
    
    echo "$input" | egrep '^[a-z0-9_.-]+@[a-z0-9-]+(\.[a-z0-9-]+)+$'
    alice@example.com
    alice._-2@mail-1.example.com