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 :)
Update:
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
The following builds on the OP's own code in the question and:
\
-escaping - e.g., names with embedded spaces - correctly; reports an error if an invalid directory name is specified (without completion)/
, so that further completion based on subdirectories can be performed right away, if desired.The modified examples are:
Here's the complete code (note that the syntax coloring suggests malformed code, but that's not the case):
It is possible to completely replace the current word with a single new word. With my bash 4.2.29, I can do this:
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: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:
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.