ときどきの雑記帖 RE* (新南口)
The Right Stuff
今週のしずえさん
銭湯のペンキ絵に富士山モチーフのものが多い理由
split
メモ: Perl、split /(((regexp)))/ したらダメ - Qiita
Perl の split 関数のバグなのか仕様なのか、パターンとしてグループ化された表現 /(~)/ を使うと変なことになるのが分かった。
split の動作がどういうものなのか分からないからどうしてこうなるのかは分からない。何にしても直観に反する結果だから奇妙としか思えない。
まあ直観的かと言われればそうじゃないよねえという回答にはなるけど、 きちんとドキュメントに記載されている動作ではある。
Perlの組み込み関数 split の翻訳 - perldoc.jp
PATTERN が 捕捉グループ を 含んでいる場合、それぞれのセパレータについて、 (後方参照 のようにグループが指定された) グループによって捕捉されたそれぞれの部分文字列について追加のフィールドが 生成されます; どのグループもマッチングしなかった場合、部分文字列の代わりに undef 値を捕捉します。 また、このような追加のフィールドはセパレータがあるとき(つまり、分割が 行われるとき)はいつでも生成され、 このような追加のフィールドは LIMIT に関してはカウント されない ことに注意してください。
my @x = split(/-|,/ , "1-10,20", 3); # ("1", "10", "20") my @x = split(/(-|,)/ , "1-10,20", 3); # ("1", "-", "10", ",", "20") my @x = split(/-|(,)/ , "1-10,20", 3); # ("1", undef, "10", ",", "20") my @x = split(/(-)|,/ , "1-10,20", 3); # ("1", "-", "10", undef, "20") my @x = split(/(-)|(,)/, "1-10,20", 3); # ("1", "-", undef, "10", undef, ",", "20")
なぜこうなっているのかの理由は見つけられなかったけど、
たしか「何でsplitしたのか」を知りたい場合に対処したいから
とか言った理由だったような記憶がかすかに。
上記の例でいうと、-
で分割したか
,
で分割したかで別の処理をしたいなんてパターン。
あるいは
use feature qw(say);
while (my $line = <>) {
chomp $line;
say $line;
my @f = split(/([-,])/, $line);
for my $e (@f) {
$e++ if $e =~ /\d+/;
}
say join('', @f);
}
>echo 1-10,20,30|perl split.pl
1-10,20,30
2-11,21,31
このように元と同じもので再構築したいパターンとか
(awkの$0
の再構築だとOFS
をつかってjoinされてしまう
(The GNU Awk User’s Guide))。
Perlのこの仕様、perl4のどこかでこう変更されたような気がしていたのだけど、 perl3の時点でこうだった(さらに前は見ていない)。
perl5.git.perl.org Git - perl5.git/blob - perl.man.3
.Ip "split(/PATTERN/,EXPR,LIMIT)" 8 8
.Ip "split(/PATTERN/,EXPR)" 8 8
.Ip "split(/PATTERN/)" 8
.Ip "split" 8
省略
.Sp
If the PATTERN contains parentheses, additional array elements are created
from each matching substring in the delimiter.
.Sp
split(/([,-])/,"1-10,20");
.Sp
produces the array value
.Sp
(1,'-',10,',',20)
.Sp
そして、PerlだけでなくRubyやPythonも同様の動作だったり。
ruby
String#split (Ruby 3.0.0 リファレンスマニュアル)
正規表現
正規表現にマッチする部分で分割する。特に、括弧によるグルーピングがあればそのグループにマッチした文字列も結果の配列に含まれる (後述)。
sep が正規表現で、かつその正規表現に括弧が含まれている場合には、各括弧のパターンにマッチした文字列も配列に含まれます。 括弧が複数ある場合は、マッチしたものだけが配列に含まれます。
p '1-10,20'.split(/([-,])/) # => ["1", "-", "10", ",", "20"]
python
re — 正規表現操作 — Python 3.10.0b2 ドキュメント
re.split(pattern, string, maxsplit=0, flags=0)
string を、出現した pattern で分割します。 pattern 中でキャプチャの丸括弧が使われていれば、パターン中の全てのグループのテキストも結果のリストの一部として返されます。maxsplit が 0 でなければ、分割は最大 maxsplit 回起こり、残りの文字列はリストの最終要素として返されます。
>>> re.split(r'\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split(r'(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split(r'\W+', 'Words, words, words.', 1) ['Words', 'words, words.'] >>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE) ['0', '3', '9']
m//g
元記事の著者は続いて メモ: Perl、split 関数で ‘abc123def456ghi’ を英数字境界で区切る正規表現 - Qiita という記事を書いているのだけど、数字とそれ以外とで切り分ける ということならsplitしないで取り出すという手もあったり。
>echo abc123def456ghi | perl -nE "say join(',', m/[^\d]+|[\d]+/g)"
abc,123,def,456,ghi
()
を使ったsplitでは先頭に空フィールドができちゃうパターンがあるので
あまりうまくない。
>echo abc123def456ghi | perl -nE "say join(',', split(/([^\d]+)/))"
,abc,123,def,456,ghi
>echo abc123def456ghi | perl -nE "say join(',', split(/([\d]+)/))"
abc,123,def,456,ghi
one true awk
正規表現の文字クラスで]を置くにはで 言及していたissueに対する修正が入っていた。…のが revertされてる?
この辺のやり取りが revertの理由かな。