184

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?

codeforester
  • 127
  • 1
  • 1
  • 8
UAdapter
  • 17,157
  • 38
  • 78
  • 102

8 Answers8

256

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

Louis Soulez
  • 2,910
  • 2
  • 12
  • 9
  • 9
    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 09:23
  • 4
    Tutorial - [Creating a bash completion script](https://iridakos.com/tutorials/2018/03/01/bash-programmable-completion-tutorial.html) – Lazarus Lazaridis Mar 16 '18 at 11:16
  • The path in the answer looks to be obsolete. User completion scripts can be stored in `~/.local/share/bash-completion/completions/` after creating it. System completion scripts are generally stored in `/usr/share/bash-completion/completions/`. – Asclepius Nov 28 '20 at 15:52
  • Can you help me understand why the path in the answer is obsolete? Is it technology or convention that is evolving. From my .. "experiment" it seems like it is just convention evolving. What convention is it, so I can read about the history of it so I can understand why they made those decisions? The "experiment": even when I place my completion scripts in `~/.local/share/bash-completion/completions/`, they do not automatically get sourced. It's no different than if they're at `~/my/completion/dir`. Not a big deal, I just source them manually either way. – Ari Sweedler Dec 17 '21 at 21:41
  • I see `/etc/bash_completion.d/` gets automatically loaded according to other links here, but I don't see anything about `~/.local`. I know that's an XDG thing, but I guess I just suck at google-fu because I cannot find anything about automatic sourcing from the `share/bash-completion/completions` directory. – Ari Sweedler Dec 17 '21 at 21:42
  • [*Updated Source](https://web.archive.org/web/20181127195640/https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) – an4s911 Jun 03 '22 at 19:19
59

Here is a complete tutorial.

Let's 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_2
    ;;
    shortlist)
      echo option_1 option_2 shortlist
    ;;
    *)
     echo Wrong option
    ;;
  esac
  shift
done

Note the option shortlist. Calling the 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
kokosing
  • 691
  • 5
  • 6
  • What does the `-o nospace` option do? – Andrew Lamarra Nov 11 '17 at 03:09
  • 1
    @AndrewLamarra `-o nospace` prevents the insertion of a space after a successful completion. ([ref](https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion)) – Asclepius Nov 28 '20 at 15:55
  • 3
    The path in the answer looks to be obsolete, and the sourcing is largely unnecessary. User completion scripts are sourced from `~/.local/share/bash-completion/completions/`; this directory can be created by the user. System completion scripts are generally stored in `/usr/share/bash-completion/completions/`. – Asclepius Nov 28 '20 at 16:00
  • not working on bash ~/.local/share/bash-completion/completions – k_vishwanath Oct 06 '22 at 07:01
47

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

Florian Diesch
  • 86,013
  • 17
  • 224
  • 214
  • 174
    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 Chaula May 02 '17 at 08:03
  • 5
    The actual scripts for Ubuntu 16 are located in `/usr/share/bash-completion/completions/` – Peter Chaula Sep 26 '17 at 21:26
  • 30
    Imo, examples should be included in the answer, not in a link. – But those new buttons though.. Mar 30 '18 at 18:24
  • 5
    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 '18 at 08:59
  • after days of searching this has so far been the only link that helped me. thanks! – snowe2010 Jan 20 '19 at 01:02
  • 9
    `The provided link has that already` - it might today, but it mightn't tomorrow. Or next year. Or in a decade. Whatever you might suggest about the documentation still being relevant, Stack Overflow discourages link-only answers for these reasons. – Liam Dawson Jan 20 '19 at 09:23
  • @LiamDawson, I found over 200 copies currently and there are over 400 copies in the WBM at archive.org. It seems as likely to exist as this site is. But just edit it yourself and add a pertinent quote, no harm done. – pbhj Feb 24 '19 at 23:07
  • Looks like we need a PhD degree in compilers to understand. I gave up after 5 minutes. – Jus12 Apr 26 '20 at 06:56
  • @Jus12 You don't need to understand all of it to make it work, just enough for your use case. Try this simpler [tutorial](https://iridakos.com/programming/2018/03/01/bash-programmable-completion-tutorial). – Asclepius Nov 28 '20 at 15:48
  • The paths noted in the answer look to be obsolete. User completion scripts can be stored in `~/.local/share/bash-completion/completions/` after creating it. System completion scripts are generally stored in `/usr/share/bash-completion/completions/`. – Asclepius Nov 28 '20 at 15:51
