bash does not autocomplete my command when using sudo?

5,171

Solution 1

Check set -o to see if maybe posix mode is enabled. If so, disable with set +o posix

In "posix" mode, bash's tab-completion is for some reason disabled in "vi-mode". I've not seen any explanation why this behavior is so, nor why it's specific to vi-mode, so I didn't bother explaining.

PS: This answer is more suited for someone who stumbles upon the question via a search engine... not for your particular setup.

Update:
Chet Ramey says:

in POSIX mode, the vi editing mode should not map tab to complete. This is because POSIX.2 completely specifies the behavior of the vi editing mode, and the standard requires tab to be mapped to self insert by default.

Solution 2

Quick answer:

  • Install bash-completion
  • Source bash_completion on Bash startup
  • Add your compspec on Bash startup
  • Don't overwrite sudo compspec with complete -cf sudo

I suppose that you use MacOSX with brew.

Try:

brew update
brew install bash-completion
brew info bash-completion
# bash-completion: stable 1.3
. $(brew --prefix)/etc/bash_completion
complete -p sudo

You should see something like this:

complete -F _root_command sudo

Test:

function _comp_foo() { COMPREPLY=($(compgen -W 'a b c' -- "$2")); }
complete -F _comp_foo foo

Type foo SpaceTabTab
You should see a b c

Type sudo foo SpaceTabTab
You should see a b c

Then remove complete -fc sudo from your initialisation files (~/.bash_profile, ~/.bashrc etc)
Add the following lines to your initialisation files:

if [ -f $(brew --prefix)/etc/bash_completion ]; then
  . $(brew --prefix)/etc/bash_completion
fi

# Your compspec here
complete -o default -o nospace -W "$(sudo ls -1 /var/cache/salt/master/minions)" salt

Reopen terminal.
Type complete -p sudo.
You should see complete -F _root_command sudo.

Type complete -p salt.
You should see something like this:

complete -o default -o nospace -W 'a
b
c' salt

bash-completion2.* note:
You can install bash-completion2: https://github.com/Homebrew/homebrew/issues/19258
But:

  • 2.* works with Bash 4.*
  • 2.* uses the -D option for compspecs lazy loading. So, complete -p sudo outputs complete: sudo: no completion specification until you type sudoSpaceTab
Share:
5,171
Nathan Hangen
Author by

Nathan Hangen

Updated on September 18, 2022

