Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I'm trying to get a bash completion script set up and having some trouble.

I would like to set it up so the completions listed are either files matching a particular extension, or directories (which may or may not contain files of that extension).

The trouble I'm having is that the only way I can get the completions to contain files and directories is by using something like -o plusdirs -f -X '!*.txt', but when I let bash complete one of the directories, it just adds a space onto the end, rather than a slash.

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

  #COMPREPLY=( $( compgen -f -X '!*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -f -G '*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -o filenames -f -X '!*.txt' -- $cur ) )
  #COMPREPLY=( $( compgen -o dirnames  -f -X '!*.txt' -- $cur ) )
  COMPREPLY=( $( compgen -o plusdirs  -f -X '!*.txt' -- $cur ) )
  return 0
}

complete -F _xyz xyz

I've tried all the commented-out lines too, but they don't even expand the directories.

For testing, I've been running this in a directory with one .txt file and one directory "dir" (with a .txt file inside it, though that doesn't matter yet). Typing xyz <TAB> with this function lists the directory and the .txt file, but typing xyz d<TAB> expands to xyz dir (well, with a space after "dir").

share|improve this question

2 Answers 2

up vote 4 down vote accepted

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.

share|improve this answer
    
This works great (though it's too bad it's not built in). Thanks! –  Rob I Mar 13 '13 at 13:53

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
share|improve this answer
    
Yes that does seem to work great. Much simpler, thanks for catching the important argument! –  Rob I Aug 28 at 14:03

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.