
В bash вы можете использовать M- fи M- bдля перемещения курсора на одно слово вперед и назад, но есть ли способ переместить курсор на одно словоаргументвперед или назад? Если не из коробки, то может быть по какой-то конфигурации?
Другими словами, я хотел бы, чтобы курсор перемещался между отмеченными ниже позициями.
cp "foo bar.txt" "/tmp/directory with space"
^ ^ ^
| | |
решение1
Я знаю, что вы используете bash, и я не уверен, что то, что вы просите, возможно в bash. Я покажу вам, как реализовать запрошенную функцию в ZSH. (ZSH немного похож на улучшенный bash — если вы переключитесь, вы все равно должны оставаться опытным).
В ZSH есть редактор строк ZSH (zle для краткости). Он предоставляет все клавиши перемещения в качестве связываемых команд, как в bash. Дальше идет возможность определять пользовательские команды. Пользовательская команда — это любая функция оболочки, которая была преобразована в виджет.
Эти функции могут выполнять другие команды, а также они получают доступ к нескольким переменным, которые представляют интерес для вашей проблемы. Я расскажу о следующих:
- $BUFFER — это вся строка, которую вы сейчас редактируете.
- $CURSOR - это позиция вставки в текущей строке
Также доступны и другие варианты, например:
- $LBUFFER - это все, что находится перед курсором
- $RBUFFER - это все, что после курсора
Теперь оказывается, что ZSH не только способен предоставлять пользовательские сочетания клавиш, но и имеет гораздо более полный набор операций, которые вы можете выполнять над переменными. Одна из операций, которая интересна для этой проблемы, это:
- z - Разбить результат расширения на слова, используя синтаксический анализ оболочки для поиска слов, т.е. принимая во внимание любые кавычки в значении.
Вы можете присвоить расширенный $BUFFER непосредственно переменной, например так:
line=${(z)BUFFER}
(строка теперь является массивом, но, что раздражает, этот массив начинается с индекса 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-БУКВА, что достаточно просто:
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+ sи Ctrl+ rдля интерактивного поиска (и перемещения) вперед и назад по командной строке (и по истории команд). Хотя это и не совсем то, о чем просит Андреас, вы можете использовать эти интерактивные функции поиска, чтобы быстро перейти к аргументу по вашему выбору.
Если вы запустите bind -P
bash, вы увидите все bashсвязываемые действия.
Более подробное обсуждение других вариантов перемещения курсора см. по ссылке ниже:
https://stackoverflow.com/q/657130