<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
    <title>k0f1sh</title>
    <subtitle>k0f1shの技術ブログ。自作ツールや日々の知見を記録しています。</subtitle>
    <link rel="self" type="application/atom+xml" href="https://k0f1sh.github.io/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://k0f1sh.github.io"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-26T00:00:00+00:00</updated>
    <id>https://k0f1sh.github.io/atom.xml</id>
    <entry xml:lang="ja">
        <title>choにCSVの列名参照を追加した</title>
        <published>2026-09-26T00:00:00+00:00</published>
        <updated>2026-09-26T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho06/"/>
        <id>https://k0f1sh.github.io/posts/cho06/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho06/">&lt;p&gt;&lt;a href=&quot;../cho01/&quot;&gt;cho&lt;/a&gt; は、Lisp風の式で入力行を加工するコマンドラインツールです。通常は空白で区切られた行を列に分けて扱いますが、&lt;code&gt;--csv&lt;/code&gt; を付けるとCSVも読み込めます。この記事では、CSVのヘッダー名で列を指定する方法と、その実装を紹介します。&lt;/p&gt;
&lt;p&gt;choでは、入力の列を &lt;code&gt;$1&lt;/code&gt;、&lt;code&gt;$2&lt;/code&gt; のように番号で参照できます。小さいファイルならこれで十分です。でも、横に長いCSVのヘッダーを左から追って「&lt;code&gt;created_at&lt;/code&gt; は何列目だっけ」と数えるのは、かなり面倒です。しかも途中に列が一つ増えたらズレてしまいます。&lt;/p&gt;
&lt;p&gt;そこで、ヘッダー付きのCSVでは &lt;code&gt;%created_at&lt;/code&gt; と名前で参照できるようにしました。&lt;/p&gt;
&lt;p&gt;例えば、次のような &lt;code&gt;users.csv&lt;/code&gt; があるとします。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;id,account_id,name,email,team,region,plan,status,owner,source,created_at,score&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1,101,Alice,alice@example.com,dev,JP,free,active,bob,web,2026-09-01,90&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メールアドレスと作成日を取り出す式は、列番号なら &lt;code&gt;$4&lt;/code&gt; と &lt;code&gt;$11&lt;/code&gt;、列名なら &lt;code&gt;%email&lt;/code&gt; と &lt;code&gt;%created_at&lt;/code&gt; です。&lt;code&gt;p&lt;/code&gt; は指定した値を空白区切りで出力します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat users.csv &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --csv --skip-header&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $4 $11)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice@example.com 2026-09-01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat users.csv &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --csv&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p %email %created_at)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice@example.com 2026-09-01&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;%name&lt;/code&gt; を使うと、choは先頭レコードをヘッダーとして読み、データ行から処理を始めます。&lt;code&gt;--skip-header&lt;/code&gt; を付け忘れてヘッダー行を処理してしまう心配もありません。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gnu.org/software/gawk/manual/html_node/Feature-History.html&quot;&gt;GNU awkでは5.3から &lt;code&gt;--csv&lt;/code&gt; でCSVを読めます&lt;/a&gt;。これがあったのでchoにもCSV入力をサポートしたいと思いました。&lt;/p&gt;
&lt;h2 id=&quot;name-toiuji-fa-nisitali-you&quot;&gt;&lt;code&gt;%name&lt;/code&gt; という記法にした理由&lt;/h2&gt;
&lt;p&gt;列名の前に付ける記号は、Perlのハッシュ変数から連想して &lt;code&gt;%&lt;/code&gt; にしました。CSVのヘッダー名から対応する列を引く、という操作がキーから値を引くハッシュに少し似ているためです。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$1       1列目を参照する&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%email   ヘッダーが email の列を参照する&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列名に空白がある場合は、&lt;code&gt;%&quot;display name&quot;&lt;/code&gt;と書けます。&lt;/p&gt;
&lt;p&gt;choでは、式の中で列番号を指定する &lt;code&gt;$1&lt;/code&gt;、CSVのヘッダー名を指定する &lt;code&gt;%email&lt;/code&gt;、&lt;a href=&quot;../cho04/&quot;&gt;&lt;code&gt;-C&lt;/code&gt; オプション&lt;/a&gt; の引数で列番号を指定する &lt;code&gt;@1&lt;/code&gt; という三つの書き方があります。&lt;code&gt;@1&lt;/code&gt; はシェルに展開されないようにするための記法で、指す列は &lt;code&gt;$1&lt;/code&gt; と同じです。&lt;/p&gt;
&lt;h2 id=&quot;hetudaming-hazui-chu-nilie-fan-hao-hejie-jue-suru&quot;&gt;ヘッダー名は最初に列番号へ解決する&lt;/h2&gt;
&lt;p&gt;choは最初にプログラムをパースして評価用のASTに変換してから、入力を1レコードずつ処理します。ASTに変換する時点ではCSVのヘッダーをまだ読んでいないため、&lt;code&gt;%email&lt;/code&gt; を直接「4列目」にはできません。そこで、式に現れたヘッダー名を &lt;code&gt;Program.header_fields&lt;/code&gt; に集め、ASTの式にはその配列の添字を &lt;code&gt;Expr::HeaderField(0)&lt;/code&gt; のように持たせます。&lt;/p&gt;
&lt;p&gt;入力の処理を始めると、最初のCSVレコードをヘッダーとして読みます。このレコードはデータ行として評価せず、参照した名前が何列目かを調べるために使います。名前と同じ順序で列番号を &lt;code&gt;header_field_indices: Vec&amp;lt;usize&amp;gt;&lt;/code&gt; に保存してから、次のレコード以降を処理します。データ行では、式が持つ添字から列番号を引いてフィールドを読むだけです。&lt;/p&gt;
&lt;figure class=&quot;header-resolve&quot;&gt;
  &lt;div class=&quot;header-resolve-step&quot;&gt;
    &lt;div class=&quot;header-resolve-heading&quot;&gt;&lt;span class=&quot;header-resolve-number&quot;&gt;1&lt;/span&gt;&lt;strong&gt;パースしてASTに変換する&lt;/strong&gt;&lt;span class=&quot;header-resolve-timing&quot;&gt;ヘッダーを読む前&lt;/span&gt;&lt;a class=&quot;header-resolve-source&quot; href=&quot;https://github.com/k0f1sh/cho/blob/32de98f7f3b65327b0078869260209d1ed1581e6/src/compiler.rs#L128-L138&quot;&gt;compiler.rs ↗&lt;/a&gt;&lt;/div&gt;
    &lt;div class=&quot;header-resolve-flow&quot;&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;%email&lt;/code&gt;&lt;span class=&quot;header-resolve-detail&quot;&gt;ASTでは &lt;code&gt;Expr::HeaderField(0)&lt;/code&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;header-resolve-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;Program.header_fields: Vec&amp;lt;String&amp;gt;&lt;/code&gt;&lt;span class=&quot;header-resolve-detail&quot;&gt;&lt;code&gt;[&quot;email&quot;, &quot;created_at&quot;]&lt;/code&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;header-resolve-step&quot;&gt;
    &lt;div class=&quot;header-resolve-heading&quot;&gt;&lt;span class=&quot;header-resolve-number&quot;&gt;2&lt;/span&gt;&lt;strong&gt;先頭レコードで列番号を決める&lt;/strong&gt;&lt;span class=&quot;header-resolve-timing&quot;&gt;一度だけ&lt;/span&gt;&lt;a class=&quot;header-resolve-source&quot; href=&quot;https://github.com/k0f1sh/cho/blob/32de98f7f3b65327b0078869260209d1ed1581e6/src/runtime/runner.rs#L135-L158&quot;&gt;runner.rs ↗&lt;/a&gt;&lt;/div&gt;
    &lt;div class=&quot;header-resolve-flow&quot;&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;ヘッダーでは &lt;code&gt;email&lt;/code&gt; が4列目&lt;/span&gt;&lt;span class=&quot;header-resolve-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;header_field_indices: Vec&amp;lt;usize&amp;gt;&lt;/code&gt;&lt;span class=&quot;header-resolve-detail&quot;&gt;&lt;code&gt;[4, 11]&lt;/code&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;header-resolve-step&quot;&gt;
    &lt;div class=&quot;header-resolve-heading&quot;&gt;&lt;span class=&quot;header-resolve-number&quot;&gt;3&lt;/span&gt;&lt;strong&gt;データ行から値を読む&lt;/strong&gt;&lt;span class=&quot;header-resolve-timing&quot;&gt;各レコードで繰り返す&lt;/span&gt;&lt;a class=&quot;header-resolve-source&quot; href=&quot;https://github.com/k0f1sh/cho/blob/32de98f7f3b65327b0078869260209d1ed1581e6/src/runtime/eval.rs#L202-L207&quot;&gt;eval.rs ↗&lt;/a&gt;&lt;/div&gt;
    &lt;div class=&quot;header-resolve-flow&quot;&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;Expr::HeaderField(0)&lt;/code&gt;&lt;/span&gt;&lt;span class=&quot;header-resolve-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;header_field_indices[0] = 4&lt;/code&gt;&lt;/span&gt;&lt;span class=&quot;header-resolve-arrow&quot; aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;header-resolve-node&quot;&gt;&lt;code&gt;record.field(4)&lt;/code&gt;&lt;span class=&quot;header-resolve-detail&quot;&gt;&lt;code&gt;alice@example.com&lt;/code&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;&lt;code&gt;(p %email %created_at)&lt;/code&gt; の例。配列の添字 &lt;code&gt;0&lt;/code&gt; は &lt;code&gt;email&lt;/code&gt; に対応し、列番号は先頭の一度だけ調べる。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;列名は完全一致で探します。式で参照した名前がヘッダーにない場合や、同じ名前の列が複数ある場合は、データ行を出力する前にエラーにします。例えば &lt;code&gt;email&lt;/code&gt; が二列あれば、&lt;code&gt;%email&lt;/code&gt; をどちらに結び付けるか決められません。一方、式に登場しない列名が重複していてもエラーにはしません。読まない列の名前が重なっているだけで、必要な列まで取り出せなくなるのは困るからです。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;%name&lt;/code&gt; は &lt;code&gt;--csv&lt;/code&gt; と一緒に使う記法です。通常の空白区切り入力ではヘッダー名による参照はできません。&lt;/p&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;choの仕様はできるだけ小さく保ちたかったので、最初はCSVでも &lt;code&gt;$1&lt;/code&gt; のような番号指定で十分かと思っていました。しかし横に長いCSVの列を数えるのはやっぱり面倒で、列名での参照記法を追加しました。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$1&lt;/code&gt;の番号指定も残しているので、番号だけで済むときはこれまでどおり書けます。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>自作のミニ言語用Emacsモードを作り、シェルから呼び出す</title>
        <published>2026-09-25T00:00:00+00:00</published>
        <updated>2026-09-25T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho05/"/>
        <id>https://k0f1sh.github.io/posts/cho05/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho05/">&lt;p&gt;&lt;a href=&quot;../cho01/&quot;&gt;cho&lt;/a&gt; は、Lisp風の構文でawkのような行指向のテキスト処理を行うコマンドラインツールです。&lt;/p&gt;
