否定にマッチする正規表現は難しい、前後に空白が含まれていない1文字以上の文字列を表す正規表現を考える(否定的先読みって何だ!)

否定にマッチする正規表現は難しい、前後に空白が含まれていない1文字以上の文字列を表す正規表現を考える(否定的先読みって何だ!)

前後に空白が含まれていない1文字以上の文字列を表す正規表現…。
「含まれている」なら簡単だけど、正規表現では否定(含まれていない)にマッチする表現は難しい…。
^[\s ]+.*[\s ]+$

記号の意味は、以下の通り
^(ハット)は、行の先頭
[](ブランケット = 英語で毛布)は、複数の文字を指定できる。[a-z]のように範囲でもOK!
\sは、半角スペース
全角空白は、そのまんま
+(プラスマーク)は、直前にある文字が1回以上繰り返している事を表す
.(ドット)は、全ての1文字にヒットする
*(アスタリスク)は、+と似ているけど、0回以上の繰り返しでもOK
後半の[\s ]+は、前半と同じく全角・半角スペースの繰り返しを表している
$(ドルマーク)は、行の最後

こうやって、説明すると結構ややこしいけど、慣れてくれば分かる!

最初は、[^0-9]みたいに否定すれば簡単!と思って、以下の様な感じにしたけど、
^[^\s ].*[^\s ]$
2文字以上じゃないとダメだった。空白じゃない1文字+0文字以上の本文+空白じゃない1文字で、前後に空白がある1文字だと駄目だ~。

じゃあ、ハテナマークを使って、0文字でもOKしよう!と思ったら、本文の.*に前後の空白が含まれてしまい、全行にヒットしてしまった…。
^[^\s ]?.*[^\s ]?$

例によって色々と試行錯誤したら、正解にたどり着いた。

// これが正解
^(?! | ).*(?! | )$

()は、パーレンと読む。正規表現では|(パイプ)と組み合わせて使う。
A(B|C|D)で検索すると
AB
AC
AD
にヒットして文字のOR演算子みたいに使える。当然ABCDにはヒットしない!

今回の正規表現のキモである「肯定的先読み・否定的先読み・肯定的後読み・否定的後読み」を説明する。

a(?=b)は「肯定的先読み」と言い、aの次の文字がbだったaにヒットする。(abcとかにヒットするけど、aだけヒットして、abじゃない)

a(?!b)は「否定的先読み」と言い、aの次の文字がbじゃないaにヒットする。

(?<=a)bは「肯定的後読み」と言い、「肯定的先読み」の後ろの方がヒットする。(abcとかにヒットするけど、bだけヒットして、abじゃない)

(?<!a)bは「否定的後読み」と言い、bの直前の文字がaじゃないbにヒットする。

と、自分で説明していても分かりづらいな~と思うが、今回のような場合に真価を発揮する。
二番目の「否定的先読み」を使うと、行頭が空白じゃない行にヒットする。
^(?! | )

四番目の「否定的後読み」を使うと、行の最後が空白じゃない行もヒット出来る。
(?

なので、その行の最初と最後が空白でない文字列を表す正規表現は、以下のようになる。
^(?! | ).*(?

簡単と思いきや、なかなかに長い道のりだった…。