5

I have tried for hours to get this working and feel like I've not come anywhere close.

I am trying to shorten our workflow by tab-completing directory names from the Mac OS X Terminal.

We have an internal tool, let's call it bob, that automatically runs commands on git submodules. What I'd like to do is get it to autocomplete the subdirectory names. The problem is, those subdirectories live in a static location.

   project directory
   ->public
     ->submodules
       ->a-module
       ->another-module
       ->other-module
       ->some-module

Commands are run from the top project directory like so:

bob start bugfix anothermodule

Rather than having to fully type out 'anothermodule', which is what we currently do, I'd love to get tab autocomplete to work. We use this workflow many times per day and shaving seconds off of it would be great and avoid typos.

The end goal is:

bob start bugfix an<tab>

would autocomplete to

bob start bugfix anothermodule

I've tried CDPATH but quickly realized this was not what I'm after, as this isn't following the cd command, only my custom command.

I have bash_completion installed but I am not very good at bash in general. Mentioning this though in case it can be a part of the solution!

2 Answers 2

2

This is an enhancement of Kamil’s excellent answer that strips the hyphen from the -module part of each sub-directory name.

bugfix)
    curdir=$(pwd)
    cd public/submodules/ 2>/dev/null && _filedir -d
    # Strip the hyphen from `-module` in each sub-directory name.
    for ((i=0; i<${#COMPREPLY[@]}; i++)); do
        COMPREPLY[$i]="${COMPREPLY[$i]/-module/module}"
    done
    cd "$curdir"
    ;;

There’s still scope for improving this section: you may have to type the hyphen, i.e., type a-Tab to get it to complete to amodule instead of a choice between amodule and anothermodule (the hyphen is still stripped by the completion function).

2
  • I think if you replace COMPREPLY=( $( compgen -d -- "$cur" ) ) with just _filedir -d then your code will handle directory names with spaces better. Nov 20, 2017 at 15:18
  • Many thanks @KamilMaciorowski I've been using the bash-completion package for so long that I had thought the space handling was built into compgen. Nov 20, 2017 at 21:10
4
+100

That's right, bash_completion is a solution. I can tell you what I have in my Ubuntu. I hope things are at least similar on Mac.

There is /etc/bash_completion file which sources /usr/share/bash-completion/bash_completion.

In a directory /usr/share/bash-completion there's also a completions subdirectory. This is the place for scripts. Let's look at the eject script:

# bash completion for eject(1)                             -*- shell-script -*-

_eject()
{
    local cur prev words cword
    _init_completion || return

    case $prev in
        -h|--help|-V|--version|-c|--changerslot|-x|--cdspeed)
            return
            ;;
        -a|--auto|-i|--manualeject)
            COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) )
            return
            ;;
    esac

    if [[ $cur == -* ]]; then
        COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) )
        return
    elif [[ $prev == @(-d|--default) ]]; then
        return
    fi

    _cd_devices
    _dvd_devices
} &&
complete -F _eject eject

# ex: ts=4 sw=4 et filetype=sh

Even without reading the documentation we could reverse-engineer it a little and adjust to your case. For us the most important part of the above code is where it says that after -a, --auto -i or --manualeject there can be either on or off. In your case after bugfix you need a directory name.

(Or not. You have another-module directory but you use anothermodule argument. For now I assume it's a typo. In case it's not, Anthony's answer will point you in a right direction, I think.)

We could somehow extract these directories you want and replace 'on off', but it may be better to utilize the existing code. After I type cd, bash autocompletes directories, and it works well even if there are spaces or special characters. So let's look at _cd() function; it's in /usr/share/bash-completion/bash_completion, I won't paste it here. Next it seems the crucial part of this function is _filedir -d invocation. Just before where _filedir() is defined, we have:

# This function performs file and directory completion. It's better than
# simply using 'compgen -f', because it honours spaces in filenames.

Bingo.

So I copied the eject script under the name bob and edited to the form of:

# bash completion for bob                             -*- shell-script -*-

_bob()
{
    local cur prev words cword curdir
    _init_completion || return

    case $prev in
        bob)
            COMPREPLY=( $( compgen -W 'start stop foo bar' -- "$cur" ) )
            return
            ;;
        start)
            COMPREPLY=( $( compgen -W 'bugfix' -- "$cur" ) )
            return
            ;;
        bugfix)
            curdir=$(pwd)
            cd public/submodules/ 2>/dev/null && _filedir -d
            cd "$curdir"    
            ;;
    esac

} &&
complete -F _bob bob

Existing bash sessions won't notice this file, you have to run a new bash to test it.

Yes, it's quick and dirty voodoo programming, sorry.

Notice we descend to public/submodules/ and go back. Normally I would use a subshell:

export -f _filedir
( cd … && _filedir -d )

but it looks like _filedir won't work this way.

2
  • Great answer: I like how you modify a pre-existing completion function. It does what the OP wants -- aside from removing the hyphen. I have seen some software projects where the name of a component does not match its corresponding name in the filesystem so it may not be a typo. Since comments aren’t suitable for code-blocks, I’ve posted my modifications as a separate answer. Nov 20, 2017 at 0:28
  • You’re welcome. A while ago, I followed a couple of tutorials to implement some basic custom completions using compgen and friends but I didn’t get too far with it. I found your approach of hacking the functions provide by bash_completion to be educational and practical. Nov 23, 2017 at 22:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .