fzf
fzf copied to clipboard
Feature - replace hostname completion with fzf
- [x] I have read through the manual page (
man fzf
) - [x] I have the latest version of fzf
- [x] I have searched through the existing issues
Info
- OS
- [x] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Etc.
- Shell
- [x] bash
- [ ] zsh
- [ ] fish
Problem / Steps to reproduce
Proof of concept for integrating "fzf" into bash completions (known_hosts in this case)
The following script that replaces declared the "_known_host_real()" function with one that uses "fzf", for all commands that does hostname completion (like "ssh")
It came about it as I have my own source of "hostnames" of accounts I use that is more accurate that the existing method (extracting them from "~/.ssh/known_hosts" sources. But I did not want to modify the system installed version, which gets updated from packages. My 'source' of hostnames is not included in the script below, but you can replace the call to the original function to be anything you want.
Currently I source this file from my ".bashrc" to replace the "_known_host_real" function. Now when I type "ssh {some partial hostname}" and hit 'TAB' to do hostname completion (as normal). With that "fzf" will pop up the hostname completions, about will abort is you press 'ESC'.
No need to type '**' or any other weird work around, as "fzf" is properly integrated, at least for "known hosts".
#!/bin/echo "Bash source Script!"
#
# compgen_known_hosts_fzf.bash
#
# Replace "_known_hosts_real()" function with one that uses "fzf"
# that allows the user to interactively select the host they want to use.
#
# To activate source this script from your ".bashrc"
#
# Anthony Thysen <[email protected]>, 9 January 2021
#
# Rename the current "_known_hosts_real" function to "_known_hosts_original".
# The original is declared in "/usr/share/bash-completion/bash_completion",
# and is used by many completions, such as ssh, rsych, etc.
if type -t _known_hosts_original >/dev/null; then
: # function has already been redefined
else
eval "$( echo "_known_hosts_original()";
declare -f _known_hosts_real | tail -n +2 )"
# for now make original use the renamed function
_known_host_real() { _known_hosts_original "$@"; }
fi
# Define a replacement known hosts function that calls "fzf"
_known_hosts_fzf() {
local suffix prefix
local OPTIND=1
local user
#printf >&2 "_known_hosts_anthony "; printf >&2 '%s ' "$@"; echo >&2
while getopts "ac46F:p:" flag "$@"; do
case $flag in
a) ;; # aliases from ssh config -- ignore
c) suffix=':' ;;
F) configfile=$OPTARG ;;
p) prefix=$OPTARG ;;
4) ;; # ip4 addresses - ignore
6) ;; # ip6 addresses - ignore
esac
done
[[ $# -lt $OPTIND ]] &&
echo "error: $FUNCNAME: missing mandatory argument CWORD"
cur=${!OPTIND}; let "OPTIND += 1"
[[ $# -ge $OPTIND ]] &&
echo "error: $FUNCNAME("$@"): unprocessed arguments:"\
$(while [[ $# -ge $OPTIND ]]; do printf '%s\n' ${!OPTIND}; shift; done)
# Extract user name from current word
[[ $cur == *@* ]] && user=${cur%@*}@ && cur=${cur#*@}
# Get the original collection of known hosts (no limitations)
# This can be replaced with whatever source of hostnames you like!
_known_hosts_original -- ''
# Add prefix, user and suffix
for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do
COMPREPLY[i]="$prefix$user${COMPREPLY[i]}$suffix"
done
# Now use "fzf" to do the completion
# If user aborts (ESC) then print the current query string
COMPREPLY=( $(
printf '%s\n' "${COMPREPLY[@]}" |
FZF_DEFAULT_OPTS='--bind esc:print-query+abort' \
fzf -q "$cur" -m -i -1 -0
) )
printf '\e[5n'
}
# To redraw line after fzf closes (EG: handle the "printf '\e[5n'" above)
bind '"\e[0n": redraw-current-line'
# Now replace the function with our "fzf" version (if available)...
if type -t fzf >/dev/null; then
_known_hosts_real() { _known_hosts_fzf "$@"; }
fi
It came about it as I have my own source of "hostnames" of accounts I use that is more acculturate that the existing method (extracting them from "~/.ssh/known_hosts" sources.
This sounds cool. I second this. (#2366)
Currently I source this file from my ".bashrc" to replace the "_known_host_real" function. now when I type "ssh {some partial hostname}" and hit 'TAB' to do hostname completion (as normal). With that "fzf" will pop up the hostname completions, about will abort is you press 'ESC'.
No need to type '**' or any other weird work around, as "fzf" is properly integrated, at least for "known hosts".
You can probably accomplish this by sourcing this
_fzf_complete_host_notrigger() { FZF_COMPLETION_TRIGGER='' _fzf_host_completion; }
somewhere, as described in https://github.com/junegunn/fzf/wiki/Examples-(completion)#bash-custom-trigger-less-completion
You can probably accomplish this by sourcing this
_fzf_complete_host_notrigger() { FZF_COMPLETION_TRIGGER='' _fzf_host_completion; }
somewhere, as described in https://github.com/junegunn/fzf/wiki/Examples-(completion)#bash-custom-trigger-less-completion
Nice... didn't know about that, though that does not allow you to use a different source of hostnames, without replacing the original function, anyway!
Nice, yet I personally would rather want that something like this would be just optional.
I mean some people may want to have their _known_host_real()
replaced, but other may rather want to keep that and have the fzf
host completion separate from it (and use either of both depending on circumstances).
Rather than use the the function _known_hosts_fzf()
in the above script to replace _known_hosts_real()
function, you can use it to set the completion for specific commands. This is actually what I am currently doing now..
It could be better as it assumes ssh command options. BUT it does what I need. for good enough.
PS: r
is my personal wrapper around ssh
, while v
is r
but running it in a new xterm
#!/bin/echo "Bash source Script!"
#
# compgen_find_acct.bash
#
# Gather a list of personal hostnames I have accounts on, for completion
# of wrappered commands that make use of these accounts.
#
# This also uses "fzf" to allow the user to interactively select the host they
# want to use.
#
# Anthony Thysen <[email protected]>, 9 January 2021
#
###
#
# FUTURE: more specific "command completion" including options.
#
_find_acct() {
local cur prev words cword
_init_completion -n : || return # do basic var/file completions
# Do option handle options as per ssh
if [[ $cur == -* || $prev == -* ]]; then
type -t _ssh >/dev/null ||
source /usr/share/bash-completion/completions/ssh
_ssh 'ssh' "$cur" 'ssh'
return
fi
local user= hosts # user prefix and hostlist
# Extract user name from current cur
[[ $cur == *@* ]] && user=${cur%@*}@ && cur=${cur#*@}
# My sources of hosts....
# Start with the host aliases I have defined
hosts="$( sed 's/#.*//;/^DOMAIN=/d;s/^[^ ]* *//;/^$/d;s/ */\n/g;' \
~/lib/host_aliases )"
# now add my account list, or otherwise the SSH known hosts
if [ -f ~/misc/dist_accts ]; then
# Use my distribution accounts list (prefered)
hosts="$hosts"$'\n'$(awk '/^\]/{print $3}' ~/misc/dist_accts)
elif [ -f ~/.ssh/known_hosts ]; then
# Use the ssh known_hosts from this machine (first 'short' name only)
hosts="$hosts"$'\n'$(
sed '/^#/d; s/[[, :].*//; s/\.$//; s/_.*//;' ~/.ssh/known_hosts*
)
fi
# selection using "fzf" against available hostnames (tab to select multiple)
# If user aborts (ESC) then abort and return to original query string
if type -t fzf >/dev/null; then
COMPREPLY=( $(
FZF_DEFAULT_OPTS='--bind esc:print-query+abort' \
fzf -q "$cur" -m -i -1 -0 <<<"$hosts"
) )
printf '\e[5n'
return
fi
# extract matching hosts using old simplier methods
# Normal Method - match start only
#[ "X$COMPREPLY" = "X" ] &&
# COMPREPLY=( $( echo "$hosts" | grep -v '-' | egrep "^$cur" ) )
# try word start for 'cur' in hostnames ('-' separated)
[ "X$COMPREPLY" = "X" ] &&
COMPREPLY=( $( echo "$hosts" | egrep "\<$cur" ) )
# try any match
[ "X$COMPREPLY" = "X" ] &&
COMPREPLY=( $( echo "$hosts" | egrep "$cur" ) )
[ $# -ne 3 ] && echo "${COMPREPLY[@]}" # direct call output
# COMPREPLY=( $(compgen -f $cur) )
}
# set the commands that will use this completion.
complete -o nospace -F _find_acct r
complete -o nospace -F _find_acct v
complete -o nospace -F _find_acct cssh
complete -o nospace -F _find_acct do_dist
complete -o nospace -F _find_acct hostinfo