2

I'm writing a bash to process files in a directory. I have some base (quite long) path in which to process. I want to specify via option and argument a sub-path and make it autocompletable. I mean something like this:

#!/bin/bash

dir=~/path/to/base/dir

while getopts ":d:" opt; do
    case $opt in
      d)
         dir=$dir/$OPTARG
         ;;
      #invalid input handling
    esac
done

But in this implementation the argument is not autocompletable and I have to type all the name of a sub-directory myself (Tab doesn't work).

Is there a way to achieve this in bash?

1

Yes, it is possible to implement path-completion from a custom base-directory specified by a command-line option-flag. Here is a small example illustrating how to accomplish this. First I'm going to slightly modify your example script to make the demonstration slightly more interesting (i.e. to produce output):

#!/bin/bash

# echo_path.sh
#
#    Echoes a path.
#

# Parse command-line options
while getopts ":d:" opt; do

    # Use '-d' to specify relative path
    case "${opt}" in
    d)
        directory="${OPTARG}"
        ;;
    esac
done

# Print the full path
echo "$(readlink -f ${directory})"

This is essentially the same as your example script, but it prints out the given argument.

Next we need to write a function to be called by the Bash programmatic-completion system. Here is a script which defines such a function:

# echo_path_completion.bash

# Programmatic path completion for user specified file paths.

# Define a base directory for relative paths.
export BASE_DIRECTORY=/tmp/basedir

# Define the completion function
function _echo_path_completion() {

    # Define local variables to store adjacent pairs of arguments
    local prev_arg;
    local curr_arg;

    # If there are at least two arguments then we have a candidate
    # for path-completion, i.e. we need the option flag '-d' and
    # the path string that follows it.
    if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then

        # Get the current and previous arguments from the command-line
        prev_arg="${COMP_WORDS[COMP_CWORD-1]}";
        curr_arg="${COMP_WORDS[COMP_CWORD]}";

        # We only want to do our custom path-completion if the previous
        # argument is the '-d' option flag
        if [[ "${prev_arg}" = "-d" ]]; then

            # We only want to do our custom path-completion if a base
            # directory is defined and the argument is a relative path
            if [[ -n "${BASE_DIRECTORY}" && ${curr_arg} != /* ]]; then

                # Generate the list of path-completions starting from BASE_DIRECTORY
                COMPREPLY=( $(compgen -d -o default -- "${BASE_DIRECTORY}/${curr_arg}") );

                # Don't append a space after the command-completion
                # This is so we can continue to apply completion to subdirectories
                compopt -o nospace;

                # Return immediately
                return 0;
            fi
        fi
    fi

    # If no '-d' flag is given or no base directory is defined then apply default command-completion
    COMPREPLY=( $(compgen -o default -- "${curr_arg}") );
    return 0;
}

# Activate the completion function
complete -F _echo_path_completion echo_path

Now let's source our completion script:

source echo_path_completion.bash

Let's make our script executable and move it to somewhere in our PATH:

chmod +x echo_path.bash
mv -i echo_path.bash /usr/local/bin

And finally, let's add an alias for our script which doesn't have a file extension:

alias echo_path=echo_path.bash

Now if you enter echo -d and hit the tab button then you should get file-path completion starting from the BASE_DIRECTORY. To test this out you could try the following:

mkdir -p ${BASE_DIRECTORY}
mkdir -p "${BASE_DIRECTORY}/file"{1..5}

When you hit tab you should get the following list of completions:

user@host:~$ echo_path -d
user@host:~$ echo_path -d /tmp/basedir/file
/tmp/basedir/file1  /tmp/basedir/file2  /tmp/basedir/file3  /tmp/basedir/file4  /tmp/basedir/file5

Notice that after the first tab the string is converted to an absolute path. You can change this if you want, but I think this is might be the preferable behavior.

Here are some references you can consult for more information.

For official references, take a look at following sections of the Bash manual:

Also look at the Advanced Bash Scripting Guide from the Linux Documentation Project:

For a quick introduction to some features of programmatic completion in Bash, see this post from "The Geek Stuff" site:

There are also several related StackOverflow posts that you might find useful:

  • @st-antario Wow! You accepted that really quickly. Did you actually try it out and verify that everything works as expected? – igal Nov 5 '17 at 22:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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