やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

SQLite3構文 expression(regexp 句)

 正規表現

成果物

情報源

 正規表現については以下を参照した。感謝。

構文

select '文字列' regexp 'パターン';
select regexp('パターン','文字列');

正規表現

 文字列から指定パターンに一致するか判定する。likeglobなどのワイルドカードよりも遥かに多く複雑なパターンを指定できる。

 じつは正規表現にはいくつかの種類がある。

 SQLite3の公式からは正規表現の文法についての情報を見つけられなかった。まずはネットで正規表現について調べて、それをSQLite3で使えるかどうか試していく。おそらくPerlの正規表現が使えると期待する。

メタ文字

. ^ $ [ ] * + ? | ( )

メタ文字のエスケープ

  • \(バックスラッシュ)をメタ文字の前につける
  • \自体のエスケープも\\で可能

メタ文字

隣接パターンの位置指定

メタ文字 意味
^abc 前方一致。先頭がabcの文字列で始まる
abc$ 後方一致。末尾がabcの文字列で終わる
^abc$ 完全一致。先頭と末尾の間がabc
.*abc.* 部分一致。

特殊エスケープ

特殊エスケープ 意味
\b スペースなどの単語の区切り
\B \b以外の文字
\cA Ctrl-A
\d 任意の数値([0-9]と同じ)
\D 数値以外の文字([^0-9]と同じ)
\f フォームフィード文字
\n 改行文字
\r 復帰文字
\s 区切り文字(スペース、タブ、改行)([ \f\n\r\t\v]と同じ)
\S \s以外の1文字([^ \n\r\f\t]と同じ)
\t タブ文字
\v 垂直タブ文字
\w 英数文字([A-Za-z0-9_]と同じ)
\W \w以外の文字
\1 1番目の(...)にマッチした文字列
\o033 8進数で033にあたる文字
\x1b 16進数で1bにあたる文字
\その他 その他の文字自身

文字種

メタ文字 意味
. 任意の1字
[abc] 候補。a,b,cのいずれか1字
[^abc] 否定。a,b,c以外のいずれか1字
[a-z] 範囲。azの間にあるいずれか1字

前のパターンのくりかえし数を指定

メタ文字 意味
? 0回か1回
+ 1回以上
* 0回以上
{3} きっかり3回
{3,} 3回以上
{3,7} 3回以上7回以下

グループ化

メタ文字 意味
(abc) abcの文字列(各文字でなく文字列)

論理演算

メタ文字 意味
`(abc def)|ORabcまたはdef`
(?=abc) 直後にabcがある(肯定的先読み)
(?!abc) 直後にabcがなる(否定的先読み)
(?<=abc) 直前にabcがある(肯定的後読み)
(?<!abc) 直前にabcがなる(否定的後読み)

やってみる

select '2000-01-01' regexp '\d{4}-\d{2}-\d{2}';
1

メタ文字のエスケープ

.

select 'etc...' regexp 'etc.\.\.';
1
select 'etc!!!' regexp 'etc.\.\.';
0
select 'etc!!!' regexp 'etc...';
1

^

select '2^4=16' regexp '2\^4=16';
1
select '2^4=16' regexp '2^4=16';
0

$

select '$100' regexp '\$100';
1
select '$100' regexp '$100';
0

[, ]

select '[123]' regexp '\[123\]';
0
select '[123]' regexp '[123]';
0

(, )

select '(123)' regexp '\(123\)';
0
select '(123)' regexp '(123)';
0

*

select '***' regexp '\*\*\*';
1
select '***' regexp '***';
Error: ICU error: uregex_open(): U_REGEX_RULE_SYNTAX

+

select '1+2' regexp '1\+2';
1
select '1+2' regexp '1+2';
0

?

select 'what?' regexp 'what\?';
1
select 'what?' regexp 'what?';
0

\|

select 'A|B' regexp 'A\|B';
1
select 'A|B' regexp 'A|B';
0

\

select '\s' regexp '\\s';
1
select '\s' regexp '\s';
0

隣接パターンの位置指定

 前方一致。

select 'abc123' regexp '^abc.*';
1

 後方一致。

select 'abc123' regexp '.*123$';
1

 完全一致。

select 'abc123' regexp 'abc123';
select 'abc123' regexp '^abc123$';
1
1

 部分一致。

select 'abc123' regexp '.*abc.*';
select '123abc' regexp '.*abc.*';
select '12abc3' regexp '.*abc.*';
1
1
1

 文字種パターン。

select '12abc3' regexp '.*[a-zA-Z]+.*';
1

 グループ化

select 'abc2000年01月02日def' regexp '.*(\d{4}年\d{2}月\d{2}日).*';
1

特殊エスケープ

\b

select ' ' regexp '\b';
0
select '\' regexp '\b';
0

 どうやら\bは使えないらしい。

\B

select 'a' regexp '\b';
0
select ' ' regexp '\b';
0

 どうやら\Bは使えないらしい。