&lt;p&gt;例えば、名前と &lt;code&gt;role=developer&lt;/code&gt; の2列を受け取り、職種を取り出し、&lt;code&gt;s/upper&lt;/code&gt; で大文字にします。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alice role=developer&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $1 (s/upper (s/after $2 &amp;quot;=&amp;quot;)))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice DEVELOPER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;私が普段EmacsでLispを書くときは、Paredit、rainbow-delimiters、ElDocを使っています。&lt;/p&gt;
&lt;p&gt;シェル上で入力する場合はそれらの拡張の支援がないので、コードが少し複雑になると辛いなと思うことがあります。
そこで、choの式を含むシェルコマンド用のEmacsメジャーモード&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/tree/main/editors/emacs&quot;&gt;&lt;code&gt;cho-mode&lt;/code&gt;&lt;/a&gt;を作り、入力中のコマンドをEmacsで開けるように&lt;code&gt;.bashrc&lt;/code&gt;に設定を追加しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/cho-mode.gif&quot; alt=&quot;シェル上でEmacsを起動しcho-modeで編集する様子&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bashの入力中にショートカットを入力し、入力中のコマンドをEmacsで開く&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cho-mode&lt;/code&gt; で編集し、&lt;code&gt;C-c C-c&lt;/code&gt; で保存してEmacsを終了する&lt;/li&gt;
&lt;li&gt;保存した内容をBashに戻す&lt;/li&gt;
&lt;li&gt;内容を確認し、Enterで実行する&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;shi-xian-fang-fa&quot;&gt;実現方法&lt;/h2&gt;
&lt;h3 id=&quot;ru-li-tu-zhong-nonei-rong-woemacsdekai-ku&quot;&gt;入力途中の内容をEmacsで開く&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.bashrc&lt;/code&gt; に &lt;code&gt;bind -x&lt;/code&gt; の設定を書き、&lt;code&gt;C-x C-l&lt;/code&gt; を押すと入力中のコマンドをEmacsで開くようにしています。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bind -x&lt;/code&gt; から呼ばれる関数では、&lt;code&gt;READLINE_LINE&lt;/code&gt; で入力中のコマンドを取得できます。この内容を一時ファイルに書き出し、cho用の設定を読み込んだEmacsで開きます。&lt;code&gt;cho-mode&lt;/code&gt; で編集して正常終了した場合だけ、一時ファイルから編集結果を読み戻します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;__cho_edit_readline_line&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    local&lt;/span&gt;&lt;span&gt; cho_edit_file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cho_edit_file&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;mktemp&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; cho-readline.XXXXXX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; || return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    printf&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;%s\n&amp;#39; &amp;quot;&lt;/span&gt;&lt;span&gt;$READLINE_LINE&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$cho_edit_file&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; command&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; emacs&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --init-directory&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;/.config/cho-emacs&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -nw \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;&lt;/span&gt;&lt;span&gt;$cho_edit_file&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --eval&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(cho-mode)&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        IFS&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; read -r -d&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;&amp;#39; READLINE_LINE&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$cho_edit_file&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; :&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        READLINE_LINE&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;/span&gt;&lt;span&gt;${READLINE_LINE&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;$&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        READLINE_POINT&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;#&lt;/span&gt;&lt;span&gt;READLINE_LINE}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    command&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; rm&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -f --&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$cho_edit_file&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -x&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;&amp;quot;\C-x\C-l&amp;quot;:__cho_edit_readline_line&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/.config/cho-emacs/init.el&lt;/code&gt; には、例えば次のように設定します。&lt;code&gt;load-path&lt;/code&gt; のパスは、手元にあるchoリポジトリの場所に合わせて変更してください。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--init-directory&lt;/code&gt; で専用の設定ディレクトリを使うため、普段のEmacsの設定や追加パッケージがそのまま使えるとは限りません。Pareditとrainbow-delimitersを使う場合は、この専用設定からも読み込めるように別途導入しておきます。&lt;code&gt;cho-mode&lt;/code&gt; はこれらのパッケージが読み込める場合に自動で有効にします。ElDocはEmacsに標準で含まれています。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;add-to-list &amp;#39;load-path&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;expand-file-name&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;~/src/cho/editors/emacs&amp;quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;require &amp;#39;cho-mode&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;defun&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho-edit-save-and-exit&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;interactive&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;save-buffer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;kill-emacs 0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;defun&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho-edit-cancel&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;interactive&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;set-buffer-modified-p nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;kill-emacs 1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;define-key&lt;/span&gt;&lt;span&gt; cho-mode-map (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;kbd&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;C-c C-c&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; #&amp;#39;cho-edit-save-and-exit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;define-key&lt;/span&gt;&lt;span&gt; cho-mode-map (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;kbd&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;C-c C-k&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; #&amp;#39;cho-edit-cancel&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;C-c C-k&lt;/code&gt;で編集をキャンセルすると、Bashには元の入力が残ります。&lt;/p&gt;
&lt;h3 id=&quot;cho-modenozhong-shen&quot;&gt;cho-modeの中身&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;cho-mode&lt;/code&gt; はEmacsの &lt;code&gt;sh-mode&lt;/code&gt; を継承しています。編集するのはchoの式だけではなく、&lt;code&gt;echo&lt;/code&gt; やパイプを含むシェルコマンド全体だからです。その中から &lt;code&gt;cho &#39;(p $1)&#39;&lt;/code&gt; のように、シングルクォートで囲まれ、&lt;code&gt;(&lt;/code&gt; で始まるchoの式を見つけます。&lt;/p&gt;
&lt;p&gt;現在は通常の1行コマンドを主な対象にしています。コマンド置換やヒアドキュメントなど、複雑なシェル構文は完全には解析していません。&lt;/p&gt;
&lt;p&gt;choの式では、関数名、&lt;code&gt;$1&lt;/code&gt; などのフィールド参照、&lt;code&gt;NR&lt;/code&gt;・&lt;code&gt;NF&lt;/code&gt;、真偽値、正規表現に色が付きます。&lt;/p&gt;
&lt;p&gt;通常、&lt;code&gt;sh-mode&lt;/code&gt; ではシングルクォートの中身が文字列として扱われ、その中のかっこをS式の構造として認識できません。そこで、choの式を囲むクォートについて、&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/blob/d165b51cfaa457e1a1d53c21cff77be1738720f1/editors/emacs/cho-mode.el#L92-L103&quot;&gt;Emacsが構文を認識するための属性を変更しています&lt;/a&gt;。コマンドに書かれたクォート自体は残し、中のかっこをS式として認識させています。これでPareditによる編集やrainbow-delimitersによる色分けが使えます。&lt;/p&gt;
&lt;p&gt;また、式の先頭で関数名を入力してTabを押すと、補完候補が出ます。ElDocでは、編集中の関数の引数や戻り値の型を確認できます。例えば、冒頭の &lt;code&gt;s/after&lt;/code&gt; を書くときも、引数の順番を確認しながら入力できます。&lt;/p&gt;
&lt;h3 id=&quot;yan-yu-noding-yi-karabu-wan-detawozuo-ru&quot;&gt;言語の定義から補完データを作る&lt;/h3&gt;
&lt;p&gt;補完やElDoc用の情報はchoのリポジトリの&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/blob/main/metadata.json&quot;&gt;&lt;code&gt;metadata.json&lt;/code&gt;&lt;/a&gt;にあります。
このJSONには、関数名や別名、引数と戻り値の型、説明が入っています。&lt;/p&gt;
&lt;p&gt;choでは関数を追加するときに、ASTを組み立てる処理と、説明文・使用例を、同じマクロ呼び出し（&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/blob/d165b51cfaa457e1a1d53c21cff77be1738720f1/src/language/mod.rs#L221&quot;&gt;&lt;code&gt;define_callable!&lt;/code&gt;&lt;/a&gt;）にまとめて定義します。&lt;/p&gt;
&lt;p&gt;この定義からパーサー用とドキュメント用の実装を生成し、ドキュメント用の情報からヘルプと &lt;code&gt;metadata.json&lt;/code&gt; を作っています。
新しく関数を作るときにドキュメントが漏れないようにこのような形にしてみました。&lt;/p&gt;
&lt;p&gt;Emacs側では &lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/blob/main/editors/emacs/generate-cho-mode-data.el&quot;&gt;&lt;code&gt;generate-cho-mode-data.el&lt;/code&gt;&lt;/a&gt; がJSONを &lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/blob/main/editors/emacs/cho-mode-data.el&quot;&gt;&lt;code&gt;cho-mode-data.el&lt;/code&gt;&lt;/a&gt; に変換します。&lt;code&gt;cho-mode&lt;/code&gt; はこの生成済みのElispを読み込むので、起動するたびに &lt;code&gt;metadata.json&lt;/code&gt; を解析する必要はありません。&lt;/p&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cho-mode&lt;/code&gt;を作り、シェル上で専用設定のEmacsを起動することで、普段と同じ感覚で編集できるようになりました。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cho-mode&lt;/code&gt; のコードと導入方法は&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho/tree/main/editors/emacs&quot;&gt;choリポジトリの &lt;code&gt;editors/emacs/&lt;/code&gt;&lt;/a&gt; に置いています。シェルから起動する設定は私の使い方の一例です。&lt;/p&gt;
&lt;p&gt;この記事の設定は、Emacs 31.1、Bash 5.3.15で動作を確認しました。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>Lispが苦手な人のためのcho</title>
        <published>2026-09-23T00:00:00+00:00</published>
        <updated>2026-09-23T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho04/"/>
        <id>https://k0f1sh.github.io/posts/cho04/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho04/">&lt;p&gt;&lt;a href=&quot;../cho01/&quot;&gt;choの紹介記事&lt;/a&gt;でも触れましたが、choはLisp風にawkが書けたらいいなという動機で作り始めました。
