golangの日記

Go言語を中心にプログラミングについてのブログ

ディレクトリ移動を便利にするコマンド zoxide の使い方

default-gray.png


ディレクトリ移動を便利にするコマンド zoxide の使い方 (環境は Ubuntu でシェルは bash です )

zsh シェルだとディレクトリの移動に cd を省略してパスだけで移動できたりする。fish シェルでは cd と入力すると移動頻度に応じて補完されたり fisher install jethrokuan/zzoxide とは別の z コマンドがあったりする。zsh/fish は補完が強力だったり他にも便利なとこがいっぱいあるんだろうけど bash でももっと便利にディレクトリ移動したい。





目次



概要


zoxide は移動したディレクトリのパスをデータベース(~/.local/share/zoxide)に保存して、移動頻度に応じてスコアを付ける。そのスコアが高いディレクトリは z コマンドで短いキーワードで移動できたり zi コマンドで上位表示される。



インストール


fzf コマンドが必要なのでインストールする

git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

OR

sudo apt install fzf



zoxide のインストール (installation に載ってますが一応)

curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash

OR

sudo apt install zoxide


~/.bashrc 追記。

eval "$(zoxide init bash)"

--cmd オプションを使えばコマンド名を z / zi から変更できるっぽい



使い方


cd コマンドと同様に z でディレクトリを移動するとデータベースに追加されてスコアが付けられる。

$ z ~/foo/bar/baz


z コマンドで移動すると次回から最後のディレクトリ名(検索対象が最後のディレクトリ名なので)で移動できるようになる

$ z baz
$ pwd
/home/user/foo/bar/baz


登録されてるディレクトリは短いキーワードで移動できる(データベースの登録件数が増えてくるとスコアによってどこに行けるか変わる)

$ z ~/Downloads
$ z ~/
$ pwd
/home/user

$ z d
$ pwd
/home/user/Downloads


短いキーワードでどこに移動できるか確認

$ for v in $(echo {a..z}); do printf "%s: " "$v"; zoxide query "$v"; done 
a: /home/user/Downloads
b: /home/user/Dropbox
c: /home/user/.local
d: /home/user/Downloads
e: /etc
f: /home/user/.config
.
.
.


zi コマンドは fzf を使ってベータベースに登録されてるディレクトリを検索して移動できる

$ zi



zoxideコマンド


zzi はどちらも関数なので以下本体である zoxide コマンドの使い方。zoxide のヘルプはシンプルで分かりやすぎるのでヘルプ見る方がいいかもしれない。


type z で関数の内容が見れる

$ type z
z is a function
z () 
{ 
    if [ "$#" -eq 0 ]; then
        _z_cd ~;
    else
        if [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
            if [ -n "$OLDPWD" ]; then
                _z_cd "$OLDPWD";
            else
                echo 'zoxide: $OLDPWD is not set';
                return 1;
            fi;
        else
            _zoxide_result="$(zoxide query -- "$@")" && _z_cd "$_zoxide_result";
        fi;
    fi
}


パスの追加とスコアのインクリメント(. で現在のディレクトリを追加)

$ zoxide add <path>


パスの削除(. で現在のディレクトリを削除)
存在しないディレクトリは自動で削除されるのでディレクトリを rm -rf しても zoxide remove の必要はない

$ zoxide remove <path>


パス一覧

$ zoxide query -l


スコア付きのパス一覧

$ zoxide query -ls


検索(最後のディレクトリ名で検索しているっぽい)

$ zoxide query conf
/home/user/.config


fzf を使った検索

$ zoxide query -i




個人的な設定


zzi は使わないので ~/.bashrc から eval "$(zoxide init bash)" を削除。


cd コマンドに z コマンドの機能を付けたいので aliascd コマンドを関数に置き換える。zoxide 以外の部分は この記事にある cd コマンドを拡張するやつです。以下の関数を .bash_functions とかに書いて ~/.bashrc に読み込む。source ~/.bashrc でリロードすると反映される。--cmdcd に置き換え可能だと書いてるけど試してない。

func_enhanced_cd() {
    local x2 the_new_dir adir index
    local -i cnt

    if [[ $1 == "--" ]]; then
        dirs -v
        return 0
    fi

    the_new_dir=$1
    [[ -z $1 ]] && the_new_dir=$HOME

    if [[ ${the_new_dir:0:1} == '-' ]]; then
        #
        # Extract dir N from dirs
        index=${the_new_dir:1}
        [[ -z $index ]] && index=1
        adir=$(dirs +$index)
        [[ -z $adir ]] && return 1
        the_new_dir=$adir
    fi

    #
    # '~' has to be substituted by ${HOME}
    [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"

    #
    # Now change to the new dir and add to the top of the stack
    pushd "${the_new_dir}" >/dev/null 2>&1
    if [[ $? -ne 0 ]]; then
        local q v
        q="$(IFS=' '; echo "$*")"
        v="$(zoxide query $q 2>/dev/null)"
        # なんかダサいこと書いてるけどエラー表示させるために
        # zoxide query の検索がヒットしなければ
        # $the_new_dir を入れて再び pushd でエラーにする
        [ -z "$v" ] && v="${the_new_dir}"
        pushd "$v" >/dev/null
        [[ $? -ne 0 ]] && return 1
    fi
    the_new_dir="$(pwd)"

    # ホームディレクトリとルートディレクトリは除外して
    # zoxide add でディレクトリの登録とスコアをインクリメントする
    local -a EXCLUDES=("$HOME" '/')
    local ok=true
    local v
    for v in "${EXCLUDES[@]}"; do
        [ "$the_new_dir" == "$v" ] && ok=false
    done
    "${ok}" && zoxide add "${the_new_dir}" >/dev/null 2>&1

    #
    # Trim down everything beyond 11th entry
    popd -n +11 2>/dev/null 1>/dev/null

    #
    # Remove any other occurence of this dir, skipping the top of the stack
    for ((cnt = 1; cnt <= 10; cnt++)); do
        x2=$(dirs +${cnt} 2>/dev/null)
        [[ $? -ne 0 ]] && return 0
        [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
        if [[ "${x2}" == "${the_new_dir}" ]]; then
            popd -n +$cnt 2>/dev/null 1>/dev/null
            cnt=cnt-1
        fi
    done
    return 0
}
alias cd=func_enhanced_cd



使ってる関数に zi を組み込む。以下の関数を .bash_functions とかに書いて ~/.bashrc に読み込む。source ~/.bashrc でリロードすると反映される。

ff() {
    local -a MAXDEPTH=(-maxdepth 6)
    local -a HIDDEN=(-and ! -path '*\/\.*')
    local -a ARGS=()
    local FIND=false
    while (($# > 0)); do
        case "$1" in
        -*)
            [[ "$1" =~ ^-(.*[^afl0-9]+.*|)$ ]] && echo "Error: invalid option -- ‘$1" && return 1
            [[ "$1" =~ ^-[^0-9]*([0-9]|[1-9][0-9]+) ]] && MAXDEPTH=(-maxdepth "${BASH_REMATCH[1]}")
            [[ "$1" =~ ^-[^-]*a ]] && HIDDEN=()
            [[ "$1" =~ ^-[^-]*f ]] && FIND=true
            [[ "$1" =~ ^-[^-]*l ]] && MAXDEPTH=() 
            shift
            continue
            ;;
        esac
        ARGS+=("$1")
        shift
    done
    local v
    if "${FIND}"; then
        v=$(find "${ARGS[0]:-.}" "${MAXDEPTH[@]}" -type d "${HIDDEN[@]}" -and ! -regex '^\.$' -and ! -regex '.*/node_modules/.*' -print 2>/dev/null | sed -e "s/^${HOME//\//\\/}/\~/" | fzf --select-1 --no-multi)
    else
        # zoxide のパス一覧を fzf で検索する
        v=$(zoxide query -l | sed -e "s/^${HOME//\//\\/}/\~/" | fzf --query="$(IFS=' '; echo "${ARGS[*]}")" --select-1 --no-multi)
    fi
    [ -n "$v" ] && v="${v/~\//$HOME/}"
    [ -d "$v" ] && cd "$v" || return 0
}

この関数は ff すると zi コマンドのように fzf で検索して cd できる。ff -f すると find コマンドで現在のディレクトリ以下を探索した結果を fzf で絞り込んで cd できる。

一応 ff -f のその他のオプションを説明すると -fa. から始まる非表示なディレクトリを検索対象に含める。 -f10 とか -数字 で階層制限を指定できる(デフォルトは local -a MAXDEPTH=(-maxdepth 6) の部分で 6)。-fl で階層制限なし。ホームディレクトリからすべてのディレクトリを検索する場合は ff -fal ~