Bash – Add Autocompletion for Custom Script

autocompletebash

I am currently working on a shell script I called makeyourself.sh (linked to /usr/bin/mys) which automatically installs a debian package from source (via apt-get source -b $1).

So I decided to add an autocomplete function for the package name which is given as the first parameter: sudo mys panth<tab><tab> should then list every package beginning with panth — my try was as follows:

complete -C "apt-cache --no-generate pkgnames" mys

But it does not seem to work: when I press the tab key as mentioned above, at changes the parameter to mys, which returns me sudo mys mys.

What am I doing wrong? Do I have to specify the location in the command where complete has to insert the string which shall be completed, like {} or $1?

I use ElementaryOS, a Linux distribution based on Ubuntu. The script is a standard *.sh shell script which uses the standard #!/bin/bash

Best Answer

As you suspect, the command receives some information about what to complete. This is documented in the manual, but not in the documentation of the complete builtin, you need to read the introductory section on programmable completion.

any shell function or command specified with the -F and -C options is invoked. When the command or function is invoked, the COMP_LINE, COMP_POINT, COMP_KEY, and COMP_TYPE variables are assigned values as described above (see Bash Variables). If a shell function is being invoked, the COMP_WORDS and COMP_CWORD variables are also set. When the function or command is invoked, the first argument is the name of the command whose arguments are being completed, the second argument is the word being completed, and the third argument is the word preceding the word being completed on the current command line. No filtering of the generated completions against the word being completed is performed; the function or command has complete freedom in generating the matches.

So the command is called with three parameters:

  • The command name — so you can combine completions for similar commands in one script.
  • The word to be completed — so you can limit the output to the prefix that's going to be filtered anyway.
  • The previous word — useful e.g. to complete options.

The same parameters are passed to completion functions (complete -F somefunction). Note that whether you use a function or a command, it is your job to filter desired matches.

With what you tried, the command that ends up being executed is apt-cache --no-generate pkgnames mys '' mys. This prints a list of package names that start with mys (apt-cache pkgnames only looks at the first operand). The longest common prefix is mys, so bash starts completing mys and expects you to select the next letter.

Given that the arguments are appended to the command (not passed as positional parameters — the argument to -C is parsed as a shell command), there is no easy way to parse them. The simplest solution is to use a wrapper function.

_mys () {
  COMPREPLY=$(apt-cache --no-generate pkgnames "$2")
}
complete -F _mys mys