作者: Bunta
日時: 2008/8/30(14:35)
藤岡さん

 全体として、僕が不具合に遭遇してから考えて調べたことを遡ることになりま
す。ごゆっくり、どうぞ。

> なぜそんなことができるのかを説明して欲しいのです。
> ステップを追って、動作を言葉で説明して欲しい。

 僕なんかの言葉より、パイソンのデバッガが出している過程を1つ1つ追った
ほうが正確だと思います。けれど、頑張ってみます。

 例はまず単純なほうを使いますね。

=== test.txt ========
aaa
---
=====================
といった入力を
=== kek.txt =========
aaa

---
=====================
としたい。
 「デリミタ(---)の前の行が空行でないなら空行を入れる」これが課題
です。

------- test1.sed -------
# all seds, OK except gsed3 (GNU3 + mb1.07)
$!N			#(0)
/^aaa\n---$/{		#(1)
	s/\n/\		#(2) s/\n/\n\n/ の意味。(5)のため空行追加。
\			#
/			#
	P		#(3) 「aaa」出力
	s/^aaa\n//	#(4) これにより PS は「\n---」となる
}
P			#(5) 「\n」出力
D			#(6)
------------------------

 基本的に、外枠のN; P; Dのサイクルで複行のパターンスペースを作ります。
言うまでもなく、 sedデフォルトの自動読み込みは最初の1行のみで、そのあと
はこのスクリプトのN;  P; Dのサイクルが順次次行を読み込んでいくという展開
になります。

  (1)以降で、その複行が
aaa\n---
 である場合の処理をします。もちろん前の行を調べているのはこの部分です。
(aaaがマッチしなければ、{ }の外に出てP; D。)
 マッチした場合も、aaaはいずれ出力されねばなりませんが、 そのような文字
(aaaのこと) が存在するなら、 改行をaaaの後ろに付加しなければなりません
(デリミタの前に改行文字を入れる、と言っても同じこと)。step (2).

 (4)は、自前の D です。(3)の P のあと D ができなための工夫と考えて下さい。
(4)で D してしまうと制御がstep (0)に戻ってしまうのです。制御をスクリプト先
頭に戻すことなく、つまり } 以降のP; Dへといくように、パターンスペースの
最初のセグメントを消去しています。

------------------------------------------------------------------

 さて、

C:\>gsed3 --version
GNU sed version 3.02 + multi-byte extension 1.07

 以外では、ここまででは問題が出ません。 そしてgsed3に僕はこだわりがない
ので(Perl-like regexが使えませんので)、よいのです。また、上の「aaa\n---」
は、 あくまでonigsedのバグ出し(恐い言葉、ごめんなさい)のために考案した
テストです。

 ではどういう場合にonigsedで問題が出るのか。デリミタの前が「aaa」でなく、
もう少し汎用的に記述した場合、つまり「空行でないなら」と記述した場合です。
これは

aaa
でなく
[^\n][\n^]*
とsedのBREでは記述されると思います。([^\n]*と略記して同じだと思いますが)

すると、スクリプトはこうなります。

------- test2.sed -------
$!N
/^[^\n][^\n]*\n---$/{
        s/\n/\
\
/
        P
        s/^[^\n][^\n]*\n//	#(*)
                }
P
D
-------------------------

 で、onigsedだけ動かない。デバッガにより(*)の箇所でおかしくなっているこ
とが判明した。#2437で書いたとおり

#	s/aaa\n//		# こうなら誤って2つ消しはしない
#	s/[^\n]*\n//		# これだと2つ消す
	s/[^\n]\+\n//		# これでも2つ消す

 と少し詰められた、というわけです。これで十分ではないとも思いますが。

 (アンサパンドその他の件は、どうも話が変な方向へ行きそうなので割愛しました。)

 最後に、デバッガの出力を載せておきます。

>python sedsed -d -f c:\test2.sed c:\test.txt >c:\kek.txt

--- egsed (GNU sed version 4.0.7)    #は出力
PATT:aaa$
COMM:$ !N
PATT:aaa\n---$
COMM:/^[^\n][^\n]*\n---$/ {
COMM:s/\n/\\N\\N/
PATT:aaa\n\n---$
COMM:P
aaa				#
PATT:aaa\n\n---$
COMM:s/^[^\n][^\n]*\n//
PATT:\n---$
COMM:}
COMM:P
				#
PATT:\n---$
COMM:D
PATT:---$
COMM:$ !N
PATT:---$
COMM:/^[^\n][^\n]*\n---$/ {
COMM:P
---				#
PATT:---$
COMM:D


--- onigsed    #は出力
PATT:aaa$
COMM:$ !N
PATT:aaa\n---$
COMM:/^[^\n][^\n]*\n---$/ {
COMM:s/\n/\\N\\N/
PATT:aaa\n\n---$
COMM:P
aaa				#
PATT:aaa\n\n---$
COMM:s/^[^\n][^\n]*\n//		;ここがおかしい
PATT:---$			;「\n」が2つともなくなっている
COMM:}
COMM:P
---				#
PATT:---$
COMM:D


Bunta