35

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.

Marco Ceppi
  • 47,783
  • 30
  • 172
  • 197
  • 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 05:40
  • @Chris see instructions at [Bash Completion FAQ](https://github.com/scop/bash-completion/blob/master/README.md) – jarno Jan 20 '19 at 12:14
  • Nowadays most completions are located in the directory given by command `pkg-c`onfig --variable=completionsdir bash-completion` and that directory is the recommendation given by Bash Completion FAQ linked above. – jarno Jan 20 '19 at 12:20
  • 1
    The path in the answer looks to be obsolete. As per [this FAQ](https://github.com/scop/bash-completion/blob/master/README.md), user completion scripts are sourced from `~/.local/share/bash-completion/completions/`; this directory can be created by the user. System completion scripts are generally stored in `/usr/share/bash-completion/completions/`. – Asclepius Nov 28 '20 at 16:02
23

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
Ben
  • 331
  • 2
  • 7
  • How do I make this work with `bash jupyter `? – papampi Nov 20 '18 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](https://iridakos.com/tutorials/2018/03/01/bash-programmable-completion-tutorial.html) about bash completion. It doesn't do exactly what you need, but maybe it'll help you out. Good luck! – Ben Nov 20 '18 at 21:05
2

Just adding this here in case it helps anyone.

Examples aside, the definitive source/reference to learn more about this topic is the bash manual itself (i.e. type man bash in a terminal)

Search for headings "Completing" and "Programmable Completion". (in the current version of the bash documentation, these appear around lines 2219 and 2332 respectively), and also the relevant bash builtin keywords referenced in those sections (e.g. compgen (line 2627), complete (line 2637), etc)

1

None of these answers gave me the 'type some random stuff and it just works' feel I was looking for so I used a different approach that I think works much better using fzf (sudo apt install fzf)

Let's take a simple case first.

Let's say we want to echo some word from a dictionary (/usr/share/dict/words) with auto-suggestions of the words.

We can do this with this bash function:

 function ppp(){ echo "Your word is: $(cat /usr/share/dict/words | fzf -q "$1" --prompt "hi> ")";};

Paste that into your shell then try:

ppp fuzz [hit enter]

enter image description here

Choose your word by typing more letters or using arrow keys, hit enter, and:

Your word is: fuzzball

Perfect.

Now let's take a more interesting example - let's do auto-completion for a package.json file's "script" commands

First sudo apt get install jq fzf then copy/paste this function:

function ppp(){ pnpm $(jq -r ".scripts|keys[]"<$(pnpm root|sed 's/node_modules//')package.json|fzf -q "$1" --prompt "pnpm> ");};

Now go into a node project folder (or subfolder) and

ppp tes

enter image description here

This works for anything for the pattern of filter/choose/run command.

Add the function to your .bashrc and happy days.

0

I wrote a script that makes it automatic to do this.

All you need to do just mark the function and arguments with #@. After that you get script with auto-complete! [[ -n "$XARGPARES_VERSION" ]] || . "$(which xargparse)"

#@
function simple_fun()
{
    a=A              #@ 
    b=B              #@ 
    ____ "$@"


    echo "a=$a"
    echo "b=$b"
}

main "$@"

example

Take a look at this https://github.com/dezhaoli/xargparse This script named xargparse can help you easy to write user-friendly command-line interfaces, and also automatically generates help and usage messages and issues errors.

dezhaoli
  • 1
  • 1