bash/zshで、入力中の内容を利用したカスタムキーバインドを作りたいということがあったので、備忘で残しておく。 (当たり前の話として、bash/zshどちらもキーバインドでfunctionを呼び出すことになるので、適当なfunctionを作ることになる)

以下、実際に動かしてるときの状態。 最初はzsh、次にbashに切り替えている。

ここでは、サンプルとしてカーソル位置から右側の文字列をダブルクオーテーションでくくるfunctionを作成する。

1. bashの場合

Bashでは、bind -xでfunctionを呼び出す際に以下の変数が利用できる。

  • READLINE_LINE ... 入力中のテキスト
  • READLINE_POINT ... 入力中のカーソル位置

つまり、function内で上記変数から入力中のテキストと位置を取得して、加工後にREADLINE_LINEに代入してやればいいということになる。 以下、bash版のサンプル。入力中の内容からカーソル位置より右を取得して、それをダブルクオーテーションで囲むように加工して置き換えている。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    local BUF=${READLINE_LINE:${READLINE_POINT}}

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}" # 最初の1文字目を削除
        local BUF="${BUF:0:-1}" # 最後の1文字目を削除
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # 入力中の内容を置き換える
    READLINE_LINE="${READLINE_LINE::${READLINE_POINT}}${BUF}"
}

# Alt + D でカーソル位置から行末までをダブルクオーテーションで囲む
bind -x '"\ed": __toggle_command_doublequote_format'

2. zshの場合

zshの場合は、bindでfunctionを呼び出す際に以下の様な変数が使えるようだ。

  • BUFFER ... 入力中のテキスト
  • CURSOR ... 入力中のカーソル位置
  • LBUFFER ... 入力中のテキスト(カーソルより左側)
  • RBUFFER ... 入力中のテキスト(カーソルより右側)

上記を踏まえzsh版は作ればいい。以下、zsh版のサンプル。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    # shellに応じて処理を変える
    local BUF=${RBUFFER}

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}"
        local BUF="${BUF:0:-1}"
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # 入力中の内容を置き換える
    RBUFFER=${BUF}
}

# Alt + D で、現在入力中の内容をダブルクオーテーションで囲む
zle -N __toggle_command_doublequote_format
bindkey '^[d' __toggle_command_doublequote_format

ちなみに、functionがどっちでも動くように記述する場合、以下のようにする。 キーバインドでのfunctionの呼び出しは各シェルによって異なるので、自分の使う方のを書けばいいだろう。

function __toggle_command_doublequote_format() {
    # 入力中の内容を変数に代入
    # shellに応じて処理を変える
    case $(basename ${SHELL}) in
        zsh*)
            local BUF=${RBUFFER}
            ;;
        bash*)
            local BUF=${READLINE_LINE:${READLINE_POINT}}
            ;;
    esac

    # すでにダブルクオーテーションで囲まれている場合は、前後のダブルクオーテーションを削除してエスケープ済のダブルクオーテーションを解除
    if [[ ${BUF:0:1} == '"' ]] && [[ ${BUF: -1} == '"' ]];then
        local BUF="${BUF:1}" # 最初の1文字目を削除
        local BUF="${BUF:0:-1}" # 最後の1文字目を削除
        local BUF=${BUF//\\\"/\"} # エスケープ済の"が含まれている場合、解除するよう置換する
    else
        local BUF=${BUF//\"/\\\"} # "が含まれている場合、エスケープするよう置換する
        local BUF='"'"${BUF}"'"'
    fi

    # shellに応じて処理を変える
    case $(basename ${SHELL}) in
        zsh*)
            RBUFFER=${BUF}
            ;;
        bash*)
            READLINE_LINE="${READLINE_LINE::${READLINE_POINT}}${BUF}"
            ;;
    esac
}

ダブルクオーテーションで囲ったりするのは結構頻度高いわりに書くのが楽だったのでサンプルにしたけど、入力中の値を加工できるなら色々とできそうだ。 それこそ、置換処理とかあると面白いのでは無いだろうか?