ed エディタ

ed エディタとは

ed エディタは古くから Unix 標準のエディターです。ラインエディタなのでスクリーンエディタが主流の現代では、今は使われていない旧式のエディタですが、意外に快適でした。

その一番の理由はカーソル操作がいらないということです。vi や emacs や gedit のカーソル操作は意外と面倒です。カーソルを見失ったり、カーソルが移動するのを追いかけたりするのが結構負担になります。一方、ed はラインエディタなのでカーソル操作は全くありません。

カーソル操作無しで文書の編集ができるのだろうかと思うかもしれませんが、コマンドを利用した編集はカーソル操作の場合よりも効率が良いときがあります。

ed では、編集コマンドが1文字です。ed を使うときの快適さは、このコマンドの簡潔性も理由のひとつです。また、正規表現による検索や s コマンドによる置き換えが強力なので、カーソル移動ではできない編集を少ないキー入力のコマンドで一気に行うことができます。

ed の起動はコンソールから ed と入力します。また、最初にプロンプト * を表示する P コマンドを実行しておきます。そうすることで、* 印のついた行はコマンドであることを識別できます。

ed は vi の様にコマンドモードと入力モードに分かれているので、文字列を入力するために a コマンドを実行して入力モードにします。入力モードのときは普通に入力することができます。入力モードのの終了は . だけを入力します。

~/sed$ ed
P
*a
fact 0 = 1
fact n = n * fact (n-1)
main = print $ fact 5
.

入力した文字列はメモリーのバッファに記憶されるだけです。バッファの文字列をファイルに書き込むには w コマンドを用います。

*w fact.hs
58

ed エディターはシェルとの親和性が高く、! コマンドでシェルコマンドを実行できます。

*! runghc fact.hs
120

バッファの文字列を表示するためには p コマンドを使います。行の範囲指定で , (全範囲)を指定し、p コマンドを実行するとバッファのすべての文字列を表示させることができます。

*,p
fact 0 = 1
fact n = n * fact (n-1)
main = print $ fact 5

バッファのデータは n コマンドで行番号を並べて表示できます。

1    fact 0 = 1
2    fact n = n * fact (n-1)
3   
4    main = print $ fact 5

また、s コマンドで文字列の置き換えができる。s コマンドは [アドレス]s/正規表現/置き換える文字列/[変換スイッチ] という文法に従って入力します。次の s コマンドは1行から4行の fact という文字列を factorial という文字列に置き換えます。g スイッチは1行中に複数の fact が存在しても置き換えることを意味しています。

*1,4s/fact/factorial/g
*,p
factorial 0 = 1
factorial n = n * factorial (n-1)
main = print $ factorial 5
*w      
78

ed エディタは q コマンドで終了します。バッファを編集したとき w コマンドを実行していないと終了できませんが Q コマンドで編集を破棄して終了できます。

*q

慣れないと面倒くさそうですが、行の選択は行番号や、正規表現でできるので、慣れると便利です。カーソルの位置を探すのに目が痛くなることがありません。

ed のコマンド

ここからは ed のいろいろなコマンドの使いかたを見ていきます。系統的にコマンドの解説をすると言うよりは実際の編集にどのように活用するかを見ていきます。

行の結合と分割

ed はラインエディタですが、行の結合と分割ができます。行の結合には j コマンドを使います。行の分割には s/regexp/new/ を入力するときに \ のあとに改行します。

~/sed$ ed
*P
*a
this is the first line.
this is the second line.
.
*,n
1    this is the first line.
2    this is the second line.
*1,2j
*,n
1    this is the first line.this is the second line.
*s/line\./&\
/
*,n
1    this is the first line.
2    this is the second line.

ed ではカーソルで行う作業を正規表現と s コマンドでやるのだと考えると、ed もそう使いづらくは感じられません。

挿入、置換、削除、入れ替え

ed による行の挿入、単語の置換、空行の削除、行の入れ替えをやってみました。

~$ ed
P
*a
foo foo foo foo foo foo
bar bar bar bar bar bar
baz baz baz baz baz baz
.

バッファを行番号付きで表示

*,n
1    foo foo foo foo foo foo
2    bar bar bar bar bar bar
3   
4    baz baz baz baz baz baz

3行目の上に行を挿入

*3i
foo foo foo foo foo foo
.
*,n
1    foo foo foo foo foo foo
2    bar bar bar bar bar bar
3    foo foo foo foo foo foo
4   
5    baz baz baz baz baz baz

foo を bar に置換

