50
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

正規表現/REGEXP/が含まれない行にマッチする正規表現(該当行を削除したい場合に)

Last updated at Posted at 2014-06-19

#結論
末尾の改行までマッチする正規表現です。

  • /^(?!.*REGEXP).*\n/

\nではなく末尾にマッチするならば

  • /^(?!.*REGEXP).*$/

#解説
まずは引用を3件。

##先読み(lookahead)
ある位置から続く文字列が ある部分式にマッチするならばその位置にマッチする という正規表現を書くことができます。
(?=pat) 肯定先読み(positive lookahead)
(?!pat) 否定先読み(negative lookahead)
正規表現|るりま

この文だけで「先読み」が理解できたら天才ですよね。
もうひとつ、るりまから引用。

##アンカー
アンカーは幅0の文字列にマッチするメタ文字列です。 幅0とは文字と文字の間、文字列の先頭、文字列の末尾、 などを意味します。 ある特定の条件を満たす「位置」にマッチします。

  • ^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
  • $ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
  • \A 文字列の先頭にマッチします。
  • \Z 文字列の末尾にマッチします。 ただし文字列の最後の文字が改行ならばそれの手前にマッチします。
  • \z 文字列の末尾にマッチします。
  • \b 単語境界にマッチします。 単語を成す文字と単語を成さない文字の間にマッチします。 文字列の先頭の文字が単語成す文字であれば、文字列の先頭 の位置にマッチします。
  • \B 非単語境界にマッチします。 \bでマッチしない位置にマッチします。

正規表現|るりま

このアンカーの概念が大事になってきます。「 幅0の文字列にマッチするメタ文字列 」です。

##先読み・後読みはアンカー

abcで始まらない任意の文字列を抽出したい場合,/^(?!abc).*/のような正規表現を記述します.

この内容を理解するためには「先読み・後読みはアンカー」という考え方が必要になってきます.
アンカーとは文字列内の特定の位置を表す物であり,文字列の先頭を表す ^ や末尾を表す $ がそれにあたります.普通の正規表現では文字に対してマッチしますが,アンカーは位置に対してマッチします.
###否定的先読み(?!pattern)
とりあえず全ての位置にマッチしておいて,文字列内にpatternが現れると,patternの直前の位置をマッチから外します.つまり,肯定的先読みでマッチする位置以外にマッチします.
正規表現の先読み・後読みを極める! - あらびき日記

文字じゃ無く アンカー にマッチするというイメージが難しい。
結局は上記の通り^$のように、位置へのマッチなんですけど。

##先読み(lookahead)
「(?=pattern)」はゼロ幅の肯定的先読み表現です。 といっても理解できないと思いますが(私も含めて)。
たとえば、ファイルのリストから「[^\\]+(?=.txt)」 で検索を行えば、拡張子がtxtのファイルのファイルタイトルだけを抽出できます。
このように、「hoge(?=pattern)」で patternが後に続くhogeにマッチします。
「(?!pattern)」はゼロ幅の否定的先読み表現です。
「hoge(?!pattern)」のとき、 patternが後に続かないhogeにマッチします。
「(?=pattern)」は ‘pattern’が続く位置にマッチします。
(中略)
この正規表現が示す位置はpatternの直前です。 「(?=.txt)」であれば ‘.’の直前の位置になります。 「(?!.txt))」であれば ‘.’では無い文字の前です。
正規表現の解説 上級編

##/^(?!.*REGEXP).*\n/を解釈してみる
この3つの引用で何となく否定先読みがイメージ出来てきたのでは、と思うのですが、もう一度見直してみましょう。
###否定先読み部分
/^(?!.*REGEXP)/行頭^から任意の文字.の0回以上繰り返し*のあとに目的の正規表現REGEXPない(否定)行行頭 にマッチしています。
もう一度言い直しますが、(原理的には)/.*/で進んだ位置(全ての文字の位置)で/REGEXP/にマッチ しない ことを確認してから 行頭 の位置に 戻って アンカーします。行中に一カ所でも/REGEXP/にマッチすれば、その行はこの否定先読みにマッチしないことになります。
なお、読んでから戻るので「先読み」です。
###後半部分
/.*\n/は、直ぐに分かるかと思いますが、 任意の文字.の0回以上繰り返し*のあとに改行\nがある つまり 改行を含む行全体 にマッチします。
###合わせてみる
全体を合わせると、 行頭^から任意の文字.の0回以上繰り返し*のあとに目的の正規表現REGEXPない(否定)行行頭 から任意の文字.の0回以上繰り返し*のあとに改行\nがある にマッチします。
前半の否定先読み部分でマッチしなかった場合には、行頭に戻り後半で行全体にマッチします。
ということで、目的とする正規表現が完成しています。

#経緯
Rubyのスクリプトscript.rbからclass, module, defから始まる行だけ抜き出して、メソッドやクラスの状況を知りたいと思いました。
正規表現は/^ *\b(module|class|def)\b.+\n/と書けました。文頭からclassなどまでは空白 しかありません(もしくは文頭になる)ので/ */と書きました。classなど以降は名前が続きますので/.+/としました。\bを使っているのは、これらの名前を含むメソッドがあるためです。
Rubyならばワンライナーで

ruby -pe '$_ = $_[/^ *\b(module|class|def)\b.+\n/]' script.rb > result.txt

と書けば目的のresult.txtが得られます。
でもいちいちファイルに保存しないと使えなません。そして普段使っている、正規表現に強いエディタであるCotEditorで処理したかったので、正規表現の否定を行いたかったわけです。

ということで
REGEXP = / *\b(module|class|def)\b/

/^(?!.*REGEXP).*\n/
から組み立てた
/^(?! *\b(module|class|def)\b).*\n/
を使って、CotEditorで不要な行を削除しました。
お分かりのように、否定マッチの条件を若干強めた(/.*// */)正規表現になっています。条件を強める必要はほとんど無いのですが。
#参考
上記引用以外でQiita記事を。

50
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?