zeno.zshでよく使うコマンドをスニペット管理——設定例付き

以前、コマンド入力が爆速になる Zsh・Fishのプラグインのzeno.zshを解説する記事を書きました。書いた時はまだスニペット機能をあまり使えてなかったのですが今では常用してます。

というわけでzeno.zshの略語展開・スニペットの機能の解説と実際に筆者が使っている設定例を書きました。

zeno.zshとは

zeno.zshはZsh・Fishのプラグインで次の機能があります。

  • 略語展開・スニペット(Abbrev snippet)
  • ファジー補完
  • リッチな履歴UI
  • リポジトリ管理のghqと連携

詳しい説明・インストール方法は別の記事にまとめました。

前述のとおりこの記事では 略語展開・スニペット機能 にフォーカスして解説します。

略語展開・スニペット(Abbrev snippet)とは

略語展開は「ある文字を入力したら特定の文字列として展開する」機能です。

DC<Space>と入力したらdocker-composeに展開」みたいなことができます。

Terminal window
DC<Space>

これがこうなります。

Terminal window
docker-compose

SpaceじゃなくてEnterを押すと、展開されてからすぐに実行されます。

筆者は以前vimのiabのよう略語展開の記事を参考に略語展開を実装していたのですが、zenoを使うと 細かく条件が指定できて 好きです。

エイリアスとの使い分け

前述のとおり、zenoの略語展開はキーワードEnterですぐに実行できるため、zshのエイリアスと同じように扱えます。では、エイリアスはもう不要でしょうか?

エイリアスと比較したときのzenoの特徴を見ていきましょう。

検索できる

zeno-insert-snippetを実行するとスニペットの一覧が出ます。ここで検索してEnterを押せばコマンドラインに挿入されるため、 スニペットの名前を覚えなくても大丈夫 です。

エイリアスの場合、期間が空いてからだと「エイリアス名」を思い出せないことがあります。zenoであれば心配不要です。

「具体的にどんな引数で実行されるんだっけ?」というのもzenoならすぐに確認できます。

展開とすぐに実行を使い分け

zenoではSpaceを押したら 展開Enterなら 展開してすぐに実行 と使い分けられます。

たとえばdocker-composeのようなコマンドは、続けて引数を書くのでSpaceで展開します。

Terminal window
DC<Space>
# ↓展開
docker-compose

一方でClaude Codeのようなコマンドは「そのまま実行したければEnter」、「--resumeなどの引数を書きたくなったらSpace」と使い分けできます。

Terminal window
# どっちもよく使う
cc<Enter>
cc<Space>

履歴には展開された状態で残る

zenoはエイリアスとは違い文字列を 展開 するスニペット方式です。つまりzenoで登録しておけば、 コマンドの引数を一部変更してから実行できます

展開された状態履歴に残るため、他人に実行履歴を共有するときにも楽です。

逆にいえば、 変更する可能性がなくて コマンド履歴をスッキリさせたいなら エイリアス というように使い分けられます。

エイリアスを使ったほうがいいケース

たとえばsedawkのようなOSによって微妙に違うコマンドを統一したいケースではエイリアスのほうがよいでしょう。

.zshrc
case ${OSTYPE} in
darwin*)
alias xargs="gxargs"
alias sed="gsed"
alias awk="gawk"
;;
esac

その他、筆者がエイリアスを使っている例

一部書いておきます。

.zshrc
alias rm='gomi'
alias zd='cd ~/dotfiles'
alias zdev='cd ~/ghq/dev/'

yamlの設定ファイルの書き方

インストールやzshの設定方法は別の記事に書きました。ここではスニペットの設定のみ解説します。

略語展開・スニペットの設定の書き方

略語展開の設定はsnippets(複数形)に書きます。

config.yml
snippets:
- name: "docker compose"
keyword: "DC"
snippet: "docker-compose"

keywordに書いたものがsnippetに展開されます。nameは自分が分かりやすいやつにしておけばOKです。

カーソル位置を変える

{{foo_bar}}のようにプレースホルダーを書くと、展開後にその位置にカーソルが飛びます。

config.yml
- name: "vhs for blog"
keyword: "VHS"
snippet: "vhs {{tape_path}} && mv ./*.mp4 $VITE_EXTERNAL_FOLDER"

複数行に分割して書く

YAMLなので複数行に分けて書けます。長いコマンドが見やすくなりますね。展開時は1行になります。

config.yml
- name: "tmux popup neovim"
keyword: "TN"
snippet:
tmux popup -E -w 95% -h 95%
-d '#{pane_current_path}'
'nvim -c "{{command_here)}}"'

展開条件を指定する

〇〇 | grepのようなパイプ処理部分を展開したい場合、context.lbufferを指定するとよいでしょう。

config.yml
- name: "grep"
keyword: "G"
snippet: "| grep"
context:
lbuffer: '.+\s'

これは「現在のカーソルの左側が.+\sに当てはまるなら展開する」という 条件指定 です。

Terminal window
cat foo.md G<Space>
# ↓展開
cat foo.md | grep
Terminal window
# 展開されない例
cat foo.mdG<Space>
G<Space>

実装を見たところ、他にはrbufferbufferglobalがあるっぽいです。

筆者のカスタマイズ

インストールや設定方法は別の記事にまとめました。

この記事では筆者のカスタマイズを紹介します。

パイプライン系

まずは「他のコマンドと組み合わせて使う前提」のパイプライン系です。