*,s/foo/bar/g
*,p
bar bar bar bar bar bar
bar bar bar bar bar bar
bar bar bar bar bar bar
baz baz baz baz baz baz

空行を削除

*g/^$/d
*,n
1    bar bar bar bar bar bar
2    bar bar bar bar bar bar
3    bar bar bar bar bar bar
4    baz baz baz baz baz baz

4行目をカットして1行目の上にペースト

*4d
*0x
*,p
baz baz baz baz baz baz
bar bar bar bar bar bar
bar bar bar bar bar bar
bar bar bar bar bar bar

2行目と3行目の結合

*2,3j
*,p
baz baz baz baz baz baz
bar bar bar bar bar barbar bar bar bar bar bar
bar bar bar bar bar bar

コンソールの1画面に収まるくらいの小さなコードを書くときは、ed は軽快で良い感じです。

grep の名前の由来

grep の名前の由来は g/re/p だそうです。

~$ ed
P
*a
foo
1st grep
bar
2nd grep
baz
.
*,p
foo
1st grep
bar
2nd grep
baz
*g/re/p
1st grep
2nd grep

ed と sed, awk は操作性が似ているので、一緒に学習すると覚えやすいです。複数のツールで操作性が統一されているのは得した気分になります。vi も ex mode があるのでこの仲間です。bash のコマンドライン編集は emacs の仲間です。

シェルの出力を取り込む

ed の ! コマンドはシェルのコマンドを実行させますが、これに r コマンドを組み合わせると、シェルの出力をバッファに取り込むことができます。

~$ ed
P
*r !cal
197
*,p
      3月 2020        
日 月 火 水 木 金 土 
 1  2  3  4  5  6  7 
 8  9 10 11 12 13 14 
15 16 17 18 19 20 21 
22 23 24 25 26 27 28 
29 30 31             

シェルとの親和性が ed の魅力のひとつです。

s コマンドあれこれ

ed の s コマンドのかしこい使い方

~/sed$ ed
P
*a
foo foo foo foo foo
.

s コマンドで置き換えをしたときに同時にその行を表示するには最後に p コマンドをつけます。

*s/foo/bar/p
bar foo foo foo foo

s コマンドの置き換えの後ろに 3 をつけると3番目に出現したパターンを置き換えます。

*s/foo/bar/3p
bar foo foo bar foo

s コマンドの置き換えの後ろに g をつけると、その行のすべてのパターンを置き換えます。

*s/foo/bar/gp
bar bar bar bar bar

u (undo) コマンド

エディタの機能で大切なのは、一旦編集した結果を元に戻す機能です。ed ディタには u (undo) コマンドがあり、直前の編集を取り消してくれます。また、Q コマンドは編集を破棄して ed を終了するコマンドです。

~$ ed -p*
*a
foo foo foo
.
*s/foo/bar/p
bar foo foo
*u
*.
foo foo foo
*Q
~$

u を2回使うと、元に戻すを元に戻すことができます。

~/sed$ ed -p*
*a
foo foo foo
.
*s/foo/bar/gp
bar bar bar
*u
*.
foo foo foo
*up
bar bar bar

s コマンドを使うと文書全体の単語の置き換えもできます、それも u コマンドで取り消しできます。

~$ ed -p*
*a
fact 0 = 1
fact n = n * fact (n-1)

main = print $ fact 5
.
*w fact.hs
58
*!runghc fact.hs
120
!
*,s/fact/foo/g    
*,p
foo 0 = 1
foo n = n * foo (n-1)
main = print $ foo 5
*u
*,p
fact 0 = 1
fact n = n * fact (n-1)
main = print $ fact 5

カーソルのない ed の編集は面倒なようですが、慣れると文書全体の単語の置き換えなどもキーボードから簡単にできます。行への移動も正規表現を使って /fact n/ のように正規表現を利用すると、n コマンドで行番号を表示しなくても簡単にできます。ed を使ったソースコードの編集も結構実用的なのではないでしょうか。特にプログラムを書くときにそれを感じます。vi を作ったビル・ジョイも ed を使っていたという話もあります。

文章の編集には向きませんが、その時はスクリーンエディタを使えばいいのです。

m コマンド

ed エディタの m コマンドは複数の行を移動させます。fact.hs ファイルでそれを検証してみます。
~$ ed fact.hs
58
,n
1    fact 0 = 1
2    fact n = n * fact (n-1)
3   
4    main = print $ fact 5
1,2m4
,n
1   
2    main = print $ fact 5
3    fact 0 = 1
4    fact n = n * fact (n-1)
1行目と2行目が4行目の後ろに移動しました。この移動も、u (undo) コマンドで元に戻すことができます。
u
,p
fact 0 = 1
fact n = n * fact (n-1)
main = print $ fact 5

