Bash – Can a bash script include its own auto-completions

autocompletebashcompgen

There are many resources available (1, 2, 3) that explain how to make use of bash's ability to auto-complete commands and arguments, but all of these resources require the addition of code to a user's ~/.bash_profile or /etc/bash_completion.d/* but is there a way to make a script and its available completions self-contained? As a crude and incomplete example:

~/bin/script-with-integrated-autocomplete.sh:

#!/usr/bin/env bash

function _completions {
  complete ...
}

if [ "$1" == "completions" ]; then
  _completions
  exit 0
fi

# (Whatever the script really does goes here.)
# ...
# ...

The deal-breaker (in the context of this question) is that the above example still requires you to add something like ~/bin/script-with-integrated-autocomplete.sh completions to your .profile to engage the completions.

Is there a way for a single bash script (file) to declare its own completions and have bash recognize them at the time of invocation (ideally with no additional system or environment setup)?

Best Answer

As stated in comments to your question, Bash cannot complete the command line for a command without any previous configuration and without changing the current environment. Bash does not peek into files, nor it tries to interpret them unless you explicitly execute or source them.

My read on the subject is that such a feature is unlikely to make its way into Bash anytime soon, because of compatibility and resources concerns—as a shell, it is meant to reliably run on a variety of different systems, with a variety of purposes and adapting to the preferences of a variety of users; and it has only one person as developer and maintainer1.

While Bash provides the tools to implement autocompletion, completion generators seem to be by design intended as external facilities. And indeed what you are looking for can be easily implemented with the existing tools and a small amount of work.

Given the sample script foo:

#!/bin/bash

# Setting up completion for foo. Only if the script is sourced
if [ "$0" != "$BASH_SOURCE" ]; then
    _foo() {
        local cur
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        COMPREPLY=( $(compgen -W 'bar baz' -- "$cur") )
        return 0
    }
    complete -F "_foo" "foo"
    return
fi

# The body of foo -- what it does when executed
echo foo "$*"
# ...

using bash-completion, self-contained autocompletion can be enabled by adding a symbolic link to foo to the directory that stores dynamically loaded user completions:

ln -s /path/to/foo ~/.local/share/bash-completion/completions/

The exact path may vary, you can refer to bash-completion's FAQ for how to check its value on your system.

Without bash-completion, one approach could be to define a default completion function that sources (files corresponding to) commands if they meet certain conditions; here we are using an associative array as a whitelist (to avoid blindly sourcing every command on the system). This code has to be added to your .bashrc (or equivalent, i.e. a file that is sourced in the current environment every time an interactive shell is started):

_default_completion () {
    # Do nothing when completion is invoked for an empty command
    if [ "$1" = "" ]; then
        return 1
    fi
    # Get the command path, exit with failure if it can't be found
    cmd=$(type -p "$1") || return 1
    if [ "${_compwhitelist["${cmd##*/}"]}" = y ]; then
        # Source the file corresponding to the command; on success,
        # return 124 to trigger a new autocompletion attempt
        source "$cmd" && return 124
    fi
}
complete -D -F _default_completion -o bashdefault -o default

# The list of commands with embedded completion
declare -A _compwhitelist
_compwhitelist[foo]=y

I know, you asked for a solution that didn't require to edit configuration files, but please note that a certain amount of configuration is always required. A substantial part of it is done by distribution/package maintainers, even if users may never come to realize it.

1 Reference: the Bash home page on Chet Ramey's website and point "A1" in the related Bash FAQ.