choでは、関数の呼び出しを &lt;code&gt;(関数名 引数...)&lt;/code&gt; という形で書きます。&lt;/p&gt;
&lt;p&gt;私はLisp系の言語が大好きなのですが、Lispの大量の丸かっこが苦手な方は多いと思います。
今回は、そんな人にも手軽に使ってもらえるように &lt;code&gt;-C&lt;/code&gt; オプションの紹介をします。&lt;/p&gt;
&lt;h2 id=&quot;kitukake&quot;&gt;きっかけ&lt;/h2&gt;
&lt;p&gt;文字列操作や日時のフォーマット変換、URLの分解、IPアドレスの判定などを実装しているうちに、「この関数群はシェルで単体で使ってもかなり便利だな」と思うようになりました。&lt;/p&gt;
&lt;p&gt;そこで、関数をコマンドの引数で直接呼び出せるように用意したのが &lt;code&gt;-C&lt;/code&gt;（&lt;code&gt;--call-exact&lt;/code&gt;）オプションです。&lt;/p&gt;
&lt;h2 id=&quot;cdeguan-shu-wozhi-jie-hu-bichu-su&quot;&gt;-Cで関数を直接呼び出す&lt;/h2&gt;
&lt;p&gt;たとえば、通常のchoで2列目を大文字にするには、&lt;code&gt;s/upper&lt;/code&gt; に &lt;code&gt;$2&lt;/code&gt; を渡します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alice developer&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(s/upper $2)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;DEVELOPER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-C&lt;/code&gt; オプションをつければ、同じ処理をかっこなしで書けます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alice developer&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -C&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; s/upper @2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;DEVELOPER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;書き方は &lt;code&gt;cho -C 関数名 引数...&lt;/code&gt; です。&lt;code&gt;-C&lt;/code&gt; の後に呼び出したい関数名と、その関数に渡す引数を順番に並べます。&lt;/p&gt;
&lt;p&gt;これまで &lt;code&gt;$2&lt;/code&gt; と書いていたところは &lt;code&gt;@2&lt;/code&gt; になります。&lt;code&gt;@0&lt;/code&gt; は入力行全体、&lt;code&gt;@1&lt;/code&gt; は1列目、&lt;code&gt;@2&lt;/code&gt; は2列目です。
&lt;code&gt;$&lt;/code&gt;だとシェルの変数展開と衝突するため、&lt;code&gt;&#39;$2&#39;&lt;/code&gt;のようにクォートが必要になります。&lt;/p&gt;
&lt;p&gt;単に列を取り出すだけなら、&lt;code&gt;p&lt;/code&gt; も呼び出せます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alice 30 developer&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -C&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; p @1 @3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice developer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日時の変換も同じ書き方でできます。たとえば、UTCの日時を日本時間で表示してみます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;2026-08-24T01:30:00Z&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -C dt/fmt @1 &amp;#39;%Y-%m-%d %H:%M&amp;#39; Asia/Tokyo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2026-08-24 10:30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでは1列目の日時、出力形式、タイムゾーンの順に引数を渡しています。&lt;/p&gt;
&lt;h2 id=&quot;chowopaipudetunagu&quot;&gt;choをパイプでつなぐ&lt;/h2&gt;
&lt;p&gt;複数の関数を呼び出したい場合は、choをパイプでつなぎます。&lt;/p&gt;
&lt;p&gt;たとえば、前後の空白を取り除き、ハイフンをアンダースコアに置き換え、最後に大文字にする処理を考えます。通常のchoなら関数を入れ子にして書けます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;  api-server  &amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho &amp;#39;(s/upper (s/replace (s/trim $0) &amp;quot;-&amp;quot; &amp;quot;_&amp;quot;))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;API_SERVER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これを &lt;code&gt;-C&lt;/code&gt; で書くなら、choをパイプでつなぎます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;  api-server  &amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -C s/trim @0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -C s/replace @0 - _ |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -C s/upper @0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;API_SERVER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初のchoが &lt;code&gt;api-server&lt;/code&gt; を出力し、次のchoが &lt;code&gt;api_server&lt;/code&gt; にして、最後のchoが &lt;code&gt;API_SERVER&lt;/code&gt; にします。それぞれの &lt;code&gt;@0&lt;/code&gt; は、その段階で受け取った行全体を指しています。&lt;/p&gt;
&lt;h3 id=&quot;ta-nounixkomandotozu-mihe-waseru&quot;&gt;他のUnixコマンドと組み合わせる&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sort&lt;/code&gt;や&lt;code&gt;uniq&lt;/code&gt;など、ほかのUnixコマンドとも組み合わせられます。&lt;/p&gt;
&lt;p&gt;たとえば、7列目にURLが入っているログから、ホスト名だけを取り出して集計したいとします。awkやsedでURLを分解するのは少し正規表現が面倒ですが、choの &lt;code&gt;url/host&lt;/code&gt; 関数を挟めば簡単に取り出せます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat app.log &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -C url/host @7 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    sort |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    uniq -c |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    sort -nr |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    head -n 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;行全体を渡すなら小文字の &lt;code&gt;-c&lt;/code&gt;&lt;/strong&gt;
今回は列（&lt;code&gt;@2&lt;/code&gt; など）を引数で明示する &lt;code&gt;-C&lt;/code&gt;（大文字）を紹介しましたが、行全体を自動で第1引数に渡す &lt;code&gt;-c&lt;/code&gt;（小文字）もあります。
先ほどのパイプラインなら、&lt;code&gt;cho -c s/trim | cho -c s/replace - _ | cho -c s/upper&lt;/code&gt;と書けます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;mu-gou-zao-ninaruchu-li-ha-c-dehashu-kenai&quot;&gt;木構造になる処理は &lt;code&gt;-C&lt;/code&gt; では書けない&lt;/h2&gt;
&lt;p&gt;1回の &lt;code&gt;-C&lt;/code&gt; で呼び出せる関数は1つだけです。関数の引数に別の関数の結果を渡すような、木構造になる呼び出しは表現できません。&lt;/p&gt;
&lt;p&gt;たとえば、1列目をそのまま残し、2列目だけを大文字にします。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alice developer&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $1 (s/upper $2))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice DEVELOPER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この式を展開すると、次のような木構造になっています。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(p&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  $1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (s/upper $2))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;外側の &lt;code&gt;p&lt;/code&gt; は、&lt;code&gt;$1&lt;/code&gt; と &lt;code&gt;s/upper&lt;/code&gt; の実行結果を受け取ります。&lt;code&gt;cho -C s/upper @2&lt;/code&gt; をパイプでつないでも、その出力にはもう &lt;code&gt;alice&lt;/code&gt; が残っていないため、同じ処理にはなりません。&lt;/p&gt;
&lt;p&gt;こういうときは、素直にかっこを書きましょう。&lt;/p&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Lispで小さな式を組み合わせて大きな処理を作ることと、Unixシェルで小さなコマンドをパイプでつなぐことは、根底にある思想が似ている気がします。&lt;/p&gt;
&lt;p&gt;Lispの大量の丸かっこが苦手な人にもぜひchoを使ってみてほしいです。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>choのフィールド範囲記法「$3..」をどう設計したか</title>
        <published>2026-09-22T00:00:00+00:00</published>
        <updated>2026-09-22T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho03/"/>
        <id>https://k0f1sh.github.io/posts/cho03/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho03/">&lt;p&gt;前回は、リストを持たないchoで文字列を分割するために作った特殊形式 &lt;a href=&quot;../cho02/&quot;&gt;&lt;code&gt;s/with&lt;/code&gt;&lt;/a&gt; について書きました。今回は、&lt;code&gt;$3..&lt;/code&gt; のように「あるフィールドから後ろをまとめて取り出す」記法について書きます。&lt;/p&gt;
