57

How to get shell tab completion cooperating with argparse in a Python script?

#!/usr/bin/env python
import argparse

def main(**args):
    pass

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', choices=['spam', 'eggs'])
    parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])
    args = parser.parse_args()
    main(**vars(args))

With an executable flag set on the .py file, the expected results should be something like:

$ ./example.py sp<tab>             
   ->  completes to "./example.py spam"
$ ./example.py --op<tab> 
   ->  completes to "./example.py --optional"
$ ./example.py --optional b<tab>
   ->  completes to "./example.py --optional bar"
$ ./example.py --optional f<tab>   
   ->  completes to "./example.py --optional foo"
       and, additionally, prints  "foo1  foo2"  choices on stdout on a new line
wim
  • 338,267
  • 99
  • 616
  • 750
  • 6
    This has a little background information: http://stackoverflow.com/questions/9568611/how-does-argparse-and-the-deprecated-optparse-respond-to-tab-keypress-after – Alex L Jan 30 '13 at 05:56

2 Answers2

86

Have a look at argcomplete by Andrey Kislyuk.

Install it with:

pip install argcomplete

Import the module and add one line in your source before calling parser.parse_args():

#!/usr/bin/env python

import argparse as ap
import argcomplete

def main(**args):
  pass

if __name__ == '__main__':
  parser = ap.ArgumentParser()
  parser.add_argument('positional', choices=['spam', 'eggs'])
  parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])
  argcomplete.autocomplete(parser)
  args = parser.parse_args()
  main(**vars(args))

and to make sure that bash knows about this script, you use

eval "$(register-python-argcomplete your_script)"

you should put that line in your ~/.bashrc or follow argcomplete's docs and activate 'global' completion.

After that you completion works as requested.

The way this works is that the eval line creates a function _python_argcomlete which is registered using complete. (Run register-python-argcomplete your_script to just have a look at what gets eval-ed into bash). The autocomplete function looks for environment variables set by the bash completion mechanism to see if it needs to act. If it acts, it exits the program. If it doesn't act, this is a normal call to the program that function does nothing and the normal flow of the program continues.

Morgoth
  • 4,935
  • 8
  • 40
  • 66
Anthon
  • 69,918
  • 32
  • 186
  • 246
  • 5
    If you enable "global completion", you need to place the comment `# PYTHON_ARGCOMPLETE_OK` at the beginning of the Python file (e.g., as the second line, right after the `#!/usr/bin/env python`), or the autocompletion will not work. I suggest to edit the code in the answer, adding the aforementioned comment line. – Alberto Pettarin Oct 19 '14 at 19:50
  • @AlbertoPettarin I explicitly did not mention global completion, as it requires additional set up and incurs a general performance hit on tab completion (because of opening multiple files searching for a potential `PYTHON_ARGCOMPLETE_OK`. The global tab completion and the requirements are mentioned in the link in the article (and now in your comment), that is good enough. – Anthon Oct 20 '14 at 07:46
  • There's also `activate-global-python-argcomplete` available (I think this may be newish, as I don't recall it being there last time I looked). – Andy Hayden Feb 25 '15 at 04:15
  • I have a big project, so if this will help me to autogenerate bash completion script, or in anyway so that I'don't have to maintain the script for command completion manually? – shiminsh Apr 23 '16 at 05:23
  • @shiminsh That is its intention, but it depends a bit whether this will work for you. argcomplete starts the application to dynamically generate the completion information and that can take time depending on how your application is structured. For me the delay this caused got annoying enough to not use argcomplete in most of my projects. But if you already use `argparse` adding argcomplete to try it out is really little work. – Anthon Apr 23 '16 at 06:07
  • Does this still work if you try to use `python your_script`? It seems to only work for me if I `chmod +x ./your_script` and then try to run it with `./your_script`. If this is expected behaviour, I think this answer should mention this. – Multihunter Oct 15 '19 at 04:50
  • Updated link for argcomplete documentation: https://kislyuk.github.io/argcomplete/ – Marv May 02 '20 at 22:41
  • Should I test is using ` ./minimal.py [TAB]`? – alper Mar 07 '22 at 13:05
  • @alper I think that depends on your shell and/or shell settings. I am currently not using argcomplete, so I cannot help much more – Anthon Mar 07 '22 at 16:26
  • I hoped PYTHON_ARGCOMPLETE_OK would work for me, because overhead is okay in my application. Weirdly, it works to complete "filename.py" when I hit tab after `python filena`, but not after `python -i filena`. I guess somehow argparse (which is looking for "-i") is interfereing with the global flag. So I had to go with the answer that relies on `import argcomplete`. Edit -- actualy this weirdly only works to autocomplete directories, not filenames – cxrodgers Feb 24 '23 at 03:25
  • In `zsh` when I press `TAB` this solution shows additional arguments: `ALTERNATE_EDITOR LESS_TERMCAP_ue _ARGCOMPLETE LESS_TERMCAP_us _ARGCOMPLETE_COMP_WORDBREAKS LOGNAME _ARGCOMPLETE_SUPPRESS_SPACE LS_COLORS BAT_PAGER LSCOLORS BROWNIE_LIB LSP_USE_PLISTS CLICOLOR MANPAGER COLUMNS NVM_BIN COMP_CWORD NVM_CD_FLAGS COMP_LINE NVM_DIR COMP_POINT NVM_INC FZF_CTRL_R_OPTS PS1` ... is it normal? – alper Jun 19 '23 at 12:51
5

For auto-complete to work you need a bash function to generate the possible options, and then you need to run complete -F <function_name> <program_name>

The best way of doing this is to have the program generate the completion function based on it's own parsing algorithm to avoid duplication. However, at a quick glance on argparse, I could not find a way to access it's internal structure, but I suggest you look for it.

Here is a bash function that will do for the above program:

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

    case "$prev" in
    --optional ) 
        COMPREPLY=( $(compgen -W "foo1 foo2 bar" -- $cur) )
        return 0
        ;;
    *)
        COMPREPLY=( $(compgen -W "--optional spam eggs" -- $cur) )
        return 0
        ;;
    esac
}
Sorin
  • 5,201
  • 2
  • 18
  • 45
  • This program is just an example. It's possible to list all options for certain program, but not for library. Something like `optcomplete` equivalent for `argparse` will do the job, but other (including partial) solutions are welcome too. – Denis Otkidach Mar 06 '13 at 14:20