4

What I want to do

I want to migrate a script from docopt to argparse. My script takes a positional argument for input file(s), then calls another command-line tool. It also takes an optional argument option that allows the user to specify additional arguments and options that will be passed to that command-line tool.

So, I allow the user to call:

my_program.py INPUT --extra-options "-foo bar -baz"

These --extra-options are then added to the other program I'm calling from inside my script, e.g.:

other_program -foo bar -baz INPUT

Problem

Now, having migrated the argument parsing to argparse, this does not work anymore:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("input", nargs="+")
parser.add_argument("-e", "--extra-options", type=str)
parser.add_argument("-f", "--force", default=False, action="store_true")
args = parser.parse_args()

When I call the same command, it says:

error: argument -e/--extra-options: expected one argument

But -e was given one argument (-foo bar -baz, to be precise)! My expectation was that the shell would group the argument based on the double-quotes, and argparse would understand that.

Now, in the manual, we find:

positional arguments may only begin with - if they look like negative numbers and there are no options in the parser that look like negative numbers … If you have positional arguments that must begin with - and don’t look like negative numbers, you can insert the pseudo-argument '--' which tells parse_args() that everything after that is a positional argument

But this does not apply here – I want -e to just parse the next word from the shell as its argument. The problem in this case is the other option -f/--force – as soon as I remove it, or give it some other name (e.g., -x), the arguments are parsed correctly. Apparently the f from -foo clashes with the -f/--force option.

What I've tried

I looked into the answers from this question, but none of them solves the issue.

  • I can't force users to specify this option differently (e.g., starting with a space, using =, ending with a space, …), as it would break backwards compatibility with existing scripts.
  • I don't want to use a deprecated module (optparse).
  • The parse_known_args solution does not work in this case.
  • The nargs=argparse.PARSER solution does not work either.
  • Removing nargs did not help either.

How can I get argparse to deal with argument values starting with - in this case?

slhck
  • 36,575
  • 28
  • 148
  • 201
  • @MartijnPieters Thanks – I had a look and updated my question. Unfortunately, none of the answers there really helps in this case. I know it may just be a bug or lack of feature in `argparse`. (I'd accept "It's currently impossible" as an answer.) – slhck Aug 07 '17 at 07:59
  • That's what the other answers tell you; it is a bug in argparse. The workaround is to use `-e="...."` or `--extra-options="..."` (note the `=` sign). – Martijn Pieters Aug 07 '17 at 08:00
  • This is still a duplicate; that there is no satisfying answer for you on the other post is regrettable, but not something a re-hash of the same terrain is going to resolve. You could try adding a bounty to the other post to see if anyone has got any better solutions. – Martijn Pieters Aug 07 '17 at 08:02
  • Sure, I understand. Thanks for the help! – slhck Aug 07 '17 at 08:04
  • What's wrong with `argparse.PARSER` (or `argparse.REMAINDER')? – hpaulj Aug 07 '17 at 16:53
  • 1
    In a sense this goes beyond the earlier SO and the 9334 bug/issue. As long as `"-f..."` or `"--foo"` match a defined flag, `argparse` will interpret it as a flag, not an argument for the previous flag. Quotes don't matter, except to keep the shell from splitting words. That behavior is built into the parsing algorithm. – hpaulj Aug 07 '17 at 17:07
  • @hpaulj I couldn't get it to work with `argparse.PARSER` … could you maybe give me a concrete example? It's unfortunate that argparse is trying to be too clever here. – slhck Aug 07 '17 at 17:20
  • I don't think I have a enough rep in this area to reopen it alone; so won't try to write an answer. But I think the only complicating thing is that it returns a list value, not just a string. – hpaulj Aug 07 '17 at 20:43
  • @hpaulj Could you just post the respective line of code in a comment? I tried setting that option for the affected argument but it didn't work. – slhck Aug 07 '17 at 21:28
  • I added to my answer in the `duplicate`. – hpaulj Aug 07 '17 at 22:54

0 Answers0