Table of Contents
Bash completion extends bashs standard completion behavior to achieve complex command lines with just a few keystrokes. This project was conceived to produce programmable completion routines for the most common Linux/UNIX commands, reducing the amount of typing sysadmins and programmers need to do on a daily basis.
~/.bash_completion
if unset or null.
COMP_*
environment variables
below. If $XDG_CONFIG_HOME
is unset or null, ~/.config
is
used instead of it.
bash_completion
when it is
loaded. If unset or null, the default compatibility directory to use is
/etc/bash_completion.d
.
configure
completion will return the entire option
string (e.g. --this-option=DESCRIPTION
) so one can see what kind of data
is required and then simply delete the descriptive text and add one’s own
data. If unset or null (default), configure
completion will strip
everything after the = when returning completions.
cvs commit
completion will try to complete on
remotely checked-out files. This requires passwordless access to the
remote repository. Default is unset.
iwconfig
completion will try to complete on
available wireless networks identifiers. Default is unset.
avahi-browse
for additional completions. This may be a slow operation in some setups.
Default is unset.
This document attempts to explain the basic styles and patterns that are used in the bash completion. New code should try to conform to these standards so that it is as easy to maintain as existing code. Of course every rule has an exception, but it’s important to know the rules nonetheless!
This is particularly directed at people new to the bash completion codebase, who are in the process of getting their code reviewed. Before getting a review, please read over this document and make sure your code conforms to the recommendations here.
Avoid "fancy" globbing in case labels, just use traditional style when possible. For example, do "--foo|--bar)" instead of "--@(foo|bar))". Rationale: the former is easier to read, often easier to grep, and doesn’t confuse editors as bad as the latter, and is concise enough.
Always use [[ ]] instead of [ ]. Rationale: the former is less error prone, more featureful, and slightly faster.
Try to wrap lines at 79 characters. Never go past this limit, unless you absolutely need to (example: a long sed regular expression, or the like). This also holds true for the documentation and the testsuite. Other files, like ChangeLog, or COPYING, are exempt from this rule.
When you need to do some code substitution in your completion script, you MUST use the $(…) construct, rather than the `…`. The former is preferable because anyone, with any keyboard layout, is able to type it. Backticks aren’t always available, without doing strange key combinations.
As a rule of thumb, do not use "complete -o filenames". Doing it makes it take effect for all completions from the affected function, which may break things if some completions from the function must not be escaped as filenames. Instead, use "compopt -o filenames" to turn on "-o filenames" behavior dynamically when returning completions that need that kind of processing (e.g. file and command names). The _filedir and _filedir_xspec helpers do this automatically whenever they return some completions.
The above is functionally a shorthand for:
if [[ ${#COMPREPLY[@]} -eq 1 && ${COMPREPLY[0]} == *= ]]; then compopt -o nospace fi
It is used to ensure that long options' name won’t get a space appended after the equal sign. Calling compopt -o nospace makes sense in case completion actually occurs: when only one completion is available in COMPREPLY.
Should be used in completions using the -s flag of _init_completion, or other similar cases where _split_longopt has been invoked, after $prev has been managed but before $cur is considered. If $cur of the form --foo=bar was split into $prev=--foo and $cur=bar and the $prev block did not process the option argument completion, it makes sense to return immediately after the $prev block because --foo obviously takes an argument and the remainder of the completion function is unlikely to provide meaningful results for the required argument. Think of this as a catch-all for unknown options requiring an argument.
Note that even when using this, options that are known to require an argument but for which we don’t have argument completion should be explicitly handled (non-completed) in the $prev handling block because --foo=bar options can often be written without the equals sign, and in that case the long option splitting does not occur.
The bash-completion package contains an automated test suite. Running the tests should help verifying that bash-completion works as expected. The tests are also very helpful in uncovering software regressions at an early stage.
The bash-completion test suite is written on top of the DejaGnu testing framework. DejaGnu is written in Expect, which in turn uses Tcl — Tool command language.
The bash-completion test suite tries to adhere to this Tcl Style Guide.
Installing dependencies should be easy using your local package manager.
On Debian/Ubuntu you can use apt-get
:
sudo apt-get install dejagnu tcllib
This should also install the necessary expect
and tcl
packages.
The tests are grouped into different areas, called tool in DejaGnu:
Each tool has a slightly different way of loading the test fixtures, see Test context below.
Completion tests are spread over two directories: completion/\*.exp
calls
completions in lib/completions/\*.exp
. This two-file system stems from
bash-completion-lib (http://code.google.com/p/bash-completion-lib/, containing
dynamic loading of completions) where tests are run twice per completion; once
before dynamic loading and a second time after to confirm that all dynamic
loading has gone well.
For example:
set test "Completion via comp_load() should be installed" set cmd "complete -p awk" send "$cmd\r" expect { -re "^$cmd\r\ncomplete -o filenames -F comp_load awk\r\n/@$" { pass "$test" } -re /@ { fail "$test at prompt" } } source "lib/completions/awk.exp" set test "Completion via _longopt() should be installed" set cmd "complete -p awk" send "$cmd\r" expect { -re "^$cmd\r\ncomplete -o filenames -F _longopt awk\r\n/@$" { pass "$test" } -re /@ { fail "$test at prompt" } } source "lib/completions/awk.exp"
Looking to the completion tests from a broader perspective, every test for a command has two stages which are now reflected in the two files:
test/completion/foo
)
test/lib/completions/foo
)
The tests are run by calling runtest
command in the test directory:
runtest --outdir log --tool completion runtest --outdir log --tool install runtest --outdir log --tool unit
The commands above are already wrapped up in shell scripts within the test
directory:
./runCompletion ./runInstall ./runUnit
To run a particular test, specify file name of your test as an argument to
runCompletion
script:
./runCompletion ssh.exp
That will run test/completion/ssh.exp
.
The test suite requires a connected terminal (tty). When invoked via cron, no tty is connected and the test suite may respond with this error:
can't read "multipass_name": no such variable
To run the tests successfully via cron, connect a terminal by redirecting stdin from a tty, e.g. /dev/tty40. (In Linux, you can press alt-Fx or ctrl-alt-Fx to switch the console from /dev/tty1 to tty7. There are many more /dev/tty* which are not accessed via function keys. To be safe, use a tty greater than tty7)
./runUnit < /dev/tty40
If the process doesn’t run as root (recommended), root will have to change the owner and permissions of the tty:
sudo chmod o+r /dev/tty40
To make this permission permanent (at least on Debian) - and not revert back on
reboot - create the file /etc/udev/rules.d/10-mydejagnu.rules
, containing:
KERNEL=="tty40", MODE="0666"
To start the test at 01:00, set the crontab to this:
* 1 * * * cd bash-completion/test && ./cron.sh < /dev/tty40
Here’s an example batch file cron.sh
, to be put in the bash-completion test
directory. This batch file only e-mails the output of each test-run if the
test-run fails.
#!/bin/sh set -e # Exit if simple command fails set -u # Error if variable is undefined LOG=/tmp/bash-completion.log~ # Retrieve latest sources git pull # Run tests on bash-4 ./runUnit --outdir log/bash-4 --tool_exec /opt/bash-4.3/bin/bash > $LOG || cat $LOG ./runCompletion --outdir log/bash-4 --tool_exec /opt/bash-4.3/bin/bash > $LOG || cat $LOG # Clean up log file [ -f $LOG ] && rm $LOG
You can run cd test && ./generate cmd
to add a test for the cmd
command.
This will add two files with a very basic tests:
test/completion/cmd.exp test/lib/completions/cmd.exp
Place any additional tests into test/lib/completions/cmd.exp
.
Let’s consider this real-life example where an ssh completion bug is fixed. First you’re triggered by unsuccessful tests:
$ ./runCompletion ... === completion Summary === # of expected passes 283 # of unexpected failures 8 # of unresolved testcases 2 # of unsupported tests 47
Take a look in log/completion.log
to find out which specific command is
failing.
$ vi log/completion.log
Search for UNRESOLVED
or FAIL
. From there scroll up to see which .exp
test is failing:
/@Running ./completion/ssh.exp ... ... UNRESOLVED: Tab should complete ssh known-hosts at prompt
In this case it appears ssh.exp
is causing the problem. Isolate the ssh
tests by specifying just ssh.exp
to run. Furthermore add the --debug
flag,
so output gets logged in dbg.log
:
$ ./runCompletion ssh.exp --debug ... === completion Summary === # of expected passes 1 # of unresolved testcases 1
Now we can have a detailed look in dbg.log
to find out what’s going wrong.
Open dbg.log
and search for UNRESOLVED
(or FAIL
if that’s what you’re
looking for):
UNRESOLVED: Tab should complete ssh known-hosts at prompt
From there, search up for the first line saying:
expect: does "..." match regular expression "..."
This tells you where the actual output differs from the expected output. In this case it looks like the test "ssh -F fixtures/ssh/config <TAB>" is expecting just hostnames, whereas the actual completion is containing commands - but no hostnames. So what should be expected after "ssh -F fixtures/ssh/config <TAB>" are both commands and hostnames. This means both the test and the completion need fixing. Let’s start with the test.
$ vi lib/completions/ssh.exp
Search for the test "Tab should complete ssh known-hosts". Here you could’ve seen that what was expected were hostnames ($hosts):
set expected "^$cmd\r\n$hosts\r\n/@$cmd$"
Adding all commands (which could well be over 2000) to expected, seems a
bit overdone so we’re gonna change things here. Lets expect the unit test for
_known_hosts
assures all hosts are returned. Then all we need to do here is
expect one host and one command, just to be kind of sure that both hosts and
commands are completed.
Looking in the fixture for ssh:
$ vi fixtures/ssh/known_hosts
it looks like we can add an additional host ls_known_host. Now if we would
perform the test "ssh -F fixtures/ssh/config ls<TAB>" both the command ls
and
the host ls_known_host
should come up. Let’s modify the test so:
$ vi lib/completions/ssh.exp ... set expected "^$cmd\r\n.*ls.*ls_known_host.*\r\n/@$cmd$"
Running the test reveals we still have an unresolved test:
$ ./runCompletion ssh.exp --debug ... === completion Summary === # of expected passes 1 # of unresolved testcases 1
But if now look into the log file ‘dbg.log` we can see the completion only returns commands starting with ls but fails to match our regular expression which also expects the hostname `ls_known_host’:
$ vi dbg.log ... expect: does "ssh -F fixtures/ssh/config ls\r\nls lsattr lsb_release lshal lshw lsmod lsof lspci lspcmcia lspgpot lss16toppm\r\nlsusb\r\n/@ssh -F fixtures/ssh/config ls" (spawn_id exp9) match regular expression "^ssh -F fixtures/ssh/config ls\r\n.*ls.*ls_known_host.*\r\n/@ssh -F fixtures/ssh/config ls$"? no
Now let’s fix ssh completion:
$ vi ../contrib/ssh ...
until the test shows:
$ ./runCompletion ssh.exp ... === completion Summary === # of expected passes 2
Now let’s consider a unit test failure. First you’re triggered by unsuccessful tests:
$ ./runUnit ... === unit Summary === # of expected passes 1 # of unexpected failures 1
Take a look in log/unit.log
to find out which specific command is failing.
$ vi log/unit.log
Search for UNRESOLVED
or FAIL
. From there scroll up to see which .exp
test is failing:
/@Running ./unit/_known_hosts_real.exp ... ... FAIL: Environment should stay clean
In this case it appears _known_hosts_real.exp
is causing the problem.
Isolate the _known_hosts_real
test by specifying just _known_hosts_real.exp
to run. Furthermore add the --debug
flag, so output gets logged in
dbg.log
:
$ ./runUnit _known_hosts_real.exp --debug ... === completion Summary === # of expected passes 1 # of unexpected failures 1
Now, if we haven’t already figured out the problem, we can have a detailed look
in dbg.log
to find out what’s going wrong. Open dbg.log
and search for
UNRESOLVED
(or FAIL
if that’s what you’re looking for):
FAIL: Environment should stay clean
From there, search up for the first line saying:
expect: does "..." match regular expression "..."
This tells you where the actual output differs from the expected output. In
this case it looks like the the function _known_hosts_real
is unexpectedly
modifying global variables cur
and flag
. In case you need to modify the
test:
$ vi lib/unit/_known_hosts_real.exp
The primary Wikipedia page is called test suite and not testsuite, so that’s what this document sticks to.
The name and location of this code generation script come from Ruby on Rails' script/generate.
The test environment needs to be put to fixed states when testing. For
instance the bash prompt (PS1) is set to the current test directory, followed
by an at sign (@). The default settings for bash
reside in config/bashrc
and config/inputrc
.
For each tool (completion, install, unit) a slightly different context is in effect.
When the completions are tested, invoking DejaGnu will result in a call to
completion_start()
which in turn will start bash --rcfile config/bashrc
.
What happens when completion tests are run?
| runtest --tool completion V +----------+-----------+ | lib/completion.exp | | lib/library.exp | | config/default.exp | +----------+-----------+ : V +----------+-----------+ +---------------+ +----------------+ | completion_start() +<---+ config/bashrc +<---| config/inputrc | | (lib/completion.exp) | +---------------+ +----------------+ +----------+-----------+ | ,+----------------------------+ | ,--+-+ "Actual completion tests" | V / +------------------------------+ +----------+-----------+ +-----------------------+ | completion/*.exp +<---| lib/completions/*.exp | +----------+-----------+ +-----------------------+ | \ ,+--------------------------------+ | `----------------------+-+ "Completion invocation tests" | V +----------------------------------+ +----------+-----------+ | completion_exit() | | (lib/completion.exp) | +----------------------+
Setting up bash once within completion_start()
has the speed advantage that
bash - and bash-completion - need only initialize once when testing multiple
completions, e.g.:
runtest --tool completion alias.exp cd.exp
What happens when install tests are run?
| runtest --tool install V +----+----+ | DejaGnu | +----+----+ | V +------------+---------------+ | (file: config/default.exp) | +------------+---------------+ | V +------------+------------+ | (file: lib/install.exp) | +-------------------------+
This is the bash configuration file (bashrc) used for testing:
# bashrc file for DejaGnu testsuite # Note that we do some initialization that would be too late to do here in # library.exp's start_bash(). # Use emacs key bindings set -o emacs # Use bash strict mode set -o posix # Unset `command_not_found_handle' as defined on Debian/Ubuntu, because this # troubles and slows down testing unset -f command_not_found_handle TESTDIR=$(pwd) export PS2='> ' # Also test completions of system administrator commands, which are # installed via the same PATH expansion in `bash_completion.have()' export PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin # ...as well as games on some systems not in PATH by default: export PATH=$PATH:/usr/games:/usr/local/games # For clean test state, avoid sourcing user's ~/.bash_completion export BASH_COMPLETION_USER_FILE=/dev/null # ...and avoid stuff in BASH_COMPLETION_USER_DIR overriding in-tree # completions. The user dir is first in the lookup path, so this should also # give precedence to the in-tree "completions" dir over other ones, e.g. # the one possibly in /usr/share/bash-completion. export BASH_COMPLETION_USER_DIR=$(cd "$SRCDIR/.."; pwd) # Make sure default settings are in effect unset -v \ COMP_CONFIGURE_HINTS \ COMP_CVS_REMOTE \ COMP_KNOWN_HOSTS_WITH_HOSTFILE \ COMP_TAR_INTERNAL_PATHS # Load bash testsuite helper functions . $SRCDIR/lib/library.sh # Local variables: # mode: shell-script # End: # ex: filetype=sh
This is the readline configuration file (inputrc) used for testing:
# Readline init file for DejaGnu testsuite # See: info readline # Press TAB once (instead of twice) to auto-complete set show-all-if-ambiguous on # No bell. No ^G in output set bell-style none # Don't query user about viewing the number of possible completions set completion-query-items -1 # Display completions sorted horizontally, not vertically set print-completions-horizontally on # Don't use pager when showing completions set page-completions off # Local variables: # mode: shell-script # End: # ex: filetype=sh