&lt;h2 id=&quot;aruhuirudokaraxing-mo-madequ-ritai&quot;&gt;あるフィールドから行末まで取りたい&lt;/h2&gt;
&lt;p&gt;テキスト処理をしていると、「3列目から後ろを全部取り出したい」という場面がよくあります。&lt;/p&gt;
&lt;p&gt;たとえば、&lt;code&gt;ps aux&lt;/code&gt; の出力からコマンド部分を取り出したい場合です。&lt;code&gt;ps aux&lt;/code&gt; では、11列目の &lt;code&gt;COMMAND&lt;/code&gt; から後ろが実行中のコマンドラインになります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ps aux &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;root         1  0.0  0.1  22532 13356 ?        Ss   10:20   0:01 /sbin/init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice     1832  0.2  1.4 1337520 116220 ?      Sl   10:24   0:12 emacs --no-window-system notes.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;alice     2419  0.0  0.0   7720  3420 pts/0    S+   10:31   0:00 sleep 100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コマンドラインの引数の数はプロセスによって違うので、「11列目から最後の列まで」を取りたいのですが、終わりの列番号はあらかじめ決められません。&lt;/p&gt;
&lt;p&gt;awkなら、11列目から &lt;code&gt;NF&lt;/code&gt; までループで出力します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 11&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; NF&lt;/span&gt;&lt;span&gt;; i&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        printf&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;%s%s&amp;quot;&lt;/span&gt;&lt;span&gt;, $i, (i &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; NF&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; OFS&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ORS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかしchoにはそもそもループがありません。&lt;/p&gt;
&lt;p&gt;そこで「ここから後ろを全部取る」という操作を手軽に書けるように、フィールド参照の記法として &lt;code&gt;$11..&lt;/code&gt; を作りました。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ps aux &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; tail&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; +2&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $11..)&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;/sbin/init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;emacs --no-window-system notes.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sleep 100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;fen-ketahuirudowojie-he-sizhi-sanai&quot;&gt;分けたフィールドを結合し直さない&lt;/h2&gt;
&lt;p&gt;最初に欲しかったのは、単に &lt;code&gt;$11&lt;/code&gt; から &lt;code&gt;NF&lt;/code&gt; までを短く書く記法でした。ただ、いざ実装を考えたときに悩んだのが「フィールド間の区切りをどう扱うか」です。&lt;/p&gt;
&lt;p&gt;choの &lt;code&gt;p&lt;/code&gt;（print）は、引数に渡された複数の値を空白1つで区切って出力します。そのため、行の各列を順番に出力すると、元の行にあった複数の空白は1つに縮まってしまいます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;INFO request    completed in 20 ms\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho &amp;#39;(p $2 $3 $4 $5 $6)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request completed in 20 ms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一方、&lt;code&gt;$2..&lt;/code&gt; を使った場合は元の空白がそのまま残ります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;INFO request    completed in 20 ms\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho &amp;#39;(p $2..)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request    completed in 20 ms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これは、分割したフィールドを集めてスペースで繋ぎ直しているのではなく、「2列目が始まる位置から行末までを、入力行の文字列からそのまま切り出している」からです。&lt;/p&gt;
&lt;p&gt;空白が1つに潰れるくらいなら好みの問題かもしれませんが、&lt;code&gt;-F&lt;/code&gt; で区切りに正規表現を指定した場合は、結合し直す方法自体が決められなくなります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;alpha,beta:gamma\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho -F &amp;#39;[,:]&amp;#39; &amp;#39;(p $2..)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;beta:gamma&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例では、カンマ（&lt;code&gt;,&lt;/code&gt;）とコロン（&lt;code&gt;:&lt;/code&gt;）の両方がフィールドの区切りになっています。一度バラバラに分割したフィールドを後から結合しようとしても、カンマとコロンのどちらで繋げばいいのかわかりません。元の行からそのまま切り出す仕組みにしておけば、実際に使われていたコロンが自然に残ります。&lt;/p&gt;
&lt;h2 id=&quot;3-karafan-wei-noji-fa-he&quot;&gt;&lt;code&gt;$3..&lt;/code&gt; から範囲の記法へ&lt;/h2&gt;
&lt;p&gt;最初は &lt;code&gt;$3-&lt;/code&gt; や &lt;code&gt;$1-2&lt;/code&gt; のような記法（&lt;code&gt;cut&lt;/code&gt; コマンドの &lt;code&gt;-f 3-&lt;/code&gt; のようなイメージ）も考えていました。ただ、&lt;code&gt;-&lt;/code&gt; は引き算や負の数と見分けがつきにくく、S式の中で少し浮いて見えます。そこで、RustやRubyでおなじみの &lt;code&gt;..&lt;/code&gt; を使って &lt;code&gt;$3..&lt;/code&gt; と書くことにしました。&lt;/p&gt;
&lt;p&gt;また、行末までの範囲だけでなく、先頭からの範囲や中間の範囲も取れるようにしています。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;one  two   three four\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $3..)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;three four&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;one  two   three four\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $..2)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;one  two&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;one  two   three four\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p $2..3)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;two   three&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$3..&lt;/code&gt;：3列目から行末まで&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$..2&lt;/code&gt;：1列目から2列目まで&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$2..3&lt;/code&gt;：2列目から3列目まで&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;範囲指定は、区切り文字も含んだ1つの文字列を返します。フィールド間の空白の幅もそのまま残ります。&lt;/p&gt;
&lt;h2 id=&quot;ge-huirudonowei-zhi-woji-lu-suru&quot;&gt;各フィールドの位置を記録する&lt;/h2&gt;
&lt;p&gt;この動作を実現するには、分割後の文字列だけでなく、各フィールドが元の行のどこにあったかを覚えておく必要があります。&lt;/p&gt;
&lt;p&gt;choの内部では、入力行を単なる文字列の配列に分解するのではなく、元の行文字列と一緒に、各フィールドの開始・終了位置（バイト位置）のベクタを保持しています。実際の構造体から関係する部分を抜き出すと、次のようになっています。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;line&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    line&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;: &amp;amp;&lt;/span&gt;&lt;span&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;line str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    field_spans&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;(&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;usize&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; usize&lt;/span&gt;&lt;span&gt;)&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先ほどのログを例にすると、各フィールドの位置関係は次のようになります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INFO request    completed in 20 ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;^^^^ ^^^^^^^    ^^^^^^^^^ ^^ ^^ ^^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$1   $2         $3        $4 $5 $6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;field_spans&lt;/code&gt; には、各フィールドの開始位置と終了位置が入ります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$1  ( 0,  4)  -&amp;gt; INFO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$2  ( 5, 12)  -&amp;gt; request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$3  (16, 25)  -&amp;gt; completed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$4  (26, 28)  -&amp;gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$5  (29, 31)  -&amp;gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$6  (32, 34)  -&amp;gt; ms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;たとえば &lt;code&gt;$2..3&lt;/code&gt; を取り出すときは、2列目の開始位置（5）から3列目の終了位置（25）までを元の行からスライスします。その結果が &lt;code&gt;request    completed&lt;/code&gt; です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$3..&lt;/code&gt; であれば、3列目の開始位置（16）から行末までを切り出します。&lt;/p&gt;
&lt;p&gt;フィールドを分割した時点で文字列として切り離してしまうと、元の位置や区切り文字の情報は失われてしまいます。行全体の文字列を残したまま位置だけを記録しておくことで、分割時に各フィールドの文字列を個別に確保せず、素直に実装できました。&lt;/p&gt;
&lt;h2 id=&quot;csvdehaaeteshi-enaiyounisita&quot;&gt;CSVではあえて使えないようにした&lt;/h2&gt;
&lt;p&gt;この範囲指定ですが、CSVモード（&lt;code&gt;--csv&lt;/code&gt;）では使えないようにしています。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ printf &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;name,city\nAlice,&amp;quot;Tokyo, Japan&amp;quot;\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    cho --csv --skip-header &amp;#39;(p $2..)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cho: field ranges are not supported with --csv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSVでは、入力行に書かれた文字と、パースされたフィールドの値が異なることがあるためです。&lt;/p&gt;
&lt;p&gt;たとえば上の例の2列目は、元の行では引用符を含んだ &lt;code&gt;&quot;Tokyo, Japan&quot;&lt;/code&gt; ですが、フィールドの値としては &lt;code&gt;Tokyo, Japan&lt;/code&gt; になってほしいはずです。&lt;/p&gt;
&lt;p&gt;元の文字列から切り出すと引用符が残り、パース後の値を繋ぎ直すと元の表記を保持できません。&lt;/p&gt;
&lt;p&gt;そのため、現在はCSVでの範囲指定をエラーにしています。&lt;/p&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;「ここから後ろを全部取りたい」という操作のために、フィールドを結合し直すのではなく「元の行から切り出す」という形に落ち着きました。&lt;/p&gt;
&lt;p&gt;取り出したい範囲を短く書けて、元の空白や区切り文字も残せるようになりました。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>リストのないchoで文字列を分割する</title>
        <published>2026-09-21T00:00:00+00:00</published>
        <updated>2026-09-21T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho02/"/>
        <id>https://k0f1sh.github.io/posts/cho02/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho02/">&lt;p&gt;前回は、awkのように列を取り出してS式で加工できる&lt;a href=&quot;../cho01/&quot;&gt;テキスト処理ツールcho&lt;/a&gt;を紹介しました。今回は、choを作る中で悩んだ「文字列の分割」について書きます。&lt;/p&gt;
