Fishのabbrを使ってコマンドライン上のトークンを複製する

by
カテゴリ:
タグ:

カーソル位置のn個前のトークンとか、先頭からn個目のトークンとかを展開できるとcpコマンドとかで便利!と思ってabbrを実装しました。 ,[+-]?[0-9]+というパターンに反応して、符号の有無と数値を基準に、うまいことトークンを展開できます。

# 展開前
cp /path/to/file ,-1

# 展開後
cp /path/to/file /path/to/file

Fish向けの実装ですが、Zshで似た機能にはcopy-prev-wordcopy-prev-shell-wordがあります。 https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#index-copy_002dprev_002dshell_002dword

abbr概要

Fishのabbrはaliasの強化版といった感じで、短縮入力をスペースやエンターと共に本来のコマンドに展開してくれる機能です。

たとえば

abbr -a g git

とすると、gと入力してスペースを押すとgitに展開されます。

さらには--commandオプションを使ってgit agit addにするような、サブコマンドの展開も可能です。

abbr --command=git -a a add

とここまでは過去の記事でも紹介した通り。

Fish 4のabbrはサブコマンドも展開できるぞ
https://blog.atusy.net/2025/03/29/fish-4-abbr/

コマンドライン上のトークンを複製するabbr

方向性

今回の記事では、この機能を利用して、コマンドライン上の任意のトークンを展開するabbrを実装してみましょう。以下の,はプレフィックスで使いやすいものならなんでもいいと思います。

  1. ,-[0-9]+
    • カーソルから左側にn番目のトークンを展開
  2. ,[+][0-9]+
    • カーソルから右側にn番目のトークンを展開
  3. ,[0-9]+
    • 左端から数えてn番目のトークンを展開

1の,-[0-9]+は特に使いどころがわかりやすいかなと思います。たとえば、直前のトークンを展開すると、cpコマンドでコピー対象のパスを複製できるので、コピー先のパスを作りやすくなります。

# 展開前
cp /path/to/file ,-1

# 展開後
cp /path/to/file /path/to/file

3の,[0-9]+はたとえば、ffmpegなどで引数を大量に指定している場合に、-iで指定した入力ファイルのパスを再利用して出力ファイルのパスを決めたい、なんてケースで効果を発揮するでしょう。 ,3とすることで、3番目のトークンであるファイルパスを展開できます。

# 展開前
ffmpeg -i path/to/file.mpg -vf "..." -loop 0 ,3

# 展開後
ffmpeg -i path/to/file.mpg -vf "..." -loop 0 path/to/file.mpg

2の,[+][0-9]+は一番使いどころが少ないかもしれませんが、一通りの機能を揃えておくために実装しました。

実装

中身は3つの要素でできてます。

  • abbr定義
    • これがないと始まらないやつ
    • 複雑な展開をするため、以下のオプションを指定する
      • --regex ',[-+]?[0-9]+'で展開対象を動的に判定する
      • --function __abbr-tokenで関数の出力結果を使って展開できるようにする
      • --position anywhereでコマンドライン上の任意位置で展開可能にする
  • __abbr-token関数
    • abbrコマンドに指定する展開用の関数
    • 実態は__abbr-token-baes関数のラッパー
    • 展開結果が1トークンとして成立するようにstring escapeしている
  • __abbr-token-base関数
    • abbrを展開する関数の本体部分
    • 展開元(,[-+]?[0-9]+)の符号の種類や数値に基いて、展開結果を出力する
abbr -a token --regex ',[-+]?[0-9]+' --function __abbr-token --position anywhere

function __abbr-token
  __abbr-token-base | string escape
end

function __abbr-token-base
   commandline --cut-at-cursor | read --tokenize --list --local tokens_left

  set -l idx ( echo $tokens_left[-1] | string sub --start 2 )

  # idxが負ならカーソル位置からidx個手前のトークンを展開
  if echo "_$idx" | string match -q -r "^_[-]"
    set -l idx2 ( math "$idx - 1" )
    echo $tokens_left[$idx2]
    return 0
  end

  commandline | read --tokenize --list --local tokens_all 

  # idxが+ではじまる正数ならカーソル位置からidx個手後のトークンを展開
  if echo "_$idx" | string match -q -r "^_[+]"
    set -l base ( count $tokens_left )
    set -l idx2 ( math "$base + $idx" )
    echo $tokens_all[$idx2]
    return 0
  end

  # idxに正負の符号がない場合は全トークンからsliceする
  echo $tokens_all[$idx]
end

本当は__abbr-token-baseidxのバリデーションを加えるべきでしょうが、実用上はabbr--regexオプションで指定したパターンしか入らないので、省略してます。

ところでプレフィクスには,を使っていますが、グローバル変数に切り出しておけば、お好みにカスタマイズもできそうです。

set -g ABBR_TOKEN_PREFIX ","

abbr -a token --regex $ABBR_TOKEN_PREFIX"[-+]?[0-9]+" --function __abbr-token --position anywhere

function __abbr-token-base
  # 前略

  set -l len_prefix (string length $ABBR_TOKEN_PREFIX)
  set -l idx ( echo $tokens_left[-1] | string sub --start $len_prefix )

  # 後略
end

ENJOY!