Search code examples
bashzshbash-completioncompletionzsh-completion

How do I defer shell completion to another command in bash and zsh?


I am attempting to write a shell script utility that wraps other shell utilities into a single CLI and am trying to get shell completion to work in zsh and bash.

For example, let's say the CLI is named util:

util aws [...args] #=> runs aws
util docker [...args] #=> runs docker
util terraform [...args] #=> runs terraform

What I would like, ideally, is a way in zsh and bash completion to be able to say "complete this subcommand X like other command Y" independently from the implementation of the completion for the wrapped scripts.

Something like:

compdef 'util aws'='aws'
compdef 'util docker'='docker'
compdef 'util terraform'='terraform'

A stretch goal would be to allow for completion of an arbitrary sub-command to a subcommand in another binary:

util aws [...args] #=> completes against `aws`
util ecr [...args] #=> completes against `aws ecr`

Is any of this possible? I've been attempting to emulate the completion scripts of the individual binaries, however there is significant variation in how other completion scripts are written.


Solution

  • I know nothing about zsh, but I can offer a solution for bash. It delegates using the _complete function (which I found following muru's suggestion - good call!).

    The second section of the function provides completions for the util command itself, which I assume here will just be a list of subcommands. You can tailor that to your needs, of course.

    The first section handles the delegation in the case where a full subcommand has been typed, and optionally a target for completion according to the subcommand's completion.

    The function

    _delegate() {
      local cur subs
      cur="${COMP_WORDS[COMP_CWORD]}" # partial word, if any
      subs="ssh aws docker terraform"
      if [[ $COMP_CWORD == 2 ]]; then
        # Two whole words before the cursor - delegate to the second arg
        _command $2
      else
        # complete with the list of subcommands 
        COMPREPLY=( $(compgen -W "${subs}" -- ${cur}) )
      fi
    }
    

    Installation

    njv@pandion:~$ complete -F _delegate util
    

    Demo

    1d [njv@eidolon:~] $ util
    aws        docker     ssh        terraform
    1d [njv@eidolon:~] $ util ssh
    ::1                        gh                         ip6-localhost              ubuntu.members.linode.com
    eidolon                    github.com                 ip6-loopback
    ff02::1                    ip6-allnodes               localhost
    ff02::2                    ip6-allrouters             ubuntu
    1d [njv@eidolon:~] $ util ssh ip6-
    ip6-allnodes    ip6-allrouters  ip6-localhost   ip6-loopback