在bash中透過參數移動遊標的方法?

在bash中透過參數移動遊標的方法?

在 bash 中,您可以使用M-fM-b將遊標向前和向後移動一個單詞,但是有沒有辦法移動一個單詞爭論向前還是向後?如果不是開箱即用,也許可以透過一些配置?

換句話說,我希望遊標在下面標記的位置之間導航。

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

答案1

我知道你正在使用 bash,我不相信你所要求的內容在 bash 中是可能的。我將向您展示如何在 ZSH 中實現所需的功能。 (ZSH 有點像改進的 bash - 如果你切換,那麼你仍然應該保持熟練)。

ZSH 中有 ZSH 行編輯器(簡稱 zle)。這將所有移動鍵提供為可綁定命令,就像 bash 一樣。更進一步的是定義自訂命令的能力。自訂命令是任何已轉換為小部件的 shell 函數。

這些函數可以執行其他命令,它們還可以存取您的問題感興趣的幾個變數。我要討論的內容是:

  • $BUFFER - 這是您目前正在編輯的整行
  • $CURSOR - 這是目前行插入的位置

還有其他可用的,例如:

  • $LBUFFER - 這是遊標之前的所有內容
  • $RBUFFER - 這是遊標之後的所有內容

現在,ZSH 不僅能夠提供自訂鍵綁定,它還具有一組更全面的操作,您可以對變數執行這些操作。這個問題有趣的地方之一是:

  • z - 使用 shell 解析將擴展結果拆分為單字以查找單字,即考慮值中的任何引用。

您可以將擴充的 $BUFFER 直接指派給變量,如下所示:

line=${(z)BUFFER}

(line 現在是一個數組,但煩人的是這個數組從索引 1 開始,與 bash 不同!)

這不會執行任何通配字元的擴展,因此它將傳回目前行中實際參數的陣列。一旦你有了這個,你就會對緩衝區中每個單字的起始點的位置感興趣。不幸的是,任何兩個單字之間很可能有多個空格,以及重複的單字。此時我能想到的最好的事情就是在您考慮時從當前緩衝區中刪除正在考慮的每個單字。就像是:

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 中沒有它,但在我的 shell 上它們分別綁定到 CMf 和 CMb。如果沒有,請將它們綁定到您的 inputrc 中

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

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

答案3

我使用Ctrl+sCtrl+r透過命令列(以及命令歷史記錄)互動地向前和向後搜尋(和移動)。雖然這不完全是安德烈亞斯所要求的,但您也許可以使用這些互動式搜尋功能快速轉到您選擇的論點。

如果你bind -P在 bash 中運行,你可以看到 bash 的所有內容可綁定的動作

有關其他遊標移動選項的更多討論,請參閱以下連結:
https://stackoverflow.com/q/657130

相關內容