これで ed の概要の説明は終わりです。info ed で調べるとまだまだ沢山の機能がありますが、いままで述べた内容でも、小さなプログラムを書くのには十分です。

ed でプログラムを書いた

最後に ed で Haskell のプログラムを書いてみました。

~/sed$ ed
P
*a
hanoi 0 _ _ = []
hanoi n from to work =
  hanoi (n-1) from work to ++
  [(n, work, to)] ++
  hannoi (n-1) work to
from
.
*,p
hanoi 0 _ _ = []
hanoi n from to work =
  hanoi (n-1) from work to ++
  [(n, work, to)] ++
  hannoi (n-1) work to
from

うっかり最後の from の前に改行を入れてしまいました。

*5
  hannoi (n-1) work to
*j
*,p
hanoi 0 _ _ = []
hanoi n from to work =
  hanoi (n-1) from work to ++
  [(n, work, to)] ++
  hannoi (n-1) work to from

hanoi.hs ファイルに書き込んで、

*w hanoi.hs
119

runghc でhanoi.hs を実行しましたが、hanoi の関数の引数が合わないとクレームが来ました。

*!runghc hanoi.hs
hanoi.hs:1:1: error:
    Equations for ‘hanoi’ have different numbers of arguments
      hanoi.hs:1:1-16
      hanoi.hs:(2,1)-(5,27)
!

そこで、hanoi 0 の引数の数を調整しました。

*/hanoi
hanoi 0 _ _ = []
*s/_/_ _/p
hanoi 0 _ _ _ = []

ファイルにセーブして再トライしましたが、hannoi という名前の関数はないと再びクレーム。

*w
121
*!runghc hanoi.hs
hanoi.hs:5:3: error:
    • Variable not in scope:
        hannoi :: t2 -> t -> t -> t1 -> [(t2, t, t)]
    • Perhaps you meant ‘hanoi’ (line 1)
!

hannoi を hanoi に変更して再チャレンジしましたが、main 関数がないとまたまたクレーム。

*/hannoi/p
  hannoi (n-1) work to from
*s/hannoi/hanoi/p
  hanoi (n-1) work to from
*w
120
*!runghc hanoi.hs
hanoi.hs:0:53: error:
    • Variable not in scope: main :: IO a0
    • Perhaps you meant ‘min’ (imported from Prelude)
!
*,p
hanoi 0 _ _ _ = []
hanoi n from to work =
  hanoi (n-1) from work to ++
  [(n, work, to)] ++
  hanoi (n-1) work to from
*$
  hanoi (n-1) work to from

main 関数を付け加えて何度めかのチャレンジ。

*a
main = print $ hanoi 3 'A' 'B' 'C'
.
*,p
hanoi 0 _ _ _ = []
hanoi n from to work =
  hanoi (n-1) from work to ++
  [(n, work, to)] ++
  hanoi (n-1) work to from
main = print $ hanoi 3 'A' 'B' 'C'
*w
156

しかし、答えがおかしいようです。

*!runghc hanoi.hs
[(1,'C','B'),(2,'B','C'),(1,'A','C'),(3,'C','B'),(1,'B','A'),(2,'A','B'),(1,'C','B')]
!

[(n, work, to)] を [(n, from, to)] に変更。

*/n, work/p
  [(n, work, to)] ++
*s/work, to/from, to/p
  [(n, from, to)] ++
*w
156

しかしまだ、答えがおかしいようです。

*!runghc hanoi.hs
[(1,'A','B'),(2,'A','C'),(1,'B','C'),(3,'A','B'),(1,'C','A'),(2,'C','B'),(1,'A','B')]
!

main の引数の順番が間違っていたので修正。

*/'A'/p
main = print $ hanoi 3 'A' 'B' 'C'
*s/'A'.*$/'A' 'C' 'B'/p
main = print $ hanoi 3 'A' 'C' 'B'
*w
156

こんどはどうでしょう。

*!runghc hanoi.hs
[(1,'A','C'),(2,'A','B'),(1,'C','B'),(3,'A','C'),(1,'B','A'),(2,'B','C'),(1,'A','C')]
!

大成功 !!

小さい Haskell のプログラムを作るときは、作成からデバッグまですべて ed の中でできます。カーソル移動がないのですごく快適です。