How to capture all arguments after double dash (--) using argparse

146 views Asked by At

Background

Many command-line utilities provide special handling for all arguments after a double dash (--). Examples:

git diff: All arguments after -- are paths (which can start with -):

git diff [options] -- [path...]

Some wrapper tools use this to pass arbitrary arguments on to the tool they are wrapping -- even arguments which may conflict with their own!

foowrap -a -- -a
             
        │     └───  Passed to wrapped tool
        └─────────  Consumed by wrapper

Problem

How can we accomplish this using argparse?

3

There are 3 answers

1
Jonathon Reinhart On

It seems that argparse (specifically parse_known_args() already supports this natively, although it is not clearly documented:

import argparse

p = argparse.ArgumentParser()
p.add_argument("-a", action="store_true")
p.add_argument("-b", action="store_true")

args, extra = p.parse_known_args()
print(f"args: {args}")
print(f"extra: {extra}")
$ python3 known.py -a -- -a -b
args: Namespace(a=True, b=False)
extra: ['--', '-a', '-b']

We can see that:

  • The -a after -- appears in extra
  • The -b after -- appears in extra and does not set b=True in the namespace

If you want to ignore the -- separator, it can be filtered out:

extra = [a for a in extra if a != "--"]

I actually discovered this while attempting to code up a "what doesn't work" example. Because it is not obvious to me from the documentation I decided to ask+answer anyway.

4
Unmitigated On

You can add an argument with nargs=argparse.REMAINDER to capture the rest of the arguments.

parser = argparse.ArgumentParser()
# other arguments...
parser.add_argument('remaining', nargs=argparse.REMAINDER)
args = parser.parse_args()
# use args.remaining...

nargs='*' can also be used, which would not include the leading -- in the list.

0
Aidan Gallagher On

I've just stumbled across this problem. I solved it by removing the "--" arguments before argparse see's them.

# Extract underlying ("--") args before argparse parsing
for idx, arg in enumerate(argv):
    if arg == "--":
        wrapper_program_args = argv[:idx]
        underlying_tool_args = argv[idx + 1:]
        break
else:
    wrapper_program_args = argv
    underlying_tool_args = None

args = parser.parse_args(wrapper_program_args)
args.underlying_tool_args = underlying_tool_args

Optionally, if you want the help message to include the "--" option you may supply a custom formatter.

# Customer formatter required to print help message for "--" option.
class CustomHelpFormatter(argparse.HelpFormatter):
    def format_help(self):
        original_help = super().format_help()
        return original_help + "  --\t\t\tArguments to pass to underlying tool. \n"

parser = argparse.ArgumentParser(formatter_class=CustomHelpFormatter)