在bash中通过参数移动光标的方法?

And*_*den 5 bash shell readline

在 bash 中,您可以使用 Mf 和 Mb 将光标向前和向后移动一个单词,但是有没有办法向前或向后移动一个参数?如果不是开箱即用,也许是通过某种配置?

换句话说,我想光标在下面标记的位置之间导航。

cp "foo bar.txt" "/tmp/directory with space"
^  ^             ^
|  |             |
Run Code Online (Sandbox Code Playgroud)

Mat*_*len 5

我知道你在使用 bash,我不相信你问的东西在 bash 中是可能的。我将向您展示的是如何在 ZSH 中实现所请求的功能。(ZSH 有点像改进的 bash - 如果您切换,那么您仍然应该保持熟练)。

在 ZSH 中有 ZSH 行编辑器(简称 zle)。这提供了所有移动键作为可绑定命令,很像 bash。更进一步的是定义自定义命令的能力。自定义命令是已转换为小部件的任何 shell 函数。

这些函数可以执行其他命令,并且它们还可以访问您的问题感兴趣的几个变量。我要谈的是:

  • $BUFFER - 这是您当前正在编辑的整行
  • $CURSOR - 这是当前行中插入的位置

还有其他可用的,例如:

  • $LBUFFER - 这是光标之前的所有内容
  • $RBUFFER - 这是光标后的所有内容

现在碰巧 ZSH 不仅能够提供自定义键绑定,它还具有一组更全面的操作,您可以对变量执行这些操作。这个问题的有趣之处之一是:

  • z - 使用 shell 解析将扩展结果拆分为单词以查找单词,即考虑值中的任何引用。

您可以将扩展的 $BUFFER 直接分配给变量,如下所示:

line=${(z)BUFFER}
Run Code Online (Sandbox Code Playgroud)

(行现在是一个数组,但令人讨厌的是,这个数组从索引 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
Run Code Online (Sandbox Code Playgroud)

我们快到了!需要一种方法来查看哪个词是光标前的最后一个词,哪个词是光标后下一个词的开头。

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
Run Code Online (Sandbox Code Playgroud)

到目前为止,我已经展示了如何为光标移动到适当的索引。但我没有向您展示如何移动光标,或如何将这些功能绑定到键。

$CURSOR 变量可以被更新,如果你这样做了,你就可以移动当前的插入点。挺容易!

将函数绑定到键首先涉及绑定到小部件的中间步骤:

zle -N WIDGET_NAME FUNCTION_NAME
Run Code Online (Sandbox Code Playgroud)

然后,您可以将小部件绑定到一个键。您可能需要查找特定的键标识符,但我通常只绑定到 Ctrl-LETTER,这很容易:

bindkey '^LETTER' WIDGET_NAME
Run Code Online (Sandbox Code Playgroud)

让我们将所有这些放在一起并解决您的问题:

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
Run Code Online (Sandbox Code Playgroud)

就我自己的测试而言,这似乎可以完成工作。您可能想要更改它绑定到的键。作为参考,我用以下行对其进行了测试:

one 'two three' "four five"    "'six seven' eight nine" * **/ **/**/*
^  ^           ^           ^                           ^ ^   ^       ^
Run Code Online (Sandbox Code Playgroud)

它在插入符号之间导航。它不包装。