bash completion for filename patterns or directories

Solution 1:

If you look at the function _cd() in /etc/bash_completion, you'll see that it appends the trailing slash itself and that complete gets called with the option -o nospace for cd.

You can do the same for xyz, but to have to verify separately if the found match is a directory (if so, append slash) or a file (if so, append space). This should be done in a for loop to process all found matches.

Also, to properly handle paths that contain spaces, you have to set the internal file separator to only newline and escape the spaces. Using IFS=$'\n' in combination with printf %q makes completion work with almost all characters.1 Special care has to be taken to not escape the trailing space.

The following should work:

_xyz ()
{
    local IFS=$'\n'
    local LASTCHAR=' '

    COMPREPLY=($(compgen -o plusdirs -f -X '!*.txt' \
        -- "${COMP_WORDS[COMP_CWORD]}"))

    if [ ${#COMPREPLY[@]} = 1 ]; then
        [ -d "$COMPREPLY" ] && LASTCHAR=/
        COMPREPLY=$(printf %q%s "$COMPREPLY" "$LASTCHAR")
    else
        for ((i=0; i < ${#COMPREPLY[@]}; i++)); do
            [ -d "${COMPREPLY[$i]}" ] && COMPREPLY[$i]=${COMPREPLY[$i]}/
        done
    fi

    return 0
}

complete -o nospace -F _xyz xyz

1 The newline character is the obvious exception here, since it is an internal file separator.

Solution 2:

I think this simple solution works in that it:

  1. Matches directories and files that end in .txt
  2. Handles spaces in the file names
  3. Adds a slash at the end of folder completions with no trailing space
  4. Adds space at the end of a file completion match

The key was passing -o filenames to complete. This was tested on GNU bash 3.2.25 on RHEL 5.3 and GNU bash 4.3.18 on osx

_xyz()
{
  local cur=${COMP_WORDS[COMP_CWORD]}

  local IFS=$'\n'
  COMPREPLY=( $( compgen -o plusdirs  -f -X '!*.txt' -- $cur ) )
}

complete -o filenames -F _xyz xyz