Comments

  • Nathan Hangen
    Nathan Hangen over 1 year

    I'm using SaltStack. I would like to auto-complete the minion name when calling the salt command.

    The following line has been added into ~/.bashrc:

    complete -o default -o nospace -W "$(sudo ls -1 /var/cache/salt/master/minions)" salt
    

    Then typing salt inTabsalt integration-Tab; I can see it works as expected:

    $ salt integration-TabTab
    integration-c   integration-u   integration-u2

    To use with sudo, I have added complete -cf sudo into ~/.bashrc, but it didn't work:

      sudo salt inTab

    returned nothing.

    I also have tried to install bash_completion and added the following lines to ~/.bash_profile:

    if [ -f $(brew --prefix)/etc/bash_completion ]; then
        . $(brew --prefix)/etc/bash_completion
    fi
    

    but no luck.

    Did I miss something?


    Update

    Oh, the first thing I would like to say is sometimes it works:

    $ sudo salt integration-TabTab
    integration-c   integration-u   integration-u2

    and sometimes it doesn't.

    So first, let's see how much your bash_completion package does.

    How can I check that? Here's my function:

    # a wrapper method for the next one, when the offset is unknown
    _command()
    {
        local offset i
    
        # find actual offset, as position of the first non-option
        offset=1
        for (( i=1; i <= COMP_CWORD; i++ )); do
            if [[ "${COMP_WORDS[i]}" != -* ]]; then
                offset=$i
                break
            fi
        done
        _command_offset $offset
    }
    
    # A meta-command completion function for commands like sudo(8), which need to
    # first complete on a command, then complete according to that command's own
    # completion definition - currently not quite foolproof (e.g. mount and umount
    # don't work properly), but still quite useful.
    #
    _command_offset()
    {
        local cur func cline cspec noglob cmd i char_offset word_offset \
            _COMMAND_FUNC _COMMAND_FUNC_ARGS
    
        word_offset=$1
    
        # rewrite current completion context before invoking
        # actual command completion
    
        # find new first word position, then
        # rewrite COMP_LINE and adjust COMP_POINT
        local first_word=${COMP_WORDS[$word_offset]}
        for (( i=0; i <= ${#COMP_LINE}; i++ )); do
            if [[ "${COMP_LINE:$i:${#first_word}}" == "$first_word" ]]; then
                char_offset=$i
                break
            fi
        done
        COMP_LINE=${COMP_LINE:$char_offset}
        COMP_POINT=$(( COMP_POINT - $char_offset ))
    
        # shift COMP_WORDS elements and adjust COMP_CWORD
        for (( i=0; i <= COMP_CWORD - $word_offset; i++ )); do
            COMP_WORDS[i]=${COMP_WORDS[i+$word_offset]}
        done
        for (( i; i <= COMP_CWORD; i++ )); do
            unset COMP_WORDS[i];
        done
        COMP_CWORD=$(( $COMP_CWORD - $word_offset ))
    
        COMPREPLY=()
        _get_comp_words_by_ref cur
    
        if [[ $COMP_CWORD -eq 0 ]]; then
            _compopt_o_filenames
            COMPREPLY=( $( compgen -c -- "$cur" ) )
        else
            cmd=${COMP_WORDS[0]}
            if complete -p ${cmd##*/} &>/dev/null; then
                cspec=$( complete -p ${cmd##*/} )
                if [ "${cspec#* -F }" != "$cspec" ]; then
                    # complete -F <function>
    
                    # get function name
                    func=${cspec#*-F }
                    func=${func%% *}
    
                    if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then
                        $func $cmd "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" "${COMP_WORDS[${#COMP_WORDS[@]}-2]}"
                    else
                        $func $cmd "${COMP_WORDS[${#COMP_WORDS[@]}-1]}"
                    fi
    
                    # remove any \: generated by a command that doesn't
                    # default to filenames or dirnames (e.g. sudo chown)
                    # FIXME: I'm pretty sure this does not work!
                    if [ "${cspec#*-o }" != "$cspec" ]; then
                        cspec=${cspec#*-o }
                        cspec=${cspec%% *}
                        if [[ "$cspec" != @(dir|file)names ]]; then
                            COMPREPLY=("${COMPREPLY[@]//\\\\:/:}")
                        else
                            _compopt_o_filenames
                        fi
                    fi
                elif [ -n "$cspec" ]; then
                    cspec=${cspec#complete};
                    cspec=${cspec%%${cmd##*/}};
                    COMPREPLY=( $( eval compgen "$cspec" -- "$cur" ) );
                fi
            elif [ ${#COMPREPLY[@]} -eq 0 ]; then
                _filedir
            fi
        fi
    }
    

    if you type sudo mkdirTabTab, does it show a list of directories?

    Yes:

    $ sudo mkdir TabTab
    .FontForge/        .djangopypi2/      .ievms/            .ssh/              .wireshark-etc/
    • Mark Plotnick
      Mark Plotnick almost 10 years
      Chaining together command completions usually involves completion functions that invoke a function defined in the completion package called _command_offset. So first, let's see how much your bash_completion package does. If you remove the complete -cf sudo line from .bashrc, then logout and login again, and just use the support built in to bash_completion, do you see any special handling at all for sudo, e.g., if you type sudo mkdir <tab><tab>, does it show a list of directories?
    • Nathan Hangen
      Nathan Hangen almost 10 years
      @MarkPlotnick: if you type sudo mkdir <tab><tab>, does it show a list of directories? --> Yes, see my update in the original question.
    • Mark Plotnick
      Mark Plotnick almost 10 years
      If completion chaining is sometimes working and sometimes not, the first thing I'd check is the completion definition for sudo when completions are not working. Do this by typing complete|grep sudo. Bash on OSX reads its init files in nonintuitive ways (see apple.stackexchange.com/a/13019), so my hunch is that sometimes not all your completion definitions are being read in or they are being superseded by unwanted definitions.
    • G-Man Says 'Reinstate Monica'
      G-Man Says 'Reinstate Monica' almost 9 years
  • Evgeny Vereshchagin
    Evgeny Vereshchagin almost 9 years