&lt;h2 id=&quot;risutowochi-tanaiyan-yu-desplitwodoushu-kuka&quot;&gt;リストを持たない言語でsplitをどう書くか&lt;/h2&gt;
&lt;p&gt;choでは意図的に、配列やリスト、タプル、変数代入、ループ、ユーザー定義関数を用意していません。短いワンライナーを書くための道具なので、できることを増やすより、式を読んだときに考えることを少なくしたいからです。Lisp風の構文なのにリストを使えないのは少し寂しいですが……（Lispは「LISt Processing」なのに。）&lt;/p&gt;
&lt;p&gt;たとえばEmacs Lispなら、&lt;code&gt;split-string&lt;/code&gt; の結果をリストとして受け取れます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;split-string&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;api:worker:8080&amp;quot; &amp;quot;:&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; =&amp;gt; (&amp;quot;api&amp;quot; &amp;quot;worker&amp;quot; &amp;quot;8080&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;nth&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;split-string&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;api:worker:8080&amp;quot; &amp;quot;:&amp;quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; =&amp;gt; &amp;quot;worker&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;choにはリスト型がないので、同じ形では返せません。そこで最初は、分割した文字列から指定位置の一つだけを返す &lt;code&gt;s/part&lt;/code&gt; を作りました。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; 当時の構文。現在のchoでは使えません。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(s/part &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;api:worker:8080&amp;quot; &amp;quot;:&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; =&amp;gt; &amp;quot;worker&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;位置は &lt;code&gt;$1&lt;/code&gt; などと同じく1始まりです。&lt;/p&gt;
&lt;p&gt;一つだけ取り出すなら十分です。しかし、複数の部分を使うと同じ文字列を何度も分割する式になります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; 当時の構文。現在のchoでは使えません。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(s/join &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (s/part &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;api:worker:8080&amp;quot; &amp;quot;:&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (s/part &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;api:worker:8080&amp;quot; &amp;quot;:&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; =&amp;gt; &amp;quot;worker/8080&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分割結果をリストにせず、一度だけ分割して複数の部分を使うにはどうしたらいいか。そこで考えたのが &lt;code&gt;s/with&lt;/code&gt;（&lt;code&gt;s/&lt;/code&gt; は文字列処理を表す接頭辞）です。&lt;/p&gt;
&lt;h2 id=&quot;fen-ge-sitazhi-woyi-shi-de-narekodonisuru&quot;&gt;分割した値を一時的なレコードにする&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;s/with&lt;/code&gt; は、値を分割し、その結果を &lt;code&gt;$1&lt;/code&gt;、&lt;code&gt;$2&lt;/code&gt;、…で参照しながら &lt;code&gt;BODY&lt;/code&gt; の式を評価します。その式の結果が、&lt;code&gt;s/with&lt;/code&gt; の返り値になります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;s/with&lt;/code&gt; は、&lt;code&gt;if&lt;/code&gt; と同じく&lt;strong&gt;特殊形式&lt;/strong&gt;として扱っています。通常の関数のように引数をすべて先に評価すると、&lt;code&gt;BODY&lt;/code&gt; の &lt;code&gt;$1&lt;/code&gt; は元の入力行の1列目を指してしまいます。分割する値と区切り文字を先に評価し、&lt;code&gt;BODY&lt;/code&gt; だけを分割後のフィールドで評価するために、この形にしました。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;api:worker:8080&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(s/with $1 &amp;quot;:&amp;quot; (s/join &amp;quot;/&amp;quot; $2 $3))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;worker/8080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;書式は &lt;code&gt;(s/with VALUE DELIMITER BODY)&lt;/code&gt; です。&lt;code&gt;VALUE&lt;/code&gt; が分割する値、&lt;code&gt;DELIMITER&lt;/code&gt; が区切り文字列、&lt;code&gt;BODY&lt;/code&gt; が分割後の値を使う式です。&lt;code&gt;DELIMITER&lt;/code&gt; を省略した &lt;code&gt;(s/with VALUE BODY)&lt;/code&gt; では、通常の入力行と同じように空白で分割します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BODY&lt;/code&gt; から見ると、渡した値全体が &lt;code&gt;$0&lt;/code&gt;、分割後の各部分が &lt;code&gt;$1&lt;/code&gt; 以降、列数が &lt;code&gt;NF&lt;/code&gt; です。区切り文字列を指定した場合は、空の部分も残ります。&lt;/p&gt;
&lt;p&gt;たとえば、入力行の3列目を &lt;code&gt;:&lt;/code&gt; で分割し、2番目の部分を &lt;code&gt;s/upper&lt;/code&gt; で大文字にします。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;job42 ready api:worker:8080&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (s/with $3 &amp;quot;:&amp;quot; (s/upper $2)))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WORKER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;外側では &lt;code&gt;$3&lt;/code&gt; が &lt;code&gt;api:worker:8080&lt;/code&gt; ですが、&lt;code&gt;s/with&lt;/code&gt; の &lt;code&gt;BODY&lt;/code&gt; では &lt;code&gt;$2&lt;/code&gt; が &lt;code&gt;worker&lt;/code&gt; を指します。&lt;code&gt;BODY&lt;/code&gt; を抜けると元のレコードのフィールド参照に戻ります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;job42 ready api:worker:8080&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (s/with $3 &amp;quot;:&amp;quot; $2) $1)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;worker job42&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lispの&lt;code&gt;let&lt;/code&gt;に少し似ていますが、ここでは&lt;code&gt;BODY&lt;/code&gt;の間だけ&lt;code&gt;$1&lt;/code&gt;などの参照先を差し替えています。変数に名前を付けたり、値を代入し直したりする操作はありません。&lt;/p&gt;
&lt;p&gt;一つの値の中に別の区切り方が必要なら、&lt;code&gt;s/with&lt;/code&gt; を重ねられます。&lt;/p&gt;
&lt;p&gt;choにはURLのクエリパラメータを取り出す &lt;code&gt;url/query-get&lt;/code&gt; もありますが、対象は絶対URLです。HTTPリクエスト行に現れる &lt;code&gt;/api/users?...&lt;/code&gt; のような相対URLには使えないため、ここでは &lt;code&gt;s/with&lt;/code&gt; で分割します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;GET /api/users?limit=20&amp;amp;sort=asc&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(s/with $2 &amp;quot;?&amp;quot; (s/with $2 &amp;quot;&amp;amp;&amp;quot; (s/with $1 &amp;quot;=&amp;quot; $2)))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;まずURLを &lt;code&gt;?&lt;/code&gt; で分け、クエリを &lt;code&gt;&amp;amp;&lt;/code&gt; で分け、先頭の &lt;code&gt;limit=20&lt;/code&gt; を &lt;code&gt;=&lt;/code&gt; で分けます。段階ごとに &lt;code&gt;$1&lt;/code&gt; や &lt;code&gt;$2&lt;/code&gt; が指す値も変わります。入れ子が深くなるほど読みづらくなるので、このあたりが &lt;code&gt;s/with&lt;/code&gt; で書く限界かもしれません。&lt;/p&gt;
&lt;h2 id=&quot;yi-bu-fen-dakenaramotutoduan-ku&quot;&gt;一部分だけならもっと短く&lt;/h2&gt;
&lt;p&gt;区切り文字の前後どちらか一方が欲しいだけなら、&lt;code&gt;s/with&lt;/code&gt; より &lt;code&gt;s/before&lt;/code&gt; と &lt;code&gt;s/after&lt;/code&gt; が簡単です。どちらも最初に現れる区切り文字を使います。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;status=active&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (s/before $1 &amp;quot;=&amp;quot;) (s/after $1 &amp;quot;=&amp;quot;))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status active&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;複数の部分を組み合わせるなら &lt;code&gt;s/with&lt;/code&gt;、前後の片方だけなら &lt;code&gt;s/before&lt;/code&gt; か &lt;code&gt;s/after&lt;/code&gt;、という使い分けにしています。区切りに正規表現が必要な場合は &lt;code&gt;re/with&lt;/code&gt; もあります。&lt;/p&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;s/with&lt;/code&gt;を作ったことで、リストを導入せずに、分割した複数の値を扱えるようになりました。&lt;/p&gt;
&lt;p&gt;もともとある&lt;code&gt;$1&lt;/code&gt;や&lt;code&gt;$2&lt;/code&gt;を、文字列の分割にも使えたのが、設計を考えていて面白かったところです。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>awkをLispで書きたかったのでテキスト処理ツール『cho』を作った</title>
        <published>2026-09-20T00:00:00+00:00</published>
        <updated>2026-09-20T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/cho01/"/>
        <id>https://k0f1sh.github.io/posts/cho01/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/cho01/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho&quot;&gt;&lt;code&gt;cho&lt;/code&gt;&lt;/a&gt; というコマンドラインツールを作っています。
choはawkを参考にした小さなテキスト処理ツールです。
標準入力を1行ずつ読み込み、行の絞り込みや列の取り出し、加工をS式で記述できます。&lt;/p&gt;
&lt;p&gt;awkの列を手軽に扱えるところが好きですが、いつも書き方を忘れてしまいます。
EmacsやClojureが好きなので、awkもLispで書けたらなぁと思って作り始めました。&lt;/p&gt;
&lt;p&gt;choでは、日時やIPアドレスなど、文字列の比較だけでは書きにくい絞り込みもできます。
たとえば、日時とIPアドレスが並ぶログから、2026年8月1日以降で、IPアドレスが&lt;code&gt;10.0.0.0/8&lt;/code&gt;に含まれる行を取り出してみます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat events.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2026-08-02T09:00:00Z 10.1.2.3 deploy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2026-07-31T23:00:00Z 10.2.3.4 old&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2026-08-03T12:00:00Z 8.8.8.8 external&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat events.log &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(f (dt/&amp;gt;= $1 &amp;quot;2026-08-01T00:00:00Z&amp;quot;)) (f (cidr/contains? &amp;quot;10.0.0.0/8&amp;quot; $2)) (p $1 $2 $3)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2026-08-02T09:00:00Z 10.1.2.3 deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;cho-note-zheng&quot;&gt;cho の特徴&lt;/h2&gt;
&lt;h3 id=&quot;1-awknoyounishou-qing-nilie-woqu-richu-seru&quot;&gt;1. awkのように手軽に列を取り出せる&lt;/h3&gt;
&lt;p&gt;「Lispでテキスト処理をする」だけであれば、SchemeやBabashkaを使うほうがよいと思います。
しかし、1行ずつ読み込んで、列に分割して値を取り出すところまで書くとちょっと面倒です。
choはawkのように入力をデフォルトで空白区切りに分割し、各列を &lt;code&gt;$1&lt;/code&gt;、&lt;code&gt;$2&lt;/code&gt; で参照できるようにします。
列数の &lt;code&gt;NF&lt;/code&gt;、レコード番号の &lt;code&gt;NR&lt;/code&gt;、レコード全体の &lt;code&gt;$0&lt;/code&gt; も使えます。&lt;/p&gt;
&lt;p&gt;たとえば、サービス名、応答時間（ミリ秒）、リクエストのパスが並ぶログを処理してみます。
2列目を数値として比較し、応答時間が20ミリ秒を超えた行のサービス名とパスを出力します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat timings.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;api 18 /health&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;api 150 /users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;api 240 /orders&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;worker 180 /jobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat timings.log &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(f (&amp;gt; $2 20)) (p $1 $3)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;api /users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;api /orders&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;worker /jobs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;f&lt;/code&gt; は &lt;code&gt;filter&lt;/code&gt; の短縮形で、条件を満たしたレコードだけを通します。&lt;code&gt;p&lt;/code&gt; は &lt;code&gt;print&lt;/code&gt; の短縮形で、指定した値を出力します。&lt;/p&gt;
&lt;p&gt;CSVやTSVにも対応しています。CSVモード（&lt;code&gt;--csv&lt;/code&gt;）では、引用符で囲まれたフィールド内のカンマや改行も扱えます。
&lt;code&gt;Tokyo, Japan&lt;/code&gt;もひとつのフィールドとして扱えます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat people.csv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;name,city&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Alice,&amp;quot;Tokyo, Japan&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Bob,Osaka&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat people.csv &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --csv --skip-header&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (s/join &amp;quot; -&amp;gt; &amp;quot; $1 $2))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Alice -&amp;gt; Tokyo, Japan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Bob -&amp;gt; Osaka&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;2-chu-li-wosshi-deshu-keru&quot;&gt;2. 処理をS式で書ける&lt;/h3&gt;
&lt;p&gt;choでは、関数の呼び出しを &lt;code&gt;(関数名 引数...)&lt;/code&gt; の形で書きます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(filter (&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; $2 &lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;print&lt;/span&gt;&lt;span&gt; $1 (s/upper $3))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;awkでいう &lt;code&gt;awk &#39;$2 &amp;gt; 20 {print $1, toupper($3)}&#39;&lt;/code&gt; に相当します。&lt;/p&gt;
&lt;h3 id=&quot;3-ji-ji-yasotohaji-cun-komandotozu-mihe-waseru&quot;&gt;3. 集計やソートは既存コマンドと組み合わせる&lt;/h3&gt;
&lt;p&gt;choはスクリプトを書くほどではない処理を、ワンライナーで簡単に書けるようにするために作っています。
そのため、配列、ループ、ユーザー定義関数、代入、&lt;code&gt;BEGIN&lt;/code&gt; / &lt;code&gt;END&lt;/code&gt; ブロックは用意していません。こうした機能が必要になる処理には、awkやスクリプト言語を使うほうがよいと思っています。&lt;/p&gt;
&lt;p&gt;出現頻度のカウントやソートなどは、素直に &lt;code&gt;sort&lt;/code&gt; や &lt;code&gt;uniq&lt;/code&gt; などの既存コマンドにお任せする方針にしています。&lt;/p&gt;
&lt;p&gt;先ほどの &lt;code&gt;timings.log&lt;/code&gt; で、応答時間が100ミリ秒を超えた行のサービス名を取り出し、&lt;code&gt;sort | uniq -c&lt;/code&gt; で数えます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat timings.log &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(f (&amp;gt; $2 100)) (p $1)&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; sort&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; uniq&lt;/span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      2 api&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;      1 worker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;4-ri-shi-yaipadoresunadowo-xing-niying-zitebi-jiao-jia-gong-dekiru&quot;&gt;4. 日時やIPアドレスなどを、型に応じて比較・加工できる&lt;/h3&gt;
&lt;p&gt;ワンライナーでも、日時やIPアドレス、URL、単位付きのファイルサイズを扱いたくなることがあります。&lt;/p&gt;
&lt;p&gt;日時の比較ではタイムゾーンを考慮する必要がありますし、IPアドレスがCIDRで指定したネットワークに含まれるかは、文字列の比較だけでは判定できません。&lt;/p&gt;
&lt;p&gt;choでは、各フィールドを文字列として読み込み、関数が求める型に応じて自動的に変換します。使う側で型を指定しなくても、次のような処理を書けます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日付や日時を比較し、日数の加算や日時の差分を計算する（&lt;code&gt;d/&amp;gt;=&lt;/code&gt;、&lt;code&gt;d/add&lt;/code&gt;、&lt;code&gt;dt/diff&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MB&lt;/code&gt; や &lt;code&gt;MiB&lt;/code&gt; などの単位が付いたサイズを比較し、バイト数に変換する（&lt;code&gt;bs/&amp;gt;=&lt;/code&gt;、&lt;code&gt;bs/to-b&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;IPv4・IPv6アドレスを正規化し、CIDRで指定したネットワークに含まれるかを判定する（&lt;code&gt;ip&lt;/code&gt;、&lt;code&gt;cidr/contains?&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;URLからホスト名やパスを取り出す（&lt;code&gt;url/host&lt;/code&gt;、&lt;code&gt;url/path&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このほか、SemVerの比較やUUID・ULIDの生成にも対応しています。&lt;/p&gt;
&lt;p&gt;たとえば、1列目がIPアドレス、5列目がURLのログなら、次のようにネットワークで絞り込み、IPアドレスとURLのパスを出力できます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat access.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;192.168.1.10 2026-08-02T09:00:00Z GET 200 https://example.com/api/users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;8.8.8.8 2026-08-02T09:01:00Z GET 200 https://example.com/health&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;192.168.1.20 2026-08-02T09:02:00Z GET 404 https://example.com/missing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cat access.log &lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(f (cidr/contains? &amp;quot;192.168.0.0/16&amp;quot; $1)) (p $1 (url/path $5))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;192.168.1.10 /api/users&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;192.168.1.20 /missing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例では、&lt;code&gt;cidr/contains?&lt;/code&gt; に渡す &lt;code&gt;$1&lt;/code&gt; はIPアドレスとして、&lt;code&gt;url/path&lt;/code&gt; に渡す &lt;code&gt;$5&lt;/code&gt; はURLとして解釈されます。&lt;/p&gt;
&lt;p&gt;型変換に失敗した場合はその時点でエラー終了します。不正な値を黙って読み飛ばし、ログの形式が崩れていることを見落とすのを防ぐためです。&lt;/p&gt;
&lt;p&gt;たとえば、URLとして解釈できない文字列を &lt;code&gt;url/path&lt;/code&gt; に渡すと、どのレコードのどの引数で失敗したかが表示されます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;not-a-url&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (url/path $1))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cho: record 1: url/path: argument 1 expects Url (absolute URL), but &amp;quot;not-a-url&amp;quot; is not a valid absolute URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この場合、終了コードは &lt;code&gt;1&lt;/code&gt; になります。エラー時に代わりの値を使って処理を続けたいなら、&lt;code&gt;default&lt;/code&gt; で指定します。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ echo &lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;not-a-url&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cho&lt;/span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;(p (default (url/path $1) &amp;quot;INVALID_URL&amp;quot;))&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;INVALID_URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;5-helptoaproposde-diao-benagarashi-wozu-mili-terareru&quot;&gt;5. helpとaproposで、調べながら式を組み立てられる&lt;/h3&gt;
&lt;p&gt;ワンライナーを書いている途中で、関数名や引数の順番を調べるためにブラウザを開くのは面倒です。&lt;/p&gt;
&lt;p&gt;関数を検索する&lt;code&gt;--apropos&lt;/code&gt;と、詳しい使い方を表示する&lt;code&gt;--help&lt;/code&gt;を用意しました。&lt;/p&gt;
&lt;p&gt;関数名には、文字列を扱う &lt;code&gt;s/&lt;/code&gt;（String）、日付を扱う &lt;code&gt;d/&lt;/code&gt;（Date）、日時を扱う &lt;code&gt;dt/&lt;/code&gt;（DateTime）などの接頭辞を付けています。&lt;code&gt;s/upper&lt;/code&gt; は文字列の大文字化、&lt;code&gt;dt/&amp;gt;=&lt;/code&gt; は日時の比較、といった具合です。IPアドレスには &lt;code&gt;ip/&lt;/code&gt;、CIDRには &lt;code&gt;cidr/&lt;/code&gt; が付くので、関数を探すときの手がかりにもなります。&lt;/p&gt;
&lt;p&gt;「CIDRに関する関数は何があったか」「文字列のトリムはどうやるか」といった場合、&lt;code&gt;-k&lt;/code&gt;（または &lt;code&gt;--apropos&lt;/code&gt;）で関連する関数の一覧と概要を検索できます。&lt;/p&gt;
&lt;p&gt;以下は出力の抜粋です。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cho -k cidr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr             create a CIDR normalized to its network address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/from-mask   create a CIDR from an IP address and netmask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/netmask     return the network mask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/contains?   test CIDR membership&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/network     return the network address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/prefix      return the prefix length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使いたい関数が見つかったら、&lt;code&gt;cho --help &amp;lt;関数名&amp;gt;&lt;/code&gt; でその関数の型シグネチャと実行可能なサンプルコードを確認できます。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cho --help cidr/contains&lt;/span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;cidr/contains? — test CIDR membership&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Signatures:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  (cidr/contains? CIDR IPADDR) -&amp;gt; BOOLEAN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;Examples:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  echo 10.20.30.40 | cho &amp;#39;(cidr/contains? &amp;quot;10.0.0.0/8&amp;quot; $1)&amp;#39;  # =&amp;gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このサンプルのように、値を返す式を1つだけ書いた場合は、&lt;code&gt;p&lt;/code&gt; を付けなくても結果が自動的に出力されます。&lt;/p&gt;
&lt;h2 id=&quot;insutoru&quot;&gt;インストール&lt;/h2&gt;
&lt;p&gt;インストールにはRustの開発環境（Cargo）が必要です。
choはまだ試行錯誤中なので、構文や動作が変わる可能性があります。&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ cargo install --git https://github.com/k0f1sh/cho.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/k0f1sh/cho&quot;&gt;k0f1sh/cho (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;matome&quot;&gt;まとめ&lt;/h2&gt;
&lt;p&gt;最初はawkっぽい自作Lispからスタートしましたが、awkとはまた違った便利なツールになったのではないかと思っています。&lt;/p&gt;
&lt;p&gt;ちなみに、choという名前は「兆」から来ています。(awkが「億」っぽいので)&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ja">
        <title>Hello World</title>
        <published>2026-09-18T00:00:00+00:00</published>
        <updated>2026-09-18T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://k0f1sh.github.io/posts/hello/"/>
        <id>https://k0f1sh.github.io/posts/hello/</id>
        
        <content type="html" xml:base="https://k0f1sh.github.io/posts/hello/">&lt;p&gt;日々の知見や気になったことを、少しずつ書き留めていく予定です。&lt;/p&gt;
</content>
        
    </entry>
</feed>
