I have a script cmd that runs tasks. cmd lives in multiple directories. Each instance of cmd defines it's own tasks which become the arguments.

I'd like each copy of cmd to generate it's own completions on demand.

./cmd tasks will output a list of newline separated arguments available in each copy of the script.

$./cmd tasks

The following zsh _cmd works as needed when running ./cmd:

#compdef cmd

args=( $(./cmd tasks) )
_arguments '*: :(${args})'

But as the path ./cmd is static in that definition, the completion fails when I run /somedir/cmd or any pathed variant.

There seems to be a lack of something like a $0 for the triggering command available in the _cmd completion definition.

I can see there's certain ways to call _arguments that return control and populate some extra state:

  local context state line state_descr
  _arguments -C '*::arg:->args'

But again, I haven't seen anything like $0 from the incoming command being populated. I haven't created a completion before so using _arguments might not event be the right way about doing this it was just the first workable solution I happened upon when attempting this.

How can I define a zsh completion that generates the available arguments from the command that triggered the completion?

1 Answer 1


The command and its arguments are in the words array. So call $words[1].

Rather than call it directly, go through _call_program, which takes care of a few niceties such as allowing substitutions and setting the locale to C (i.e. English messages, default US date formatting, etc.).

Most of the information you need to write good, complex completion functions is there in the manual, but it's often hard to find the one half-sentence containing the information you need, or even worse, piecing together the information you need from multiple places that each are hard to find. The easiest way to figure something out is often to look for an existing completion function that does something similar, read what it does, and use the manual to get information about the functions and variables it uses. But even that is not ideal because not all the functions shipped with zsh do things right. For example, I first checked _git which is very big and has examples of a lot of things, but for this particular task it does it wrong, calling whatever git is in the $path. So instead here's a good example from _gpg:

allopts=( $(_call_program options $words[1] --dump-options) )

Thus, in your case, if cmd takes multiple arguments, each of which are task names (e.g. you can call cmd dump dump tasks other):

tasks=($(_call_program tasks $words[1] tasks))
_arguments "*:task:($tasks)"

Or, if it's the first argument to cmd that's a task name, and subsequent arguments are that task name, let _arguments do the parsing. It sets the array line to the non-option arguments.

local context state state_descr line
typeset -A opt_args
tasks=($(_call_program tasks $words[1] tasks))
_arguments \
  '--global-option[This is an example global option that goes before the task name]' \
  "1:task:($tasks)" \
case $state in
    … # here $line[1] is the task name
  • Thanks Gilles, on point as always. I hadn't even noticed the multiple arguments issue yet!
    – Matt
    Commented Aug 26, 2022 at 9:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .