Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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/
share|improve this question
1  
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? –  Mark Plotnick Jul 11 '14 at 14:38
    
@MarkPlotnick: if you type sudo mkdir <tab><tab>, does it show a list of directories? --> Yes, see my update in the original question. –  quanta Jul 12 '14 at 4:49
1  
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. –  Mark Plotnick Jul 14 '14 at 21:29
    
Possibly related: How does Bash path completion work with sudo? –  G-Man Jun 27 at 8:41

3 Answers 3

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|improve this answer

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.

share|improve this answer
1  
Question about bash completion on posix mode –  Evgeny Vereshchagin Jun 28 at 3:01

sudo doesn't give you an login shell unless you ask for it with -i. So you'll find that without it you'll need to load the system's completions:

. /etc/bash_completion

If you run with -i, then $HOME will be ~root, and so anything written to the user's ~/.bashrc won't get read. If you can separate out the completion stuff from the rest of your ~/.bashrc, source that with . and that should work.

share|improve this answer
    
But this question is about somebody typing a sudo command into his default shell.  sudo hasn't started running yet, so its behavior is irrelevant; this is all about the behavior of the user's shell. –  G-Man Jun 27 at 7:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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