Complete command options with quoted values - bash

I am trying to write a Bash completion function for completing long command line options like --param1=value1. This works fine if value1 is not quoted. But in many cases value1 needs to be quoted, for example, --param1='Hello world'. In this case, Bash completion stops working. For example:
_myprog()
{
local cur="${COMP_WORDS[$COMP_CWORD]}"
local words=(--param1 --param2)
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}
complete -F _myprog myprog
If I source this source script.sh and then type myprog --param1='hello' <tab><tab> nothing happens. It works fine if I start the quote before the double dashes, like myprog '--param1=hello' <tab><tab>..
Any suggestions?

This can be done using COMP_LINE instead of COMP_WORDS:
_myprog()
{
local comp_line=( $COMP_LINE )
local cur
if [[ ${COMP_LINE: -1} == ' ' ]] ; then
cur=""
else
cur="${comp_line[#]: -1}"
fi
local words=(--param1 --param2)
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}

Related

bash complete with colon

I'm trying to add auto-complete function to mytool command with following _mytool complete function:
_mytool()
{
local cur
_get_comp_words_by_ref -n : cur
# my implementation here
COMPREPLY=() # Array variable storing the possible completions.
cur=${COMP_WORDS[COMP_CWORD]}
if [[ $cur = -* ]]; then
COMPREPLY=( $(compgen -W '-a:first -a:second' -- $cur) )
fi
__ltrim_colon_completions "$cur"
}
complete -F _mytool mytool
Because there is ":" in my COMPREPLY, I try to make it work by using _get_comp_words_by_ref and __ltrim_colon_completions function, which is learnt from: here
But it still not work when type:
$mytool -a:[TAB]
there is no auto-complete at all. I was expecting bash will print the following completion for me:
-a:first -a:second
my bash version:
4.3.46(1)-release
What am I missing? Thanks!

Bash filename autocompletion after switch/parameter

I tried this other questions's accepted answer but it doesn't work for me. So please don't vote this as duplicate.
My script is named "tracker" and it accepts the following switches: --dummy --audit_sessiones --user_count --detailed_user_count --parfile
The --parfile switcj should be followed by a filename.
I have this autocompletion script:
_tracker()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="--dummy --audit_sessiones --user_count --detailed_user_count --parfile"
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
opts="ls *"
if [[ ${prev} == --parfile ]]; then
COMPREPLY=( "${files[#]##*/}" )
return 0
fi
}
complete -F _tracker tracker
Autocompletion of switches works fine.
But I also want the user to be able to use filename autocompletion right after the parameter --parfile but I haven't been able to make it work.
complete has a -o default option so you can remove the opts="ls *"; if ... fi part and just do complete -F _tracker -o default tracker.
According to bash manual:
If the -o default option was supplied to complete when
the compspec was defined, readline's default completion will
be performed if the compspec (and, if attempted, the default
bash completions) generate no matches.
Try replacing COMPREPLY=( "${files[#]##*/}" ) with COMPREPLY=( $(compgen -f ${cur}) )
More information about auto completion can be found in the following links
An introduction to bash completion: part 1
An introduction to bash completion: part 2

How to make bash completion ignore certain directories?

I am trying to write a completion function that ignores file names ending with *.bak and also directories named backup. Apart from this it should work like the default Readline completion (for example the one used for the ls command). Here is my first attempt:
$ cat setup.sh
_compTest() {
local cur
FIGNORE='bak:backup'
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
COMPREPLY=( $(compgen -f "$cur") )
}
complete -F _compTest aaa
After sourcing:
$ . setup.sh
I can type
$ aaa <tab><tab>
and I get a completion list excluding *.bak files, but unfortunately not backup directories.
How can I extend this function to exclude also backup directories from the completion list?
This can be done by post processing the array returned by compgen:
_compTest() {
local cur words
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
words=( $(compgen -f "$cur") )
COMPREPLY=()
for val in "${words[#]}" ; do
name=$(basename "$val")
if [[ $name == *.bak || $name == "backup" ]] ; then
continue
fi
COMPREPLY+=( "$val" )
done
}

Conditional trailing space with bash programmable completion

I'm creating a function to provide programmable completion for a command that I use (with much help from http://www.debian-administration.org/articles/317). The shell script usage is as follows:
script.sh command [command options]
where command can be either 'foo' or 'bar' and command options for 'foo' are 'a_foo=value' and 'b_foo=value' and command options for 'bar' are 'a_bar=value' and 'b_bar=value'.
Here's the configuration I'm using:
_script() {
local cur command all_commands
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
command="${COMP_WORDS[1]}"
all_commands="foo bar"
case "${command}" in
foo)
COMPREPLY=( $(compgen -W "--a_foo --b_foo" -- ${cur}) ); return 0;;
bar)
COMPREPLY=( $(compgen -W "--a_bar --b_bar" -- ${cur}) ); return 0;;
*) ;;
esac
COMPREPLY=( $(compgen -W "${all_commands}" -- ${cur}) )
return 0
}
complete -F _script script.sh
This mostly works as I'd like:
% script.sh f[TAB]
completes to:
% script.sh foo
(with a trailing space as desired)
However, this:
% script.sh foo a[TAB]
completes to:
% script.sh foo a_foo
(also with a trailing space)
I'd like to replace the trailing space with an '='. Alternatively, I'd be willing to change the values passed to compgen to be "--a_foo= --b_foo=", in which case I could just delete the trailing space.
Unfortunately, the command is not under my control, so I can't change the command line options to be of format "--a_foo value" instead of "--a_foo=value".
First you need to add = to the COMPREPLY:
COMPREPLY=( $(compgen -W "--a_foo= --b_foo=" -- ${cur}) )
next you need to tell completion not to add space after = with
compopt -o nospace
So, you script lines should be:
foo)
COMPREPLY=( $(compgen -W "--a_foo= --b_foo=" -- ${cur}) ); compopt -o nospace; return 0;;
bar)
COMPREPLY=( $(compgen -W "--a_bar= --b_bar=" -- ${cur}) ); compopt -o nospace; return 0;;

custom directory completion appends whitespace

I have the following directory structure:
/home/tichy/xxx/yyy/aaa
/home/tichy/xxx/yyy/aab
/home/tichy/xxx/yyy/aac
I would like to enter cdw y<TAB> and get cdw yyy/<CURSOR> as a result, so I could add cdw yyy/a<TAB> and get cdw yyy/aa<CURSOR>
The solution I came up with gives me the following:
cdw y<TAB> => cdw yyy<SPACE><CURSOR>
Following code I have so far:
_cdw () {
local cur prev dirs
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=($(compgen -d -- /home/tichy/xxx/${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
# no difference, a bit more logical:
dirs=$(compgen -o nospace -d /home/tichy/xxx/${cur}|perl -pe 's/{^/home/tichy/xxx/}{}')
COMPREPLY=($(compgen -d -W ${dir} ${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
return 0
}
complete -F _cdw cdw
cdw () {
cd /home/tichy/xxx/$#
}
Any ideas what's wrong? It seems to me that the completion process seems to be finished and isn't expecting any more input.
The simplest solution I've found so far is to generate completions that look like this:
COMPREPLY=( $( compgen -W "file1 file2 file3 dir1/ dir2/ dir3/" ) )
and add this line just before returning
[[ $COMPREPLY == */ ]] && compopt -o nospace
This sets the nospace option whenever the completion may fill in until the slash so that the user ends up with:
cmd dir1/<cursorhere>
instead of:
cmd dir1/ <cursorhere>
and it doesn't set the nospace option whenever the completion may fill in until a full filename so that the user ends up with:
cmd file1 <cursorhere>
instead of:
cmd file1<cursorhere>
If I understand correctly, you want to bash-autocomplete a directory name, and not have the extra space? (That's what I was looking for when I got to this page).
If so, when you register the completion function, use "-o nospace".
complete -o nospace -F _cdw cdw
I don't know if nospace works on compgen.
How about something like this:
COMPREPLY=( $(cdw; compgen -W "$(for d in ${cur}* ${cur}*/*; do [[ -d "$d" ]] && echo $d/; done)" -- ${cur}) )
(I'm not sure if you can call your shell function from here or not, otherwise you may have to duplicate it a bit.)
This also gets rid of your perl hack :-)
completion provides a solution for this without any workaround: funciton _filedir defined in /etc/bash_completion:
626 # This function performs file and directory completion. It's better than
627 # simply using 'compgen -f', because it honours spaces in filenames.
628 # #param $1 If `-d', complete only on directories. Otherwise filter/pick only
629 # completions with `.$1' and the uppercase version of it as file
630 # extension.
631 #
632 _filedir()
Then, specifying the following is enough:
_cdw () {
local cur prev dirs
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
_filedir # add -d at the end to complete only dirs, no files
return 0
}

Resources