Gauche練習帳 cat/nl/grep/sed/sort/uniqもどきを書いてみる
Gaucheのドキュメントには「プログラマやシステム管理者がこなす日常の雑事を効率よくSchemeで書けるようにすることを目的として設計されています」とあります。ということは、普段shellやawkやperlやなんかで書いているような実用的で短いプログラム、いわゆるワンライナーみたいなやつがSchemeでもできるということなのでしょう。
練習のためにいくつか書いてみました。もっとエレガントな書き方があれば教えていただけると嬉しいです。
cat
最初はいわゆるcat。標準入力の内容をそのまま標準出力に出すだけ。とくに役に立たないけど、まあ基本形ってことで。
(案1)命令型言語っぽくdoでループするとこうかな。
(do ((s (read-line) (read-line))) ((eof-object? s)) (print s))
(案2)named letでループするならこうか。
(let loop ((s (read-line))) (unless (eof-object? s) (print s) (loop (read-line))))
(案3)port-for-eachを使うと本当にワンライナーになる。
(port-for-each print read-line)
実行方法は以下のような感じ。他も同じ。
gosh cat.scm < foo.txt
nl
行番号をつけて出力するプログラム。set!を使わないようにするならnamed letがよさげ。
(let loop ((s (read-line)) (n 1)) (unless (eof-object? s) (print (format #f "~4,'0d " n) s) (loop (read-line) (+ 1 n))))
共通マクロ
こういったプログラムは基本形が同じなので共通部分をマクロにしてみる。
textfilter.scm
(define-macro (textfilter-s body) `(port-for-each ,body read-line)) (define-macro (textfilter body) `(port-for-each (lambda (s) (print (,body s))) read-line))
grep
んで、grep。ファイルから特定文字列を含む行の出力。
(load "./textfilter") (textfilter-s (lambda (s) (if (string-scan s "foo") (print s))))
行を抽出するのに正規表現を使うならこう。いわゆるegrep。
(load "./textfilter") (textfilter-s (lambda (s) (if (rxmatch #/foo/ s) (print s))))
sed -e 's/foo/bar/g'
一括置換でたまに使うのがsed。
(load "./textfilter") (textfilter (lambda (s) (regexp-replace-all #/foo/ s "bar")))
lambdaの代わりにcutを使うとちょっと簡潔に書ける。
(load "./textfilter") (textfilter (cut regexp-replace-all #/foo/ <> "bar"))
sort
ソートは全行メモリに読み込む必要があるので、これまでと違うパターンでport-foldを使う。
(let ((lines (port-fold cons '() read-line))) (map print (sort lines)))
uniq
連続した同じ内容の行を1行に省略する処理。もう少しシンプルに書けないものか。
(use srfi-13) (let ((lines (port-fold-right (lambda (s l) (cond ((null? l) (list s)) ((string= (car l) s) l) (else (cons s l)))) '() read-line))) (map print lines))
試しに以下のようにパイプでつないでもうまくいってるっぽい。
> gosh cat.scm < foo.txt | gosh sort.scm | gosh uniq.scm