\cA

 そもそも、Ctrl制御コードをどうやって入力するの?

 たとえば改行コードは16進数で0x0A、10進数で10。制御コードでは^J正規表現では^\cで表す。つまり\cJ

select char(0x0A) regexp '\cJ';
1

 もっとも、現代でも使うような制御コードは改行やタブなどであり、それらは\sでまとめて表現できるはず。

\d

 数字。[0-9]と同じ。

select '123' regexp '\d+';
1
select '123a' regexp '\d+';
0

\D

 数字以外。[^0-9]と同じ。

select 'abc' regexp '\D+';
1
select '123' regexp '\D+';
0

\f

 フォームフィード文字。プリンタでは次のページを給紙する。

select char(0x0C) regexp '\f';
1
select char(0x0C) regexp '\cL';
1

\n

 改行コード。

select 'A' || char(0x0A) || 'B' regexp 'A\nB';
1

\r

 復帰コード。

select 'A' || char(0x0D) || 'B' regexp 'A\rB';
1
select 'A' || char(0x0D) || char(0x0A) || 'B' regexp 'A\r\nB';
1

\s

 空白文字(区切り文字(スペース、タブ、改行))。[ \f\n\r\t\v]と同じ。

select 'A B' regexp 'A\sB';
select 'A' || char(0x0C) || 'B' regexp 'A\sB';
select 'A' || char(0x0A) || 'B' regexp 'A\sB';
select 'A' || char(0x0D) || 'B' regexp 'A\sB';
select 'A' || char(0x09) || 'B' regexp 'A\sB';
select 'A' || char(0x0B) || 'B' regexp 'A\sB';
1

 全角スペースも真になった。

select 'A B' regexp 'A\sB';
1

 他は偽。

select '' regexp '\s';
0

\S

 区切り文字(スペース、タブ、改行)以外。[^ \f\n\r\t\v]と同じ。

select 'A B' regexp 'A\SB';
select 'A' || char(0x0C) || 'B' regexp 'A\SB';
select 'A' || char(0x0A) || 'B' regexp 'A\SB';
select 'A' || char(0x0D) || 'B' regexp 'A\SB';
select 'A' || char(0x09) || 'B' regexp 'A\SB';
select 'A' || char(0x0B) || 'B' regexp 'A\SB';
0

 全角スペースも対象外。

select 'A B' regexp 'A\SB';
0

 他は真。

select '' regexp '\S';
1

\t

select 'A' || char(0x09) || 'B' regexp 'A\tB';
1

\v

select 'A' || char(0x0B) || 'B' regexp 'A\vB';
1

\w

 英数字。[0-9A-Za-z_]と同じ。

select 'a' regexp '\w';
1
select 'Z' regexp '\w';
1
select '3' regexp '\w';
1
select '_' regexp '\w';
1
select '$' regexp '\w';
0

\W

 英数字以外。[^0-9A-Za-z_]と同じ。

select 'a' regexp '\W';
0
select 'Z' regexp '\W';
0
select '3' regexp '\W';
0
select '_' regexp '\W';
0
select '$' regexp '\W';
1

\o

 8進数。これ、効いてないと思われる。

select 'o001' regexp '\o001';
1
select '001' regexp '\o001';
0
select '1' regexp '\o001';
0

\x

 16進数。これ、効いてないと思われる。

select 'x0F' regexp '\x0F';
0
select '0x0F' regexp '\x0F';
0
select 'x0F' regexp '\x0F';
0
select '15' regexp '\x0F';
0

 何一つヒットさせられなかった。

\数(後方参照)

select '11' regexp '(\d)\1';
1
select '1a1' regexp '(\d)a\1';
1

\その他

 その他の文字自身。

select 'c' regexp '\c';
1

 \aはなぜか一致せず。特殊な意味があるのか?

select 'a' regexp '\a';
0

 ネットには^,$の代わりに\A,\zを使うようなことが書いてあった。それか? でも大文字だし。

select 'a' regexp '\aa';
0

 やはり違う。\Aとすべき。つまり\aは何か別の特殊な意味になるようだ。情報がない……。

select 'a' regexp '\Aa';
1

 eでもヒットせず。

select 'e' regexp '\e';
0
select 'g' regexp '\g';
1
select 'h' regexp '\h';
0
select 'i' regexp '\i';
1
select 'j' regexp '\j';
1
select 'k' regexp '\k';
Error: ICU error: uregex_open(): U_REGEX_INVALID_CAPTURE_GROUP_NAME

 上記の英字はすべて特殊エスケープ以外だと思うのだが、それぞれ反応が違う……。もうわけわかんない。公式さん仕様ください。

文字種

.

 任意の1字。

select 'a' regexp '.';
1
select 'aa' regexp '.';
0

 任意の長さをした任意の字。

select 'aaaaa' regexp '.*';
1

[]

 いずれかの1字。

select 'a' regexp '[abc]';
1
select 'z' regexp '[abc]';
0