config.yml
snippets:
- name: "cd there"
keyword: "CD"
snippet: "&& cd $_"
context:
lbuffer: '.+\s'
- name: "copy"
keyword: "CC"
snippet: "| pbcopy"
context:
lbuffer: '.+\s'
- name: "grep"
keyword: "G"
snippet: "| grep"
context:
lbuffer: '.+\s'
- name: "null"
keyword: "NULL"
snippet: ">/dev/null 2>&1"
context:
lbuffer: '.+\s'

CDmkdirの後によく使ってます。

Terminal window
mkdir foo CD<Space>
# ↓ 展開
mkdir foo && cd $_
# fooを作成してfooに移動

もはやコマンド名自体の入力が面倒なやつら

AIエージェントCLIはコマンド名の入力もダルいので登録してます。コマンド名に不満があるわけではなく、それだけ入力頻度が高いということです。

config.yml
snippets:
- name: "claude code"
keyword: "cc"
snippet: "claude"
- name: "codex"
keyword: "cx"
snippet: "codex"

zshのキーバインドに設定すると誤爆したり引数足したりできないのでスニペットにしてます。

引数付きで登録しているやつら

引数がほぼ固定されているやつは引数まで登録してます。

config.yml
snippets:
- name: "difit"
keyword: "difit"
snippet: "bun x difit ."
# difitで今のHEADとdevelopmentブランチの比較
- name: "difit to dev"
keyword: "difitd"
snippet: "bun x difit @ development"
- name: "nvim-noplugin"
keyword: "nvimno"
snippet: "nvim --noplugin -u NONE"
- name: "SJIS to UTF-8"
keyword: "UTF"
snippet: "nkf -w --overwrite"
- name: "git worktree new brach"
keyword: "gwn"
snippet: "git worktree add -b"
- name: "git worktree already exists brach"
keyword: "gwe"
snippet: "git worktree add ../"

ワークツリーはそろそろラッパーとなるCLIを作りたいところ。

コマンドというかスクリプト

「シェルスクリプト化するまでもないかな」とか「ちょっと引数変えて実行するかも」なやつらです。「コマンドラインの履歴から探して実行」だとPCを変えたときに引き継げないので、zenoにスニペットとして登録するのがオススメです。

config.yml
snippets:
# gitignoreしてるmise.local.tomlをワークツリーでシンボリックリンク貼るやつ
- name: "link mise.local.toml in worktree"
keyword: "wtmise"
snippet: 'ln -s "$(git rev-parse --path-format=relative --git-common-dir | xargs dirname)/mise.local.toml" mise.local.toml && mise trust'
# 指定したcsvの2列目に重複したデータがないか調べる
# 列を変えるなら`-f2`の部分を変更する
- name: "csv checker for duplicates col2"
keyword: "csv_checker_for_duplicates_col2"
snippet: "tail -n +2 {{csv_here}} | cut -d, -f2 | sort | uniq -d"
- name: "convert to avif"
keyword: "AVIF"
snippet: 'ffmpeg -i screenshot.png "$R2_BACKUP_PATH/works/{{image_name}}.avif"'
- name: "--testNamePattern="
keyword: "TNP"
snippet: "--testNamePattern='{{cursor}}'"
context:
# jest以外でも`mise run 〇〇`のように
# タスクランナーの可能性ありのためcontextは簡素に
lbuffer: '.+\s'

自作スクリプトの実行

普通に長いやつは素直にスクリプトファイルにしたほうがいいです。が、名前を覚えられないしパスも書くのが面倒なのでzenoのスニペットとして登録してます。これでzeno-insert-snippetを使って検索できます。

config.yml
snippets:
- name: "tmux-first-choose-session"
keyword: "tf"
snippet: "tmux-first-choose-session"
# ログをスプレッドシートに貼るときに
- name: "all csv to tsv"
keyword: "all_csv_to_tsv"
snippet: "for f (*.csv) bun run ~/dotfiles/bin/csv_to_tsv.ts --file $f"
- name: "docker log to TSV"
keyword: "docker-log-to-tsv"
snippet: "bun run ~/dotfiles/bin/docker-log-to-tsv.ts"
- name: "nginx access log to TSV"
keyword: "nginx-access-log-to-tsv"
snippet: "bun run ~/dotfiles/bin/nginx_to_tsv.ts"
- name: "mdjanai"
keyword: "mdjanai"
snippet: "~/ghq/github.com/eetann/mdjanai/dist/index.js"

ディレクトリ名の短縮入力

ディレクトリ名・環境変数だけのやつも登録してます。

筆者は~/Downloads/の入力が面倒だと思ってます。~/Documents/が存在するので、~/Dowまで入力してTab補完はちょっと面倒なんですよね。なのでDLで展開できるように設定してます。

config.yml
snippets:
- name: "input 'download'"
keyword: "DL"
snippet: "~/Downloads/"
context:
lbuffer: '.+\s'
- name: "media for blog"
keyword: "MEDIA"
snippet: "$VITE_EXTERNAL_FOLDER"
context:
lbuffer: '.+\s'

略語の思想

筆者は今までの癖でkeywordDCのように大文字にしてきましたが、覚えきれなくなったのでよく使うやつだけにしてます。 スニペット検索で何とかなります

もっと知りたい人向け

GitHubで検索したら出てきます。検索リンク置いておきます。

この記事では設定ファイルをYAMLで紹介しましたが、TypeScriptでも書けるようです。


以上、zeno.zshのスニペット機能の解説でした。zenoを使い始めた時のスニペットはベンチメンバーみたいな感じでしたが今では第一線です。