Rilcon42
Rilcon42

Reputation: 9765

autocomplete in bash script

I am trying to autocomplete a folder name in a bash script. If I enter a complete folder name everything works, but I don't know how to autocomplete the name. Any ideas?

repo() {
 cd ~/Desktop/_REPOS/$1
}

I tried reading this SO post for ideas, but got lost quickly because I am still fairly new to bash.

GOAL (with the repo function above included in my bashrc):

>ls ~/Desktop/_REPOS/
folder1
hellofolder
stackstuff

>repo sta[TAB] fills in as: repo stackstuff

Upvotes: 1

Views: 14762

Answers (2)

Benjamin W.
Benjamin W.

Reputation: 52112

I see two options to get what you want, i.e., path autocompletion for a custom cd command, if I understand correctly.

  1. You can add the parent directory, ~/Desktop/_REPOS, to your CDPATH environment variable:

    CDPATH="$CDPATH":"$HOME"/Desktop/_REPOS
    

    Now, from any directory, you can type cd SpaceTab, and in addition to the subdirectories of your current directory, all the directories in ~/Desktop/_REPOS will show up. (Which is also the drawback of this method: more clutter.)

  2. You can add a completion function to your .bashrc. The way you've started, you want the basenames of all the directories in ~/Desktop/_REPOS. To get autocompletion for directories, we can use the compgen -d builtin:

    $ compgen -d "$HOME"/Desktop/_REPOS/
    /home/me/Desktop/_REPOS/folder1
    /home/me/Desktop/_REPOS/folder2
    /home/me/Desktop/_REPOS/stackstuff
    /home/me/Desktop/_REPOS/hellofolder
    

    This returns the names of all subdirectories. It reduces to fewer candidates when the path is more specific:

    $ compgen -d "$HOME"/Desktop/_REPOS/f
    /home/me/Desktop/_REPOS/folder1
    /home/me/Desktop/_REPOS/folder2
    

    To remove everything but the basenames, we use shell parameter expansion, like this:

    $ arr=(/path/to/dir1 /path/to/dir2)
    $ echo "${arr[@]##*/}"
    dir1 dir2
    

    ##*/ in the parameter expansion removes the longest possible match of */ from each element of arr, i.e., leaves only what's after the last forward slash – exactly what we want.

    Now we put this together and into a function:

    _comp_repo () {
        # Get list of directories
        # $2 is the word being completed
        COMPREPLY=($(compgen -d "$HOME"/Desktop/_REPOS/"$2"))
    
        # Reduce to basenames
        COMPREPLY=("${COMPREPLY[@]##*/}")
    }
    

    This goes into your .bashrc, together with the instruction to use it for autocompletion with repo:

    complete -F _comp_repo repo
    

    Notice that your repo function should quote the $1 argument to make sure it handles directory names with special characters (spaces, tabs...) properly:

    repo () {
        cd ~/Desktop/_REPOS/"$1"
    }
    

The number of times you have hit Tab depends on readline settings such as show-all-if-ambiguous.


References

Upvotes: 8

Euro
Euro

Reputation: 638

Actually you can borrow quite a bit of code from geirha's answer:

# this is a custom function that provides matches for the bash autocompletion
_repo_complete() {
    local file
    # iterate all files in a directory that start with our search string
    for file in ~/Desktop/_REPOS/"$2"*; do
        # If the glob doesn't match, we'll get the glob itself, so make sure
        # we have an existing file. This check also skips entries
        # that are not a directory
        [[ -d $file ]] || continue

        # add the file without the ~/Desktop/_REPOS/ prefix to the list of 
        # autocomplete suggestions
        COMPREPLY+=( $(basename "$file") )
    done
}

# this line registers our custom autocompletion function to be invoked 
# when completing arguments to the repo command
complete -F _repo_complete repo

The for loop iterates all files in a directory that start with the string given as second argument to the _repo_complete function (this is the string to be autocompleted).

Add the code to your .bashrc and it should work!

Upvotes: 12

Related Questions