[^]

 いずれかの1字以外。

select 'a' regexp '[^abc]';
0
select 'z' regexp '[^abc]';
1

[-]

 指定した字の範囲にあるいずれかの字。文字コードポイントの範囲指定。

select 'k' regexp '[a-z]';
1
select 'A' regexp '[a-z]';
0

前のパターンのくりかえし数を指定

?

 0回か1回。

select '' regexp 'a?';
select 'a' regexp 'a?';
select 'b' regexp 'a?';
select 'aaa' regexp 'a?';
1
1
0
0
select 'k' regexp '[a-z]?';
select 'A' regexp '[a-z]?';
1
0
select '' regexp '(abc)?';
select 'abc' regexp '(abc)?';
select 'def' regexp '(abc)?';
1
1
0

+

 1回以上。

select '' regexp 'a+';
select 'a' regexp 'a+';
select 'b' regexp 'a+';
select 'aaa' regexp 'a+';
0
1
0
1
select 'k' regexp '[a-z]+';
select 'A' regexp '[a-z]+';
select 'kkk' regexp '[a-z]+';
1
0
1
select '' regexp '(abc)+';
select 'abc' regexp '(abc)+';
select 'def' regexp '(abc)+';
select 'abcabc' regexp '(abc)+';
select 'abcdef' regexp '(abc)+';
select 'defabc' regexp '(abc)+';
select 'abcdef' regexp '(abc)+.*';
select 'abcabcdef' regexp '(abc)+.*';
0
1
0
1
0
0
1
1

*

 0回以上。

select '' regexp 'a*';
select 'a' regexp 'a*';
select 'b' regexp 'a*';
select 'aaa' regexp 'a*';
1
1
0
1
select 'k' regexp '[a-z]*';
select 'A' regexp '[a-z]*';
select 'kkk' regexp '[a-z]*';
1
0
1
select '' regexp '(abc)*';
select 'abc' regexp '(abc)*';
select 'def' regexp '(abc)*';
select 'abcabc' regexp '(abc)*';
select 'abcdef' regexp '(abc)*';
select 'defabc' regexp '(abc)*';
select 'abcdef' regexp '(abc)*.*';
select 'abcabcdef' regexp '(abc)*.*';
1
1
0
1
0
0
1

{N}

 きっかり指定回数。

select '' regexp 'a{1}';
select 'a' regexp 'a{1}';
select 'b' regexp 'a{1}';
select 'aaa' regexp 'a{1}';
select 'aaa' regexp 'a{3}';
0
1
0
0
1
select 'k' regexp '[a-z]{1}';
select 'A' regexp '[a-z]{1}';
select 'ghi' regexp '[a-z]{1}';
select 'ghi' regexp '[a-z]{3}';
1
0
0
1
select '' regexp '(abc){1}';
select 'abc' regexp '(abc){1}';
select 'abc' regexp '(abc){1}';
select 'def' regexp '(abc){1}';
select 'abcabc' regexp '(abc){1}';
select 'abcdef' regexp '(abc){1}';
select 'defabc' regexp '(abc){1}';
select 'abcdef' regexp '(abc){1}.*';
select 'abcabcdef' regexp '(abc){1}.*';
0
1
1
0
0
0
0
1
1

{N,}

 指定回数以上。

select '' regexp 'a{1,}';
select 'a' regexp 'a{1,}';
select 'b' regexp 'a{1,}';
select 'aaa' regexp 'a{1,}';
select 'aaa' regexp 'a{4,}';
0
1
0
1
0

{N,M}

 指定回数以内。

select '' regexp 'a{1,2}';
select 'a' regexp 'a{1,2}';
select 'b' regexp 'a{1,2}';
select 'aaa' regexp 'a{1,2}';
select 'aaa' regexp 'a{4,7}';
select 'aaa' regexp 'a{3,4}';
0
1
0
0
0
1

()

 グループ化。

select 'aabc' regexp 'a(abc)';
select 'adef' regexp 'a(abc)';
1
0

 任意の文字列があったりなかったり。

select 'a' regexp 'a(abc)?';
select 'aabc' regexp 'a(abc)?';
select 'adef' regexp 'a(abc)?';
1
1
0

(|)

 いずれかの文字列。

select 'aabc' regexp 'a(abc|def)';
select 'adef' regexp 'a(abc|def)';
1
1

(?=), (?!), (?<=), (?<!)

 先読み・後読みは使えないのか?

select 'abc' regexp '(?=abc)';
select 'abc' regexp '(?<=abc)';
select 'abc' regexp '(?!abc)';
select 'abc' regexp '(?<!abc)';
0
0
0
0

regexp()関数

select regexp('\w+','abc');
1

判定・抽出・置換

 ところで上記まではマッチするかの「判定」のみだった。ほかにもパターン箇所のみ「抽出」したり「置換」したい。それをSQLite3でできるか?

 linuxgrepコマンドのように「抽出」もしたいのだが、方法が見つからず。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux

前回まで