Lispが苦手な人のためのcho
S式で書けるテキスト処理ツールchoを、-Cオプションとパイプでかっこを書かずに使う話
choの紹介記事でも触れましたが、choはLisp風にawkが書けたらいいなという動機で作り始めました。 choでは、関数の呼び出しを (関数名 引数...) という形で書きます。
私はLisp系の言語が大好きなのですが、Lispの大量の丸かっこが苦手な方は多いと思います。 今回は、そんな人にも手軽に使ってもらえるように -C オプションの紹介をします。
きっかけ
文字列操作や日時のフォーマット変換、URLの分解、IPアドレスの判定などを実装しているうちに、「この関数群はシェルで単体で使ってもかなり便利だな」と思うようになりました。
そこで、関数をコマンドの引数で直接呼び出せるように用意したのが -C(--call-exact)オプションです。
-Cで関数を直接呼び出す
たとえば、通常のchoで2列目を大文字にするには、s/upper に $2 を渡します。
$ echo 'alice developer' | cho '(s/upper $2)'
DEVELOPER-C オプションをつければ、同じ処理をかっこなしで書けます。
$ echo 'alice developer' | cho -C s/upper @2
DEVELOPER書き方は cho -C 関数名 引数... です。-C の後に呼び出したい関数名と、その関数に渡す引数を順番に並べます。
これまで $2 と書いていたところは @2 になります。@0 は入力行全体、@1 は1列目、@2 は2列目です。 $だとシェルの変数展開と衝突するため、'$2'のようにクォートが必要になります。
単に列を取り出すだけなら、p も呼び出せます。
$ echo 'alice 30 developer' | cho -C p @1 @3
alice developer日時の変換も同じ書き方でできます。たとえば、UTCの日時を日本時間で表示してみます。
$ echo '2026-08-24T01:30:00Z' |
cho -C dt/fmt @1 '%Y-%m-%d %H:%M' Asia/Tokyo
2026-08-24 10:30ここでは1列目の日時、出力形式、タイムゾーンの順に引数を渡しています。
choをパイプでつなぐ
複数の関数を呼び出したい場合は、choをパイプでつなぎます。
たとえば、前後の空白を取り除き、ハイフンをアンダースコアに置き換え、最後に大文字にする処理を考えます。通常のchoなら関数を入れ子にして書けます。
$ echo ' api-server ' |
cho '(s/upper (s/replace (s/trim $0) "-" "_"))'
API_SERVERこれを -C で書くなら、choをパイプでつなぎます。
$ echo ' api-server ' |
cho -C s/trim @0 |
cho -C s/replace @0 - _ |
cho -C s/upper @0
API_SERVER最初のchoが api-server を出力し、次のchoが api_server にして、最後のchoが API_SERVER にします。それぞれの @0 は、その段階で受け取った行全体を指しています。
他のUnixコマンドと組み合わせる
sortやuniqなど、ほかのUnixコマンドとも組み合わせられます。
たとえば、7列目にURLが入っているログから、ホスト名だけを取り出して集計したいとします。awkやsedでURLを分解するのは少し正規表現が面倒ですが、choの url/host 関数を挟めば簡単に取り出せます。
$ cat app.log |
cho -C url/host @7 |
sort |
uniq -c |
sort -nr |
head -n 5行全体を渡すなら小文字の
-c今回は列(@2など)を引数で明示する-C(大文字)を紹介しましたが、行全体を自動で第1引数に渡す-c(小文字)もあります。 先ほどのパイプラインなら、cho -c s/trim | cho -c s/replace - _ | cho -c s/upperと書けます。
木構造になる処理は -C では書けない
1回の -C で呼び出せる関数は1つだけです。関数の引数に別の関数の結果を渡すような、木構造になる呼び出しは表現できません。
たとえば、1列目をそのまま残し、2列目だけを大文字にします。
$ echo 'alice developer' | cho '(p $1 (s/upper $2))'
alice DEVELOPERこの式を展開すると、次のような木構造になっています。
(p
$1
(s/upper $2))外側の p は、$1 と s/upper の実行結果を受け取ります。cho -C s/upper @2 をパイプでつないでも、その出力にはもう alice が残っていないため、同じ処理にはなりません。
こういうときは、素直にかっこを書きましょう。
まとめ
Lispで小さな式を組み合わせて大きな処理を作ることと、Unixシェルで小さなコマンドをパイプでつなぐことは、根底にある思想が似ている気がします。
Lispの大量の丸かっこが苦手な人にもぜひchoを使ってみてほしいです。