
No bash, você pode usar M- fe M- bpara mover o cursor uma palavra para frente e para trás, mas existe uma maneira de mover umaargumentopara frente ou para trás? Se não estiver pronto para uso, talvez por alguma configuração?
Em outras palavras, gostaria de mover o cursor para navegar entre as posições marcadas abaixo.
cp "foo bar.txt" "/tmp/directory with space"
^ ^ ^
| | |
Responder1
Eu sei que você está usando o bash e não tenho certeza de que o que você pergunta seja possível no bash. O que vou mostrar é como implementar o recurso solicitado no ZSH. (ZSH é um pouco como um bash aprimorado - se você mudar, ainda deverá permanecer proficiente).
No ZSH existe o ZSH Line Editor (abreviadamente zle). Isso fornece todas as teclas de movimento como comandos vinculáveis, assim como o bash. O que vai além é a capacidade de definir comandos personalizados. Um comando personalizado é qualquer função shell que foi transformada em um widget.
Essas funções podem executar outros comandos, e também têm acesso a diversas variáveis que são de interesse para o seu problema. Os que irei falar são:
- $BUFFER - esta é a linha inteira que você está editando atualmente
- $CURSOR - esta é a posição da inserção na linha atual
Existem também outros disponíveis, como:
- $LBUFFER - isso é tudo antes do cursor
- $RBUFFER - isso é tudo depois do cursor
Agora acontece que o ZSH não é apenas capaz de fornecer atalhos de teclado personalizados, mas também possui um conjunto muito mais abrangente de operações que você pode executar em variáveis. Um dos que é interessante para este problema é:
- z - Divida o resultado da expansão em palavras usando análise de shell para encontrar as palavras, ou seja, levando em consideração qualquer citação no valor.
Você pode atribuir o $BUFFER expandido diretamente a uma variável, assim:
line=${(z)BUFFER}
(line agora é um array, mas irritantemente esse array começa no índice 1, ao contrário do bash!)
Isso não realizará nenhuma expansão de caracteres globais, portanto retornará uma matriz dos argumentos reais em sua linha atual. Depois de fazer isso, você estará interessado na posição do ponto inicial de cada palavra no buffer. Infelizmente, você pode ter vários espaços entre duas palavras, bem como palavras repetidas. A melhor coisa que consigo pensar neste momento é remover cada palavra considerada do buffer atual conforme você a considera. Algo como:
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
Estamos quase lá agora! O que é necessário é uma maneira de ver qual palavra é a última palavra antes do cursor e qual palavra é o início da próxima palavra após o cursor.
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
Até agora, mostrei como você pode derivar o índice apropriado para o qual o cursor se moverá. Mas não mostrei como mover o cursor ou como vincular essas funções às teclas.
A variável $CURSOR pode ser atualizada e, se você fizer isso, poderá mover o ponto de inserção atual. Bem fácil!
Vincular funções a chaves envolve uma etapa intermediária de vincular primeiro a um widget:
zle -N WIDGET_NAME FUNCTION_NAME
Você pode então vincular o widget a uma chave. Você provavelmente terá que procurar os identificadores de chave específicos, mas normalmente apenas ligo a Ctrl-LETTER, o que é bastante fácil:
bindkey '^LETTER' WIDGET_NAME
Vamos juntar tudo isso e resolver seu problema:
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
No que diz respeito aos meus próprios testes, isso parece funcionar. Você provavelmente desejará alterar as chaves às quais ele está vinculado. Para referência, testei com a linha:
one 'two three' "four five" "'six seven' eight nine" * **/ **/**/*
^ ^ ^ ^ ^ ^ ^ ^
e navegou entre os sinais de intercalação. Não embrulha.
Responder2
Sim, no man bash diz que você pode usar
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.
Uma sequência de metacaracteres não citados é um argumento.
Ele não diz que eles estão vinculados a qualquer chave por padrão e eu não a tenho em meu inputrc, mas no meu shell eles estão vinculados a CMf e CMb respectivamente. Caso contrário, vincule-os em seu inputrc com
"\C-M-f": shell-forward-word
"\C-M-b": shell-backward-word
Responder3
Eu uso Ctrl+ se Ctrl+ rpara pesquisar (e mover) interativamente para frente e para trás na linha de comando (e no histórico de comandos). Embora não seja exatamente o que Andreas está pedindo, você pode usar esses recursos de pesquisa interativos para passar rapidamente para o argumento de sua escolha.
Se você executar bind -P
no bash, poderá ver todos os bashações vinculáveis.
Veja o link abaixo para mais discussões sobre outras opções de movimento do cursor:
https://stackoverflow.com/q/657130