Unix & Linux Asked on October 31, 2021
suppose I have following entry in my /etc/hosts
192.168.1.10 server1.mydomain.com
and I have a directory SERVER-FILES
in current dir. I want to scp
the directory SERVER-FILES
somewhere. I type SE
and use autocompletion to complete the directory name:
$ scp -rp SE<TAB>
This completion should be totally unambiguous. But zsh autocomplletion tries to be too smart, and treats hostnames case-insensitive, and thus attempts to match SE
to hostnames:
$ scp -rp SE<TAB>
SERVER-FILES/
server1.mydomain.com
How can I disable this annoying feature, where zsh is trying to match hostnames case-insensitive, and therefore completes SE<TAB> to
server1.mydomain.com` ?
Based on suggestions from @zeppelin
, I have changed the following line in the ssh completion file Unix/_ssh
:
- compadd -M 'm:{a-zA-Z}={A-Za-z} r:|.=* r:|=*' "$@" $config_hosts
+ compadd "$@" $config_hosts
but that did not help. It has absolutely no effect.
And I don’t understand the answer from @Tomasz Pala
. My zsh completion is not case-insensitive.
Please sopmebody just tell me what I need to change in /usr/share/zsh/functions/Completion/Unix/_foo
to change this behaviour.
I have finally narrowed the problem down, and found out why the solution from @Tomasz Pala
did work for him, but not for me:
When I change the Unix/_hosts
file on a newly setup machine/user account, the solution works.
scp -r SE<TAB>
The above command ignores server1.mydomain.com
in /etc/hosts
, and only offers local directory SERVER-FILES
for completion.
But this does not work for me on my existing user account, because I have
server.mydomain.com
in my ~/.ssh/config
. When I remove the entry, then everything works as desired.
But how can I make this hack work even with my current ~/.ssh/config
?
Second answer tries to explain that you need to do two things:
1_ make sure your general matching rules are not case-insensitive (matcher-list
) - from the updated question it's not,
2_ change Unix/(Type/)_hosts (the actual location might vary, but not the Unix/_ssh - this one handles ~/.ssh/config
hosts, see below) last 2 lines to:
_wanted hosts expl host
compadd -M 'm:{a-z}={A-Z} r:|.=* r:|=*' -a "$@" - _hosts
All of this was already summarized in my answer, so simply try doing this without reading all the rationale before. Also, since your global config is not case-insensitive, the @zeppelin's answer should also work, although it doesn't use $fpath and removes also small->CAPS matching of the hosts.
I did test this with your settings from the update and it works as expected.
Update: remember that zsh
keeps it's functions loaded, so after modifying the _hosts
you need to reload it either by logging in fresh, or:
unfunction _hosts
autoload -Uz _hosts
Also remember that zsh
can have the scripts 'compiled' in zwc
form (zcompile [file]
) and if such file exists and is newer than the source it would be used instead.
Ad. update 2:
Handling the ~/.ssh/config
defined hosts is actually pretty much the same as for _hosts
- depending on your zsh version in either Unix/(Command/)_ssh or Unix/(Type/)_ssh_hosts change the
compadd -M 'm:{a-zA-Z}={A-Za-z} r:|.=* r:|=*' "$@" $config_hosts
line to
compadd -M 'm:{a-z}={A-Z} r:|.=* r:|=*' "$@" $config_hosts
Answered by Tomasz Pala on October 31, 2021
You do not need to change any completion functions to get what you want. Just add this to your ~/.zshrc
file:
# $PREFIX is the part of the current word that's to the left of the cursor.
# $SUFFIX is the part of the current word that's to the right of the cursor.
# Let's ignore all host completions that don't explicitly match what we've typed,
# but allow for additional characters at the cursor position and at the end of what
# we've typed. This makes the matching case sensitive.
# `(b)` escapes characters that are significant to globbing.
zstyle -e ':completion:*:hosts' ignored-patterns 'reply=(
"^(${(b)PREFIX}*${(b)SUFFIX}*)"
)'
# But if that would lead to no results, then offer the ignored completions anyway.
zstyle ':completion:*' completer _expand _complete _ignored
I've tested this with the config and test cases you posted and it works.
Documentation:
Answered by Marlon Richert on October 31, 2021
There are two aspects of this question.
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
mkdir DOC drone
ls D[tab] => DOC
ls d[tab] => drone DOC
ls Dr[tab] => [empty]
The problem with matcher-list is that this is called very early and cannot be replaced on per-command/argument basis.
And since matcher-list repeated values are being concatenated (quote from the documentation: "This option may be given more than once. In this case all match-specs given are concatenated with spaces between them to form the specification string to use"), you cannot override this later, e.g.
compdef '_hosts -M m:' foo
won't revoke the previous definition from _hosts file. So if you have {a-zA-Z}={A-Za-z}
in your global configuration, you won't be able to clear this, even with matcher
, e.g.:
zstyle ':completion:*:scp:*:*' matcher "m:{A-Z}={A-Z}"
won't help. Sorry, you need to start resolving this issue at global level. The trade-off solution could be to have two-pass global:
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' 'm:{A-Z}={a-z}'
ls d[tab] => DOC drone
ls D[tab] => DOC
ls Dr[tab] => drone
_hosts:
[....]
_wanted hosts expl host
compadd -M 'm:{a-z}={A-Z} r:|.=* r:|=*' -a "$@" - _hosts
Now, and with system-wide matcher-list 'm:{a-z}={A-Z}':
scp d[tab] => /directory/ DOC drone /remote host name/ docker /user/ daemon
scp D[tab] => DOC
The proposed solution with altering original completion files is not the right way to do, just like @zeppelin noted ...but creating own host-fetching function is also flawned. Proper solution is unfortunately up to zsh developers, who might either add a matcher-clearing option, define some new style or simply fix the completion function to not ignore case on it's own, just follow the global settings.
Yet even with this issue there is nice and clean solution - instead of modifying (probably some distro package provided) /usr/share/zsh/... one can put the fixed copy in /etc/zsh/functions and simply tell zsh to use this location: fpath=(/etc/zsh/functions $fpath)
. This way you avoid overwriting the file after system update.
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' 'm:{A-Z}={a-z}'
fpath=(/etc/zsh/functions $fpath)
/etc/zsh/functions/_hosts:
[....]
_wanted hosts expl host
compadd -M 'm:{a-z}={A-Z} r:|.=* r:|=*' -a "$@" - _hosts
Answered by Tomasz Pala on October 31, 2021
I believe you are editing a wrong line.
AFAIK config_hosts in Unix/_ssh refers to the host entries in your ~./ssh/config, not /etc/hosts.
The completion rules for /etc/hosts are defined a bit earlier, in the following block:
# If users-hosts matches, we shouldn't complete anything else.
if [[ "$IPREFIX" == *@ ]]; then
_combination -s '[:@]' my-accounts users-hosts "users=${IPREFIX/@}" hosts "$@" && return
else
_combination -s '[:@]' my-accounts users-hosts
${opt_args[-l]:+"users=${opt_args[-l]:q}"} hosts "$@" && return
fi
but this in turn just reuses the hosts style defined in Unix/_hosts
So if you edit the compadd definition at the end of the Unix/_hosts file like this:
#_wanted hosts expl host
# compadd -M 'm:{a-zA-Z}={A-Za-z} r:|.=* r:|=*' -a "$@" - _hosts
_wanted hosts expl host
compadd -a "$@" - _hosts
you should get the behavior you want.
P.S.
Please note that editing a system-wide completion files is not generally a very good practice, so you may want to just redefine hosts in your local ZSH config instead, e.g. by adding a function like that to your ~./zsh:
_hosts() { compadd $(getent hosts | tr -s ' ' 't' | cut -f2) }
Answered by zeppelin on October 31, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP