sedによる複数行対象の処理

よくあるWikiStyle記法(いまだとMarkdownと呼ぶのが流行り?) でHTMLのtable要素をテキストで生成させる記法にこんなのがあったりする。

...
|a|b|c|
|foo|bar|baz|
...

...
<table>
<tr><td>a</td><td>b</td><td>c</td></tr>
<tr><td>foo</td><td>bar</td><td>baz</td></tr>
</table>
...

この処理をsedでやらせてみる。各々の行は | 記号で挟まれた範囲を <td>...</td> に置換すればよいので sed の s コマンドによる置換で一発でできそうだが、 行頭が | で始まる範囲の行全体を <table></table> で括るのはどうしたらよいのだろう。

ホールドスペースを使って「行頭 |」を蓄えていき、 最後に全体を括るようにする。 たとえば、こんな感じのスクリプトで行ける。

tbl.sed

#!/usr/bin/sed -f
/^|.*|/ {;			# 行頭 | のあと1個以上の | がある行なら
 s,|$,,;			# まず行末の | を消しておく
 s,|\([^|]*\),<td>\1</td>,g;	# |... を <td>...</td> に置換する
 s,^,<tr>,; s,$,</tr>,;		# 行頭に <tr> を、行末に </tr> を付加
 H;				# 置き換え結果をホールドスペースに追加
 s/.*//;			# パターンスペースは出力されないよう消しておく
 # ↓最終行なら残ったホールドスペース処理のため :cont へ
 $ b cont
 d;				# 最終行以外なら次のサイクルへ
}
:cont
x;				# 行頭| 以外の行: まずホールドスペースと交換
/./ {;				# ホールドスペースに文字列があれば
  s|^|<table>|;	s|$|</table>|;	# 先頭に <table> を、末尾に </table> を追加
  p;				# <table>...</table>全体を出力する
  s/.*//;			# 出力し終わったら消しておく
}
x
# ここで最後にパターンスペースにある内容が出力される