sed (GNU sed)

linux 正規表現

ここで sed は GNU sed を指す. BSD sed (Mac で標準に入っている sed がこれだ) のことは知らない. 同じものが動くといいですね.

リンク

  1. sed1line.txt

sed の挙動を理解する

実装を読むことにする. GNU sed の execute.c

コマンド 機能 default_output を抑制 補足
a 続く文字列を line の後ろに付け足す ahoge a に続く文字列は次の改行文字または末尾までを読み取る. 付け足すときは改行文字を挟む.
b jump_index までジャンプする bx jump_index: で作ったラベルを指定する
c line と続く文字列で置き換えて, プログラムループを抜ける choge c に続く文字列は a 同様に読む. 実行直後に新しい cycle が始まる
d 即座にプログラムループを抜ける ✔️ d line を削除して次の cycle を始めるような挙動
D line から一行目(最初に見つかる改行文字まで)を削除してプログラムループを最初からやり直す D コメントでは「次の行を読まずに新しい cycle を始める」とある
e line の内容で popen する echo 'echo @' | sed 's/@/HELLO WORLD/; e' eval の e だろうか?
g hold の内容で line を上書きする g
G hold の内容を line の末尾に付け足す G 改行文字を挟む
h line の内容で hold を上書きする h
H line の内容で hold の末尾に付け足す H
i 続く文字列を line の前に付け足す ihoge 改行文字を挟む
n 次の行を入力から読んで, line を上書き
N 次の行を読んで line の末尾に付け足す 改行文字を挟む
p 現在の line を出力
P 現在の line の内, 一行目を出力
q 即座に全ての cycle を終える
Q 即座に全ての cycle を終える ✔️
r テキストファイルを開いて中身を出力 rTEXT.txt
R テキストファイルを開いて一行だけ読んで出力 RTEXT.txt どこまで読んだか記憶して続きの一行を読む
s line に対する文字列置換を行う s/[0-9]*/NUMS/g
t 直前の置換に成功したら jump_index までジャンプ :loop s/x//; tloop
T 直前の置換に失敗したら jump_index までジャンプ
w ファイル名を指定して, line をこれに出力 wOUT.txt
W ファイル名を指定して, line の一行目をこれに出力 WOUT.txt
x linehold を交換
y translate による置換をする y/123/abc/ 一対一の変換対応を与える
z line を空にする
= 行番号を出力する
F ファイル名を出力する

例文集

sed の例文を集める. ほとんどは自分で思いついたものではないので, 他人が書いたコードを勝手に解説してることになる.

tac (逆順出力)

tac コマンドは行単位で逆順に出力する

   seq 5
1
2
3
4
5

   seq 5 | tac
5
4
3
2
1

これを行う sed スクリプトは [sed1line.txt] で紹介されている. このスクリプトの理解はパターンスペースとホールドスペースを理解するのに役立ちそう.

簡略化すると次のようなスクリプトで tac は再現できる.

G
h

sed は実行の際に内部にパターンスペースとホールドスペースというそれぞれ文字列型のレジスタを持つ. 入力から読まれた一行はパターンスペースに保存されてスクリプトを実行する. ホールドスペースは初めはただ空文字列が入っている. G は ホールドスペースの文字列をパターンスペースの末尾に改行を入れてから追加する. ( g なら追加ではなく上書きになる.) 逆に h はパターンスペースの文字列でホールドスペースを上書きする. ( H なら上書きじゃなくて改行を入れてから追加をする.) これを繰り返すと,

Command           Pattern      Hold
---------------   ---------    -------
(next cycle)      1            ""
G                 1\n          ""
h                 1\n          1\n
(next cycle)      2            1\n
G                 2\n1\n       1\n
h                 2\n1\n       2\n1\n

となってホールドスペースに tac の結果が保存されていることが分かる.

結果をきれいに出力するための処理として, 一番最初のホールドスペースは G してもしょうがないのでさせないのと, 出力は一番最後だけすればいい (デフォルトではサイクルの最後にパターンスペースにあるものを出力する) ので d で出力せずに次のサイクルに移すような処理を付け足す.

1!G
h
$!d

長さを保つ文字置換

A - WAsedAC

入力が英大文字のときに含まれる W*AAC* に置換したい. ただしここでパターンで W が \(n\) 個続いたとき, これを A のあとに C が \(n\) 個続いたような文字列にしたい. 例えば WWAACC にしたい.

一手ずつ変換する場合

地道に s/WA/AC/g を何度も繰り返せば良い. これは

:a
s/WA/AC/g
ta

と出来る.

一度に変換する

上のようにループを使って一手ずつやると大変遅い. 一度に変換できるならしたほうが良い.

x20 さんの方法 wupc2019/submissions/4545993 を真似る.

s/\(W*\)A/A\L\1/g

とすると, \L はそれより後ろを lowercase で出力してくれる ( manual ) ので, WWWAAwww になってくれる. 入力に他に w という文字がないことを仮定すれば (この問題ではそうなっている), 最後に

y/w/C

とすれば ACCC を得る.

シェル実行

gsed (GNU sed) の拡張を用いるとシェルスクリプトが実行できる. これは s コマンドの e フラグとして実装されている.

すなわち, シェルスクリプトを出力させるような s コマンドを書いて,

   echo 2 3 | sed 's/\(.*\) \(.*\)/echo $(( \1 + \2 ))/'
echo $(( 2 + 3 ))

これに e フラグを付けると実行する.

   echo 2 3 | sed 's/\(.*\) \(.*\)/echo $(( \1 + \2 ))/e'
5

中身は bash なので(?), for 文でもなんでも書ける.