pecoは標準入力で受け取ったテキストデータをインクリメンタルに絞り込んで、選択した行を標準出力に出力するコマンド。
リポジトリのdemoに動きがわかるGIFがある。
似たようなものでfzfも良さそう → おい、peco もいいけど fzf 使えよ - Qiita
インストール
使うだけならgo get
する必要はなく、Releases · peco/pecoにあるバイナリをダウンロードすればよい。
macだとbrewでインストールすることも可。
拙作のghinstを使うと
$ ghinst peco/peco
で~/bin
にインストールできる
使い方
ファイルがいっぱいあるディレクトリで
$ ls | peco
で目的にファイルを見つけたり、見つけたものをvimで開くには
$ vim `ls | peco`
などとやる。よく使うものはシェルの設定ファイルに書いておくと良い。
Macでの下準備
bash, zshを最新版にしておく。
bashを使うなら
$ brew install bash
$ echo '/usr/local/bin/bash' | sudo tee -a /etc/shells
$ chsh -s /usr/local/bin/bash
zshを使うなら
$ brew install zsh
$ echo '/usr/local/bin/zsh' | sudo tee -a /etc/shells
$ chsh -s /usr/local/bin/zsh
macの標準コマンドはlinuxと挙動が違うので、brewを使ってlinuxに合わせる。
$ brew install coreutils gnu-sed
PATH等を設定しておく
# coreutils
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
export MANPATH="/usr/local/opt/coreutils/libexec/gnuman:$MANPATH"
# gnu-sed
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
export MANPATH="/usr/local/opt/gnu-sed/libexec/gnuman:$MANPATH"
コマンド履歴でpecoる
C-r
でコマンド履歴を検索できるけど、pecoを使ったほうが一覧性がよくて、絞り込みも柔軟なので使いやすい。
例えばzshの場合は
function peco-select-history() {
# historyを番号なし、逆順、最初から表示。
# 順番を保持して重複を削除。
# カーソルの左側の文字列をクエリにしてpecoを起動
# \nを改行に変換
BUFFER="$(history -nr 1 | awk '!a[$0]++' | peco --query "$LBUFFER" | sed 's/\\n/\n/')"
CURSOR=$#BUFFER # カーソルを文末に移動
zle -R -c # refresh
}
zle -N peco-select-history
bindkey '^R' peco-select-history
を.zshrcに書いておくとctrl+rでpecoを使ったコマンド履歴の絞り込みができる。
改行を含むコマンド実行した場合は、historyで改行部分が文字列の\nで出力されるため、sedで変換している。
bashの場合は
function peco-select-history() {
local tac
which gtac &> /dev/null && tac="gtac" || \
which tac &> /dev/null && tac="tac" || \
tac="tail -r"
READLINE_LINE=$(HISTTIMEFORMAT= history | $tac | sed -e 's/^\s*[0-9]\+\s\+//' | awk '!a[$0]++' | peco --query "$READLINE_LINE")
READLINE_POINT=${#READLINE_LINE}
}
bind -x '"\C-r": peco-select-history'
とする。以降bashの例は省く。
sshでpecoる
ホスト名を絞り込んでsshする設定。最近入ったサーバを上に持ってきたいので履歴でsshから始まるのを抽出後、known_hostsからも抽出。
function _get_hosts() {
# historyを番号なし、逆順、ssh*にマッチするものを1番目から表示
# 最後の項をhost名と仮定してhost部分を取り出す
local hosts
ssh_hist="$(history -nrm 'ssh*' 1 | \grep 'ssh ')"
hosts="$(echo $ssh_hist | perl -pe 's/ssh(\s+-([1246AaCfGgKkMNnqsTtVvXxYy]|[^1246AaCfGgKkMNnqsTtVvXxYy]\s+\S+))*\s+(\S+@)?//' | cut -d' ' -f1)"
# ----------------------------------------------------------------------- -------
# hostnameよりも前にあるオプション user@ を削除
# know_hostsからもホスト名を取り出す
# portを指定したり、ip指定でsshしていると
# [hoge.com]:2222,[\d{3}.\d{3].\d{3}.\d{3}]:2222
# といったものもあるのでそれにも対応している
hosts="$hosts\n$(cut -d' ' -f1 ~/.ssh/known_hosts | tr -d '[]' | tr ',' '\n' | cut -d: -f1)"
hosts=$(echo $hosts | awk '!a[$0]++')
echo $hosts
}
function peco-ssh() {
hosts=`_get_hosts`
local selected_host=$(echo $hosts | peco --prompt="ssh >" --query "$LBUFFER")
if [ -n "$selected_host" ]; then
BUFFER="ssh ${selected_host}"
zle accept-line
fi
}
zle -N peco-ssh
bindkey '^[s' peco-ssh
今回の例だとzle accept-line
を使って、選択後はすぐにsshをする。
以下のようにオプションを設定したsshの履歴でもホスト名を抽出できる
$ cat <<EOT | perl -pe 's/ssh(\s+-([1246AaCfGgKkMNnqsTtVvXxYy]|[^1246AaCfGgKkMNnqsTtVvXxYy]\s+\S+))*\s+(\S+@)?//' | cut -d' ' -f1
ssh example.com
ssh -A example.com
ssh -p 2222 example.com
ssh -A -p 2222 example.com
ssh -A example.com -p 2222
ssh example.com ls
ssh hoge@example.com
ssh -A -p 2222 hoge@example.com -i ~/.ssh/id_rsa ls
EOT
example.com
example.com
example.com
example.com
example.com
example.com
example.com
example.com
GNU sedとMacのBSDのsed 1 で正規表現の違いがあったため、perlを使った。GNU sedがいい人はhosts=...
の部分を
hosts="$(echo $ssh_hist | sed 's/ssh\(\s\+-\([1246AaCfGgKkMNnqsTtVvXxYy]\|[^1246AaCfGgKkMNnqsTtVvXxYy]\s\+\S\+\)\)*\s\+\(\S\+@\)\?//' | cut -d' ' -f1)"
に変更して下さい。
ghqでpecoる
ghqはリポジトリをhost/user/repo形式で管理するためのツール。Rebuild: 42の後、ghqを使ったローカルリポジトリの統一的・効率的な管理について - Kentaro Kuribayashi's blogをきっかけに流行ったと思う。
ghqを使うと、リポジトリのディレクトリがかぶらないし、ディレクトリを自分で切らなくていいのが利点だけど、その分ディレクトリが深くなって、移動が面倒になるけど、pecoをつかって解決する。
$ ghinst motemen/ghq
などでインストールする。
デフォルトだと~/.ghq
以下にディレクトリが作られるが、ghq以外で管理するようになる可能性もあるので~/src
以下で管理するように.gitconfigに以下の設定をしている。
[ghq]
root = ~/src
こうしておいて
$ ghq get tmsanrinsha/dotfiles
などと打つと~/src/github.com/tmsanrinsha/dotfiles
にgit cloneされる。
-p
をつけてるか、ssh形式で指定するとsshプロトコルでcloneされる。
$ ghq get -p tmsanrinsha/dotfiles
$ ghq get git@github.com:tmsanrinsha/dotfiles.git
余談だけど、
[url "git@github.com:"]
pushInsteadOf = https://github.com/
を設定しておくと、https形式でcloneしても、pushはssh形式になる。
こうするとfetchはhttps形式なので、パスフレーズが必要なくなり、cronでgit pull
することができる。pushはsshでできる。
各サーバのdotfilesなどをcronで最新に保つときに便利。
で、ghq look
を使うとリポジトリに移動することができるけど、pecoを使って移動することにする。また今git clone
してきたリポジトリや、最近更新したリポジトリが上に来て欲しいので、ちょっと工夫する。
function peco-ghq-cd () {
# Gitリポジトリを.gitの更新時間でソートする
local ghq_roots="$(git config --path --get-all ghq.root)"
local selected_dir=$(ghq list --full-path | \
xargs -I{} ls -dl --time-style=+%s {}/.git | sed 's/.*\([0-9]\{10\}\)/\1/' | sort -nr | \
sed "s,.*\(${ghq_roots/$'\n'/\|}\)/,," | \
sed 's/\/.git//' | \
peco --prompt="cd-ghq >" --query "$LBUFFER")
if [ -n "$selected_dir" ]; then
BUFFER="cd $(ghq list --full-path | grep -E "/$selected_dir$")"
zle accept-line
fi
}
zle -N peco-ghq-cd
bindkey '^[g' peco-ghq-cd
コメントをつけると
ghq list --full-path | \ # repositoryのfull pathを取得
xargs -I{} ls -dl --time-style=+%s {}/.git | \ # .gitの更新時間を表示
sed 's/.*\([0-9]\{10\}\)/\1/' | \ # タイムスタンプ部分と.gitのフルパス部分を取り出す
sort -nr | \ # タイムスタンプでソート
sed "s,.*\(${ghq_roots/$'\n'/\|}\)/,," | \ # 複数ある改行切りのghq.rootの改行を\|(sedの正規表現の|)にして、ghq.root部分を削除
sed 's/\/.git//' | \ # .gitを削除
peco --prompt="cd-ghq >" --query "$LBUFFER")
これで.gitの更新時間順で
cd-ghq >
github.com/tmsanrinsha/dotfiles
github.com/motemen/ghq
こんな風に表示される。選択されたら
BUFFER="cd $(ghq list --full-path | grep -E "/$selected_dir$")"
でフルパスを取得して移動する。
vimでも移動できるようにunite-ghqを使う。
もともとsorahさんが作ったのを上記のようなソートをするためにforkしたもの。
あと、選択したらvimfilerが開いて欲しい場合は
call unite#custom_default_action('source/ghq/directory', 'vimfiler')
としておくとよい。
cdrでpecoる
cdrは移動したディレクトリを記録して、そこに移動するためのzshのコマンド。bashの人とかはenhancdとか使うといいかも。
以下の様な設定をすると使える。
if [[ -n $(echo ${^fpath}/chpwd_recent_dirs(N)) && -n $(echo ${^fpath}/cdr(N)) ]]; then
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
zstyle ':completion:*' recent-dirs-insert both
zstyle ':chpwd:*' recent-dirs-default true
zstyle ':chpwd:*' recent-dirs-max 1000
zstyle ':chpwd:*' recent-dirs-file "$ZDOTDIR/.cache/chpwd-recent-dirs"
fi
$ZDOTDIR
を設定してない場合は$HOME
にするなど。
pecoで使う設定は以下。
function peco-cdr () {
local selected_dir="$(cdr -l | sed 's/^[0-9]\+ \+//' | peco --prompt="cdr >" --query "$LBUFFER")"
if [ -n "$selected_dir" ]; then
BUFFER="cd ${selected_dir}"
zle accept-line
fi
}
zle -N peco-cdr
bindkey '^[r' peco-cdr
これをvimからも使うためにはunite-zsh-cdrを使う。
zshの設定でrecent-dirs-fileをを変更している場合は設定してやる
let g:recent_dirs_file = $ZDOTDIR.'/.cache/chpwd-recent-dirs'
let g:unite_zsh_cdr_chpwd_recent_dirs = g:recent_dirs_file
これでvimからcdrで記録されたディレクトリに移動することはできた。しかし、逆にvimで開いているファイルのディレクトリにシェルから行きたい場合もある。
それをやるには以下のような設定をする。
let g:recent_dirs_file = $ZDOTDIR.'/.cache/chpwd-recent-dirs'
augroup cdr
autocmd!
autocmd BufEnter * call s:update_cdr(expand('%:p:h'))
augroup END
function! s:update_cdr(dir)
" .gitなどのdirectoryは書き込まない
let l:ignore_pattern = '\%(^\|/\)\.\%(hg\|git\|bzr\|svn\)\%($\|/\)'.
\ '\|^\%(\\\\\|/mnt/\|/media/\|/temp/\|/tmp/\|\%(/private\)\=/var/folders/\)'
if !isdirectory(a:dir) || a:dir =~ l:ignore_pattern
return
end
if filereadable(g:recent_dirs_file)
let l:recent_dirs = readfile(g:recent_dirs_file)
call insert(l:recent_dirs, "$'".a:dir."'", 0)
let l:V = vital#of('vital')
let l:List = l:V.import('Data.List')
let l:recent_dirs = l:List.uniq(l:recent_dirs)
call writefile(l:recent_dirs, g:recent_dirs_file)
endif
endfunction
バッファを変更するごとにディレクトリをcdrのファイルに書き込んでいる。この時ユニークなディレクトリを得るためにvital.vimの関数を使っているので、vital.vimが必要。
l:ignore_pattern
のパターンはneomru.vimのg:neomru#directory_mru_ignore_pattern
から拝借した。
pecoり方を変える
$ vim `ls | peco`
とかなんとなく嫌だ。絞り込んだ後にコマンドを入力したい。
GHQ - r7km/sで書かれているp()
という関数を使うと心理的なハードルが下がる。
$ ghq list -p | p cd
などと入力する。
他に、実行結果をpecoに送って、絞り込んだ内容をバッファに出すキーバインドを設定しているといい気がする。
function peco-buffer() {
BUFFER=$(eval ${BUFFER} | peco)
CURSOR=0
}
zle -N peco-buffer
bindkey "^[p" peco-buffer
このように設定すると
$ ls<M-p> # <M-p>でコマンドを実行して、結果をpecoに送る
# 選択すると
$ <選択した行>
な感じになる。
以上です。m(*_ _)m pecoり。
-
Macでも
brew install gnu-sed
するとGNU sedを使うことができる。gsedという名前でインストールされるが、export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
すればsedでgsedが使えるようになる。 ↩