1

Is there a way to do bash completion of a file path (like the ls program), but only for a certain argument? I've been puzzling over the documentation: https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html#Programmable-Completion-Builtins

I'd like to use the -F option to setup my own function, however I only want file path completion for one of the sub-commands of my program.

Note that it is straightforward to complete using compgen -f, however this returns a list of complete paths, e.g.:

# compgen -f /bin/
/bin/[
/bin/bash
/bin/cat
/bin/chmod
/bin/cp
/bin/csh
<snip>

However ls completes like this:

# ls /bin/
[*         cat*       cp*        date*      df*        ed*
hostname*  ksh*       link*      ls*        mv*        ps*        
<snip>

I believe there is only one COMPREPLY list sent back to bash from a completion routine, and there seems to be no way to distinguish between the full program argument and the portion of it that gets displayed by bash. Unless there is some trick I can use.

Unfortunately the full path strings can very quickly become unwieldy, particularly for large programming projects with lots of files in the directories.

Keeely
  • 895
  • 9
  • 21

1 Answers1

1

Like this:

foo_completion.sh

_complete_foo_ () {
    # Current word
    local word=${COMP_WORDS[$COMP_CWORD]}
    # Previous word
    local pword=${COMP_WORDS[$((COMP_CWORD-1))]}

    if [ ${COMP_CWORD} -eq 1 ] ; then
        # The sub commands bar and baz are available
        COMPREPLY=( $(compgen -W "bar baz" -- "${word}" ))
    elif [ "${pword}" = "bar" ] ; then
        # (Only) For bar we do default filename completion (like ls)
        compopt -o default
    elif [ "${pword}" = "baz" ] ; then
        # For baz we do something else
        COMPREPLY=( $(compgen -W "something else" -- "${word}" ))
    fi

    return 0
}

complete -F _complete_foo_ foo

Now source the file:

source foo_completion.sh

... and try the completion

foo <tab><tab>      # bar baz
foo bar <tab><tab>  # file and dir name completion like ls
foo baz <tab><tab>  # something else
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Thanks for the effort, but that doesn't complete in the same way as ls. Try it with /bin, you get the full paths to the completion, I want the same completion style as ls uses, offering only the relative path elements as completion choices. Sorry that wasn't clear. – Keeely Jan 18 '19 at 09:11
  • Shouldn't be a problem to fix. Let me search for the_right_ way. Btw, if you are curious, just look at the completion script for `ls`. (That's what I'm doing now) – hek2mgl Jan 18 '19 at 10:20
  • Ah, OK. When I type 'complete' it was not listed. That's why I thought there was something special about it. – Keeely Jan 18 '19 at 11:35
  • Sorry, got interrupted. Indeed, there seems to be something special about `ls` completions.. further investigating ... – hek2mgl Jan 18 '19 at 12:06
  • I think ls is just using the default behaviour. You can just type `not_existing_cmd to have the same behaviour, right? – hek2mgl Jan 18 '19 at 12:08
  • Yes, I believe not_existing_cmd has the default behaviour applied, namely the equivalent of `-o default` supplied to the complete command. You can have that as a fallback for when nothing else matches and maybe that's how ls is setup. You can't do that only for a specific argument position. The above may be the best I can do. – Keeely Jan 18 '19 at 14:09
  • I'll keep trying here. I'm atm still optimistic that we'll find what you need – hek2mgl Jan 18 '19 at 14:11
  • Hehe :) just ask stackoverflow: https://stackoverflow.com/questions/14513571/how-to-enable-default-file-completion-in-bash – hek2mgl Jan 18 '19 at 14:13
  • There is nothing there that seems useful unfortunately. What if I have a sub-command taking only one optional option, say `-p` and -p is already on the command-line? I don't want it to suggest any options at all, so return empty list, but that will get interpreted as a filepath match and prompt from the current directory. – Keeely Jan 18 '19 at 15:09
  • I changed my answer and use `compopt -o default` now. can you verify that it is working for you? – hek2mgl Jan 18 '19 at 18:39
  • Regrettably I can't install Bash 4 so cannot test or use this, however from the manual it does look like that should work so I will accept the answer. Well done for figuring that out. – Keeely Jan 18 '19 at 21:27