I'm wondering if this is possible in Bash, but I'd like to use tab completion to completely replace the current argument that's being expanded. I'll give an example: I'd like to have a function that moves up an arbitrary number of levels in the tree, so I can call up 2 And that would cd me 2 directories up. However, I would like to make it so that if at the number 2, I press tab, it will expand that number to being the path (either relative or absolute, either is fine). I have this almost working using the complete builtin except it will only append the text, so it will be something like up 2/Volumes/Dev/
Is it possible to replace the completed symbol?
Thanks in advance :)
So a big thanks to chepner, because actually checking my code revealed where my bug was. I was comparing against the wrong var, and the debug code I had was causing the value to not replace.
For anyone interested, here's the code (and there could be a much better way to accomplish this):
# Move up N levels of the directory tree
# Or by typing in some dir in the PWD
# eg. Assuming your PWD is "/Volumes/Users/natecavanaugh/Documents/stuff"
# `up 2` moves up 2 directories to "/Volumes/Users/natecavanaugh"
# `up 2/` and pressing tab will autocomplete the dirs in "/Volumes/Users/natecavanaugh"
# `up Users` navigate to "/Volumes/Users"
# `up us` and pressing tab will autocomplete to "/Volumes/Users"
function up {
dir="../"
if [ -n "$1" ]; then
if [[ $1 =~ ^[0-9]+$ ]]; then
strpath=$( printf "%${1}s" );
dir=" ${strpath// /$dir}"
else
dir=${PWD%/$1/*}/$1
fi
fi
cd $dir
}
function _get_up {
local cur
local dir
local results
COMPREPLY=()
#Variable to hold the current word
cur="${COMP_WORDS[COMP_CWORD]}"
local lower_cur=`echo ${cur##*/} | tr [:upper:] [:lower:]`
# Is the arg a number or number followed by a slash
if [[ $cur =~ ^[0-9]+/? ]]; then
dir="../"
strpath=$( printf "%${cur%%/*}s" );
dir=" ${strpath// /$dir}"
# Is the arg just a number?
if [[ $cur =~ ^[0-9]+$ ]]; then
COMPREPLY=($(compgen -W "${dir}"))
else
if [[ $cur =~ /.*$ ]]; then
cur="${cur##*/}"
fi
results=$(for t in `cd $dir && ls -d */`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi done)
COMPREPLY=($(compgen -P "$dir" -W "${results}"))
fi
else
# Is the arg a word that we can look for in the PWD
results=$(for t in `echo $PWD | tr "/" "\n"`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi; done)
COMPREPLY=($(compgen -W "${results}"))
fi
}
#Assign the auto-completion function _get for our command get.
complete -F _get_up up
Bourne shell and csh do not, but ksh, bash, tcsh, and zsh all have tab completion to varying degrees. The basic principle in all of these shells is the same; you type the start of the word, hit the <TAB> key twice, and the list of possible commands or files is displayed.
Tab Completion on Linux For example, let's say you want to run the firefox command. You can just type fir or fire into the terminal and press Tab — if your system doesn't have any other commands that begin with those letters, Bash will automatically fill in firefox and you can press Enter to run the command.
PowerShell provides completions on input to provide hints, enable discovery, and speed up input entry. Command names, parameter names, argument values and file paths can all be completed by pressing the Tab key. The Tab key is the default key binding on Windows.
It is possible to completely replace the current word with a single new word. With my bash 4.2.29, I can do this:
_xxx() { COMPREPLY=( foo ); }
complete -F _xxx x
x bar # pressing tab turns this into x foo
You encounter problems, however, if there is more than one possible completion, and you want to get partial completion of the common prefix. Then my experiments indicate that bash will try to match the available completions to the prefix you entered.
So in general you should probably only replace the current argument with something completely different if that something is uniquely defined. Otherwise, you should generate completions which match the current prefix, to have the user select from those. In your case you could replace the COMPREPLY=($(compgen -P "$dir" -W "${results}"))
with something along these lines:
local IFS=$'\n'
COMPREPLY=( $(find "${dir}" -maxdepth 1 -type d -iname "${cur#*/}*" -printf "%P\n") )
if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
COMPREPLY=( "${dir}${COMPREPLY[0]}" )
fi
However, in this specific case it might be better to only replace the prefix digit by the appropriate path, and leave everything else to the default bash completion:
_up_prefix() {
local dir cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=()
if [[ ${cur} =~ ^[0-9]+/? ]]; then
# Starting with a number, possibly followed by a slash
dir=$( printf "%${cur%%/*}s" );
dir="${dir// /../}"
if [[ ${cur} == */* ]]; then
dir="${dir}${cur#*/}"
fi
COMPREPLY=( "${dir}" "${dir}." ) # hack to suppress trailing space
elif [[ ${cur} != */* ]]; then
# Not a digit, and no slash either, so search parent directories
COMPREPLY=( $(IFS='/'; compgen -W "${PWD}" "${cur}") )
if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
dir="${PWD%${COMPREPLY[0]}/*}${COMPREPLY[0]}/"
COMPREPLY=( "${dir}" "${dir}." ) # hack as above
fi
fi
}
complete -F _up_prefix -o dirnames up
The code becomes a lot easier to read and maintain, plus more efficient to boot. The only drawback is that in some cases you'd have to press tab one more time than you used to: once to substitute the prefix, and twice more to actually see the list of possible completions. Your choice whether that's acceptable.
One more thing: the completion will turn the argument into a regular path, but your up function as it is does not accept those. So perhaps you should start that function with a [[ -d $1 ]]
check, and simply cd to that directory if it exists. Otherwise your completion will generate arguments which are unacceptable to the called function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With