Skip to content

Usage#

There are two ways of using shtab:

  • CLI Usage: shtab's own CLI interface for external applications
    • may not require any code modifications whatsoever
    • end-users execute shtab your_cli_app.your_parser_object
  • Library Usage: as a library integrated into your CLI application
    • adds a couple of lines to your application
    • argument mode: end-users execute your_cli_app --print-completion {bash,zsh,tcsh,fish,powershell}
    • subparser mode: end-users execute your_cli_app completion {bash,zsh,tcsh,fish,powershell}

CLI Usage#

The only requirement is that external CLI applications provide an importable argparse.ArgumentParser object (or alternatively an importable function which returns a parser object). This may require a trivial code change.

Then simply put the output of shtab --shell=your_shell your_cli_app.your_parser_object somewhere your shell looks for completions.

Below are various examples of enabling shtab's own tab completion scripts.

Info

If both shtab and the module it's completing are globally importable, eager usage is an option. "Eager" means automatically updating completions each time a terminal is opened, and likely should not use the -u, --error-unimportable flag.

Terminal start might be slow if scripts are very complex.

shtab -u --shell=bash shtab.main.get_main_parser \
  | sudo tee /etc/bash_completion.d/shtab

Eager

# Install locally
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  >> ~/.bash_completion

# Install system-wide (pkg-config bash-completion --variable=compatdir)
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  | sudo tee /etc/bash_completion.d/shtab

# Install system-wide (pkg-config bash-completion --variable=completionsdir)
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  | sudo tee /usr/share/bash-completion/completions/shtab

Info

zsh requires completion script files to be named _{EXECUTABLE} (with an underscore prefix).

# note the underscore `_` prefix
shtab -u --shell=zsh shtab.main.get_main_parser \
  | sudo tee /usr/local/share/zsh/site-functions/_shtab

Eager

Place the generated script somewhere in $fpath. For example, add these lines to the top of ~/.zshrc:

mkdir -p ~/.zsh/completions
fpath=($fpath ~/.zsh/completions)  # must be before `compinit` lines
shtab --shell=zsh shtab.main.get_main_parser -o ~/.zsh/completions/_shtab
shtab -u --shell=tcsh shtab.main.get_main_parser \
  | sudo tee /etc/profile.d/completion_shtab.csh

Eager

# Install locally
echo 'shtab --shell=tcsh shtab.main.get_main_parser | source /dev/stdin' \
  >> ~/.cshrc

# Install system-wide
echo 'shtab --shell=tcsh shtab.main.get_main_parser | source /dev/stdin' \
  | sudo tee /etc/profile.d/completion_shtab.csh
# Install locally
shtab -u --shell=fish shtab.main.get_main_parser \
  -o ~/.config/fish/completions/shtab.fish

# Install system-wide (pkg-config fish --variable=completionsdir)
shtab -u --shell=fish shtab.main.get_main_parser \
  | sudo tee /usr/share/fish/vendor_completions.d/shtab.fish

Info

PowerShell 7+ (pwsh) is fully supported, while Windows PowerShell 5 (powershell.exe) is only partially supported.

New-Item -Path ~\.config\powershell\completions -ItemType Directory -Force
shtab --shell=powershell shtab.main.get_main_parser --error-unimportable `
  | Out-File -FilePath ~\.config\powershell\completions\shtab.ps1
# Add to $PROFILE:
. ~\.config\powershell\completions\shtab.ps1

Eager:

Add the following to your PowerShell profile ($PROFILE):

shtab --shell=powershell shtab.main.get_main_parser `
  | Out-String | Invoke-Expression

Or save to a file and dot-source it from your profile:

New-Item -Path ~\.config\powershell\completions -ItemType Directory -Force
shtab --shell=powershell shtab.main.get_main_parser `
  | Out-File -FilePath ~\.config\powershell\completions\shtab.ps1
# Add to $PROFILE:
. ~\.config\powershell\completions\shtab.ps1

argparse#

Any existing argparse-based scripts should be supported with minimal effort. For example, starting with this existing code:

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env python
import argparse

def get_main_parser():
    parser = argparse.ArgumentParser(prog="MY-PROG", ...)
    parser.add_argument(...)
    parser.add_subparsers(...)
    ...
    return parser

if __name__ == '__main__':
    parser = get_main_parser()
    args = parser.parse_args()
    ...

Assuming this code example is installed in MY_PROG.cli, simply run:

shtab --shell=bash -u MY_PROG.cli.get_main_parser \
  | sudo tee /etc/bash_completion.d/MY-PROG
shtab --shell=zsh -u MY_PROG.cli.get_main_parser \
  | sudo tee /usr/local/share/zsh/site-functions/_MY-PROG
shtab --shell=tcsh -u MY_PROG.cli.get_main_parser \
  | sudo tee /etc/profile.d/MY-PROG.completion.csh
shtab --shell=fish -u MY_PROG.cli.get_main_parser \
  | sudo tee /usr/share/fish/vendor_completions.d/MY-PROG.fish
New-Item -Path ~\.config\powershell\completions -ItemType Directory -Force
shtab --shell=powershell -u MY_PROG.cli.get_main_parser `
  | Out-File -FilePath ~\.config\powershell\completions\MY-PROG.ps1
. ~\.config\powershell\completions\MY-PROG.ps1

click#

Speedup click's completions (and get support for more shell types) by changing from e.g. _MY_PROG_COMPLETE=bash_source MY-PROG to shtab MY_PROG.cli.main --prog MY-PROG -s bash

Library Usage#

Tip

For more, see:

Complex projects with subparsers and custom completions for paths matching certain patterns (e.g. --file=*.txt or --branch=$(git branch)) are fully supported (see examples/customcomplete.py or even treeverse/dvc:commands/completion.py for example).

Add direct support to scripts for a little more configurability:

pathcomplete.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
import argparse, shtab

parser = argparse.ArgumentParser(prog="pathcomplete")
shtab.add_argument_to(parser, ["-s", "--print-completion"])  # magic!
# file & directory tab complete
parser.add_argument("file", nargs="?").complete = shtab.FILE
parser.add_argument("--dir", default=".").complete = shtab.DIRECTORY
parser.add_argument("--config")\
  .complete = shtab.glob('*.toml', '*.yml', '*.yaml', '*.json')
# WARNING: shtab.cmd is (re)run by your shell on each tab press, so could be slow
parser.add_argument("--branch", help="git branch from current workdir")\
  .complete = shtab.cmd("git branch")

def main(args=None):
    args = parser.parse_args(args=args)
    print(f"received <file>={args.file} --dir={args.dir}"
          f" --config={args.config} --branch={args.branch}")
if __name__ == '__main__':
    main()

Simply use argopt to create a parser object from docopt syntax:

docopt_greeter.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
"""Greetings and partings.

Usage:
  greeter [options] [<you>] [<me>]

Options:
  -g, --goodbye  : Say "goodbye" (instead of "hello")

Arguments:
  <you>  : Your name [default: Anon]
  <me>  : My name [default: Casper]
"""
import argopt, shtab
parser = argopt.argopt(__doc__)
shtab.add_argument_to(parser, ["-s", "--print-completion"])  # magic!
def main(args=None):
    args = parser.parse_args(args=args)
    msg = "k thx bai!" if args.goodbye else "hai!"
    print(f"{args.me} says '{msg}' to {args.you}")
if __name__ == '__main__':
    main()
click_process.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env python
import click, shtab.click

@click.command('click-process')
@shtab.click.option() # magic!
@click.argument('out-dir', type=click.Path(file_okay=False), required=False)
@click.option('--config', type=click.File(), help="Config file.")
@click.option('-q', '--quiet', is_flag=True, help="Suppress output.")
def process(config, out_dir, quiet):
    """Click example CLI with shtab."""
    if not quiet:
        print(f"Reading from {config} and writing to {out_dir}")

if __name__ == '__main__':
    process()
click_subcommand.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
import click, shtab.click

@click.group('click-subcommand')
def main():
    """Main (root) CLI group command."""

@main.command()
@click.argument('out-dir', type=click.Path(file_okay=False))
@click.option('--config', type=click.File(), help="Config file.")
@click.option('-q', '--quiet', is_flag=True, help="Suppress output.")
def process(config, out_dir, quiet):
    """Click example CLI with shtab."""
    if not quiet:
        print(f"Reading from {config} and writing to {out_dir}")

shtab.click.add_command_to(main) # magic!

if __name__ == '__main__':
    main()