When I use program like svn and I type in Gnome Terminal:

svn upd

and hit Tab it's autocompleted to:

svn update

Is it possible to do something like that in my custom bash script?

  • explain "bash script", you mean when editing a script? what do you want to do with it? – Bruno Pereira Oct 17 '11 at 15:25
  • 3
    when using script in console – UAdapter Oct 17 '11 at 15:29
up vote 36 down vote accepted

You can use the Programmable Completion. Have look at /etc/bash_completion and /etc/bash_completion.d/* for some examples.

  • 115
    How about including a simple example directly related to the question? – MountainX Jun 23 '13 at 21:14
  • The provided link already has that already. :D – peter May 2 '17 at 8:03
  • 2
    The actual scripts for Ubuntu 16 are located in /usr/share/bash-completion/completions/<program> – peter Sep 26 '17 at 21:26
  • 11
    Imo, examples should be included in the answer, not in a link. – billynoah Mar 30 at 18:24
  • 1
    I believe this platform is supposed to be a more practical alternative to full documentations that could be found with a simple google search. Dumping a documentation link doesn't help that. The link containing an anchor surely doesn't make much difference. – timuçin Jun 24 at 8:59

I'm six months late but I was looking for the same thing and found this:

You'll have to create a new file:

/etc/bash_completion.d/foo

For a static autocompletion (--help / --verbose for instance) add this:

_foo() 
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--help --verbose --version"

    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi
}
complete -F _foo foo
  • COMP_WORDS is an array containing all individual words in the current command line.
  • COMP_CWORD is an index of the word containing the current cursor position.
  • COMPREPLY is an array variable from which Bash reads the possible completions.

And the compgen command returns the array of elements from --help, --verbose and --version matching the current word "${cur}":

compgen -W "--help --verbose --version" -- "<userinput>"

Source : http://www.debian-administration.org/articles/316

  • 22
    This should be the accepted answer! It's a complete example. – Victor Schröder Aug 14 '15 at 11:21
  • 4
    Tip: If someone wants suggestions for words not starting with - and show them without having to start typing the target word, just remove the if [...] then and fi lines. – Cedric Reichenbach Sep 16 '15 at 9:23
  • 1
    This is the exact answer I've been looking for for hours, and it turns out it just drowned in some complicated documentation that just never mentions that the file should be placed in /etc/bash_completion.d/. I only came here because I wanted to post an answer somewhere, but it turns out someone was three years ahead all along :) Clear, concise and complete example, thank you! – Time Sheep Dec 14 '16 at 9:43
  • Tutorial - Creating a bash completion script – Lazarus Lazaridis Mar 16 at 11:16

All of the bash completions are stored in /etc/bash_completion.d/. So if you're building software with bash_completion it would be worthwhile to have the deb/make install drop a file with the name of the software in that directory. Here's an example bash completion script for Rsync:

# bash completion for rsync

have rsync &&
_rsync()
{
    # TODO: _split_longopt

    local cur prev shell i userhost path   

    COMPREPLY=()
    cur=`_get_cword`
    prev=${COMP_WORDS[COMP_CWORD-1]}

    _expand || return 0

    case "$prev" in
    --@(config|password-file|include-from|exclude-from))
        _filedir
        return 0
        ;;
    -@(T|-temp-dir|-compare-dest))
        _filedir -d
        return 0
        ;;
    -@(e|-rsh))
        COMPREPLY=( $( compgen -W 'rsh ssh' -- "$cur" ) )
        return 0
        ;;
    esac

    case "$cur" in
    -*)
        COMPREPLY=( $( compgen -W '-v -q  -c -a -r -R -b -u -l -L -H \
            -p -o -g -D -t -S -n -W -x -B -e -C -I -T -P \
            -z -h -4 -6 --verbose --quiet --checksum \
            --archive --recursive --relative --backup \
            --backup-dir --suffix= --update --links \
            --copy-links --copy-unsafe-links --safe-links \
            --hard-links --perms --owner --group --devices\
            --times --sparse --dry-run --whole-file \
            --no-whole-file --one-file-system \
            --block-size= --rsh= --rsync-path= \
            --cvs-exclude --existing --ignore-existing \
            --delete --delete-excluded --delete-after \
            --ignore-errors --max-delete= --partial \
            --force --numeric-ids --timeout= \
            --ignore-times --size-only --modify-window= \
            --temp-dir= --compare-dest= --compress \
            --exclude= --exclude-from= --include= \
            --include-from= --version --daemon --no-detach\
            --address= --config= --port= --blocking-io \
            --no-blocking-io --stats --progress \
            --log-format= --password-file= --bwlimit= \
            --write-batch= --read-batch= --help' -- "$cur" ))
        ;;
    *:*)
        # find which remote shell is used
        shell=ssh
        for (( i=1; i < COMP_CWORD; i++ )); do
            if [[ "${COMP_WORDS[i]}" == -@(e|-rsh) ]]; then
                shell=${COMP_WORDS[i+1]}
                break
            fi
        done
        if [[ "$shell" == ssh ]]; then
            # remove backslash escape from :
            cur=${cur/\\:/:}
            userhost=${cur%%?(\\):*}
            path=${cur#*:}
            # unescape spaces
            path=${path//\\\\\\\\ / }
            if [ -z "$path" ]; then
                # default to home dir of specified
                # user on remote host
                path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
            fi
            # escape spaces; remove executables, aliases, pipes
            # and sockets; add space at end of file names
            COMPREPLY=( $( ssh -o 'Batchmode yes' $userhost \
                command ls -aF1d "$path*" 2>/dev/null | \
                sed -e 's/ /\\\\\\\ /g' -e 's/[*@|=]$//g' \
                -e 's/[^\/]$/& /g' ) )
        fi
        ;;
    *)  
        _known_hosts_real -c -a "$cur"
        _filedir
        ;;
    esac

    return 0
} &&
complete -F _rsync $nospace $filenames rsync

# Local variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indent-comment: t
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et filetype=sh

It would likely be worthwhile to review one of the bash completion files in there that most closely matches your program. One of the simplest examples is the rrdtool file.

  • 2
    Can we configure completions to load from other locations? IE. ~/.local – Chris Jul 31 '14 at 16:14
  • 1
    Yes, you can put a file like this wherever you want and then put source ~/.local/mycrazycompletion in your ~/.bashrc – Stefano Palazzo Mar 22 '16 at 5:40

Here is a complete tutorial.

Lets have an example of script called admin.sh to which you would like to have autocomplete working.

#!/bin/bash

while [ $# -gt 0 ]; do
  arg=$1
  case $arg in
    option_1)
     # do_option_1
    ;;
    option_2)
     # do_option_1
    ;;
    shortlist)
      echo option_1 option_2 shortlist
    ;;
    *)
     echo Wrong option
    ;;
  esac
  shift
done

Note option shortlist. Calling script with this option will print out all possible options for this script.

And here you have the autocomplete script:

_script()
{
  _script_commands=$(/path/to/your/script.sh shortlist)

  local cur
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"
  COMPREPLY=( $(compgen -W "${_script_commands}" -- ${cur}) )

  return 0
}
complete -o nospace -F _script ./admin.sh

Note that the last argument to complete is the name of the script you want to add autocompletion to. All you need to do is to add your autocomplete script to bashrc as

source /path/to/your/autocomplete.sh

or copy it to /etc/bash.completion.d

  • 1
    What is the prev variable for? You don't seem to use it. – dimo414 Feb 28 '17 at 23:44
  • @dimo414 It seems so, I removed that variable – kokosing Mar 2 '17 at 7:19
  • What does the -o nospace option do? – Andrew Lamarra Nov 11 '17 at 3:09

If all you want is a simple word based auto-completion (so no subcommand completion or anything), the complete command has a -W option that just does the right thing.

For example, I have the following lines in my .bashrc to autocomplete a program called jupyter:

# gleaned from `jupyter --help`
_jupyter_options='console qtconsole notebook' # shortened for this answer
complete -W "${_jupyter_options}" 'jupyter'

Now jupyter <TAB> <TAB> autocompletes for me.

The docs at gnu.org are helpful.

It does seem to rely on the IFS variable being set correctly, but that hasn't caused any issues for me.

To add filename completion and default BASH completion, use the -o option:

complete -W "${_jupyter_options}" -o bashdefault -o default 'jupyter'

To use this in zsh, add the following code before running the complete command in your ~/.zshrc:

# make zsh emulate bash if necessary
if [[ -n "$ZSH_VERSION" ]]; then
    autoload bashcompinit
    bashcompinit
fi
  • How do I make this work with bash jupyter <TAB><TAB>? – papampi Nov 20 at 11:02
  • @papampi, I think it only works with one level of completion - I think to do it with 2 layers you'd need one of the more complicated answers above. Also, I recently read a pretty decent tutorial about bash completion. It doesn't do exactly what you need, but maybe it'll help you out. Good luck! – Ben Nov 20 at 21:05

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.