在 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 现在是一个数组,但令人讨厌的是,与 bash 不同,这个数组从索引 1 开始!)

这不会执行任何通配符的扩展,因此它将返回当前行中实际参数的数组。一旦有了这个,您就会对缓冲区中每个单词的起始点的位置感兴趣。不幸的是,任何两个单词之间都有多个空格,以及重复的单词。此时我能想到的最好的办法是,在考虑每个单词时,将其从当前缓冲区中删除。例如:

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在命令行(以及命令历史记录)中以交互方式向前和向后搜索(和移动)。虽然这不是 Andreas 所要求的,但您可以使用这些交互式搜索功能快速移动到您选择的参数。

如果你bind -P在 bash 中运行,你可以看到 bash 的可绑定动作

有关其他光标移动选项的更多讨论请参阅以下链接:
https://stackoverflow.com/q/657130

相关内容