← 一覧へ戻る

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を使ってみてほしいです。