bash で引数によってカーソルを移動する方法はありますか?

bash で引数によってカーソルを移動する方法はありますか?

bashでは、M-fM-を使用しbてカーソルを1単語ずつ前後に移動できますが、1単語ずつ移動する方法はありますか?口論前方または後方ですか? すぐに使用できない場合は、何らかの設定によって可能になるでしょうか?

つまり、カーソルを使用して、以下のマークされた位置間を移動したいと思います。

cp "foo bar.txt" "/tmp/directory with space"
^  ^             ^
|  |             |

答え1

あなたが bash を使っていることは承知していますが、あなたが求めている機能が bash で実現できるかどうかは自信がありません。私がお見せするのは、要求された機能を ZSH で実装する方法です。(ZSH は改良された bash のようなもので、切り替えても使いこなせるはずです)。

ZSH には、ZSH ライン エディター (略して zle) があります。これは、bash と同様に、すべての移動キーをバインド可能なコマンドとして提供します。さらに、カスタム コマンドを定義する機能もあります。カスタム コマンドとは、ウィジェットに変換された任意のシェル関数のことです。

これらの関数は他のコマンドを実行でき、問題に関連するいくつかの変数にもアクセスできます。ここで説明するのは、次のものです。

  • $BUFFER - これは現在編集中の行全体です
  • $CURSOR - これは現在の行の挿入位置です

他にも以下のようなものがあります:

  • $LBUFFER - カーソルの前のすべて
  • $RBUFFER - カーソルの後のすべて

ZSH はカスタム キーバインドを提供できるだけでなく、変数に対して実行できるより包括的な操作セットも備えています。この問題にとって興味深い操作の 1 つは次のとおりです。

  • z - シェル解析を使用して単語を検索し、展開の結果を単語に分割します。つまり、値内の引用符を考慮します。

次のように、拡張された $BUFFER を変数に直接割り当てることができます。

line=${(z)BUFFER}

(行は配列になりましたが、厄介なことに、この配列は bash とは異なり、インデックス 1 から始まります)

これはグロブ文字の拡張を行わないため、現在の行の実際の引数の配列を返します。これを取得したら、バッファ内の各単語の開始位置が重要になります。残念ながら、2 つの単語の間に複数のスペースがあったり、単語が繰り返されたりする可能性があります。この時点で考えられる最善の策は、検討中の各単語を現在のバッファから削除することです。次のようになります。

buffer=$BUFFER
words=${(z)buffer}
for word in $words[@]
do
    # doing regular expression matching here,
    # so need to quote every special char in $word.
    escaped_word=${(q)word}
    # Fancy ZSH to the rescue! (q) will quote the special characters in a string.

    # Pattern matching like this creates $MBEGIN $MEND and $MATCH, when successful
    if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
    then
        echo "Something strange happened... no match for current word"
        return 1
    fi
    buffer=${buffer[$MEND,-1]}
done

もうすぐ終わりです! 必要なのは、どの単語がカーソルの前の最後の単語で、どの単語がカーソルの後の次の単語の始まりであるかを確認する方法です。

buffer=$BUFFER
words=${(z)buffer}
index=1
for word in $words[@]
do
    if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
    then
        echo "Something strange happened... no match for current word"
        return 1
    fi

    old_length=${#buffer}
    buffer=${buffer[$MEND,-1]}
    new_length=${#buffer}
    old_index=$index
    index=$(($index + $old_length - $new_length))

    if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
    then
        # $old_index is the start of the last argument.
        # you could move back to it.
    elif [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
    then
        # $index is the start of the next argument.
        # you could move forward to it.
    fi
    # Obviously both of the above conditions could be true, you would
    # have to have a way to tell which one you wanted to test - but since
    # you will have two widgets (one forward, one back) you can tell quite easily. 
done

これまで、カーソルを移動するための適切なインデックスを導出する方法を示しました。しかし、カーソルを移動する方法や、これらの機能をキーにバインドする方法については説明していませんでした。

$CURSOR 変数は更新可能で、更新すると現在の挿入ポイントを移動できます。とても簡単です!

関数をキーにバインドするには、まずウィジェットにバインドするという中間ステップが必要です。

zle -N WIDGET_NAME FUNCTION_NAME

次に、ウィジェットをキーにバインドします。おそらく特定のキー識別子を調べる必要がありますが、私は通常、Ctrl-LETTER にバインドします。これは簡単です。

bindkey '^LETTER' WIDGET_NAME

これらすべてを組み合わせて問題を解決しましょう:

function move_word {
    local direction=$1

    buffer=$BUFFER
    words=${(z)buffer}
    index=1
    old_index=0
    for word in $words[@]
    do
        if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
        then
            echo "Something strange happened... no match for current word $word in $buffer"
            return 1
        fi

        old_length=${#buffer}
        buffer=${buffer[$MEND,-1]}
        new_length=${#buffer}
        index=$(($index + $old_length - $new_length))

        case "$direction" in
            forward)
                if [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
                then
                    CURSOR=$index
                    return
                fi
                ;;
            backward)
                if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
                then
                    CURSOR=$old_index
                    return
                fi
                ;;
        esac
        old_index=$index
    done
    case "$direction" in
        forward)
            CURSOR=${#BUFFER}
            ;;
        backward)
            CURSOR=0
            ;;
    esac
}

function move_forward_word {
    move_word "forward"
}

function move_backward_word {
    move_word "backward"
}

zle -N my_move_backwards move_backward_word
zle -N my_move_forwards move_forward_word
bindkey '^w' my_move_backwards
bindkey '^e' my_move_forwards

私のテストでは、これでうまくいっているようです。バインドするキーを変更したほうがよいかもしれません。参考までに、次の行でテストしました。

one 'two three' "four five"    "'six seven' eight nine" * **/ **/**/*
^  ^           ^           ^                           ^ ^   ^       ^

キャレット間を移動しました。折り返しは行われません。

答え2

はい、man bashでは次のように使用できます

shell-forward-word
              Move forward to the end of the next word.  Words  are  delimited  
              by non-quoted shell metacharacters.

shell-backward-word  
              Move  back  to the start of the current or previous word.  Words  
              are delimited by non-quoted shell metacharacters.  

引用符で囲まれていないメタ文字のシーケンスは引数です。

デフォルトではどのキーにもバインドされているとは書かれておらず、私のinputrcにも書いていませんが、私のシェルではそれぞれCMfとCMbにバインドされています。そうでない場合は、inputrcで次のようにバインドしてください。

"\C-M-f": shell-forward-word

"\C-M-b": shell-backward-word  

答え3

私はCtrl+sCtrl+を使用してr、コマンド ライン (およびコマンド履歴) を前後にインタラクティブに検索 (および移動) します。Andreas が求めているものとは正確には一致しませんが、これらのインタラクティブな検索機能を使用して、選択した引数にすばやく移動できる可能性があります。

bashで実行するとbind -P、bashのすべてを見ることができますバインド可能なアクション

その他のカーソル移動オプションの詳細については、以下のリンクを参照してください。
https://stackoverflow.com/q/657130

関連情報