Intrepret parameter starting with a hyphen as a string [duplicate] - python

I would like to parse a required, positional argument containing a comma-separated list of integers. If the first integer contains a leading minus ('-') sign, argparse complains:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional')
parser.add_argument('-t', '--test', action='store_true')
opts = parser.parse_args()
print opts
$ python example.py --test 1,2,3,4
Namespace(positional='1,2,3,4', test=True)
$ python example.py --test -1,2,3,4
usage: example.py [-h] [-t] positional
example.py: error: too few arguments
$ python example.py --test "-1,2,3,4"
usage: example.py [-h] [-t] positional
example.py: error: too few arguments
I've seen people suggest using some other character besides - as the flag character, but I'd rather not do that. Is there another way to configure argparse to allow both --test and -1,2,3,4 as valid arguments?

You need to insert a -- into your command-line arguments:
$ python example.py --test -- -1,2,3,4
Namespace(positional='-1,2,3,4', test=True)
The double-dash stops argparse looking for any more optional switches; it's the defacto standard way of handling exactly this use case for command-line tools.

From the documentation:
The parse_args() method attempts to give errors whenever the user has
clearly made a mistake, but some situations are inherently ambiguous.
For example, the command-line argument -1 could either be an attempt
to specify an option or an attempt to provide a positional argument.
The parse_args() method is cautious here: 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:
Since -1,2,3,4 does not look like a negative number you must "escape" it with the -- as in most *nix systems.
An other solution would be to use nargs for the positional and pass the numbers as space separated:
#test.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='*') #'+' for one or more numbers
print parser.parse_args()
Output:
$ python test.py -1 2 3 -4 5 6
Namespace(positional=['-1', '2', '3', '-4', '5', '6'])
A third way to obtain what you want is to use parse_known_args instead of parse_args.
You do not add the positional argument to the parser and parse it manually instead:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--test', action='store_true')
parsed, args = parser.parse_known_args()
print parsed
print args
Result:
$ python test.py --test -1,2,3,4
Namespace(test=True)
['-1,2,3,4']
This has the disadvantage that the help text will be less informative.

Related

Pass a specific argument in python and ignore the rest [duplicate]

Optparse, the old version just ignores all unrecognised arguments and carries on. In most situations, this isn't ideal and was changed in argparse. But there are a few situations where you want to ignore any unrecognised arguments and parse the ones you've specified.
For example:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', dest="foo")
parser.parse_args()
$python myscript.py --foo 1 --bar 2
error: unrecognized arguments: --bar
Is there anyway to overwrite this?
Replace
args = parser.parse_args()
with
args, unknown = parser.parse_known_args()
For example,
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
args, unknown = parser.parse_known_args(['--foo', 'BAR', 'spam'])
print(args)
# Namespace(foo='BAR')
print(unknown)
# ['spam']
You can puts the remaining parts into a new argument with parser.add_argument('args', nargs=argparse.REMAINDER) if you want to use them.
Actually argparse does still "ignore" _unrecognized_args. As long as these "unrecognized" arguments don't use the default prefix you will hear no complaints from the parser.
Using #anutbu's configuration but with the standard parse.parse_args(), if we were to execute our program with the following arguments.
$ program --foo BAR a b +cd e
We will have this Namespaced data collection to work with.
Namespace(_unrecognized_args=['a', 'b', '+cd', 'e'], foo='BAR')
If we wanted the default prefix - ignored we could change the ArgumentParser and decide we are going to use a + for our "recognized" arguments instead.
parser = argparse.ArgumentParser(prefix_chars='+')
parser.add_argument('+cd')
The same command will produce
Namespace(_unrecognized_args=['--foo', 'BAR', 'a', 'b'], cd='e')
Put that in your pipe and smoke it =)
nJoy!

how to allow -non_number as arguments in python argparse

python test.py --arg -foo -bar
test.py: error: argument --arg: expected at least one argument
python test.py --arg -8
['-8']
How do I allow -non_number to work with argparse?
Is there a way to disable short arguments?
Call it like this:
python test.py --arg='-foo'
To allow specifying multiple:
parser.add_argument('--arg', action='append')
# call like python test.py --arg=-foo --arg=-bar
I think you're looking for the nargs parameter to argparser.
parser.add_argument('--arg', nargs='?')
At the moment, --arg is interpreting the value '-8' as the input, whereas it thinks '-f' (with parameters 'oo') is a new argument.
Alternatively, you could use action='store_true', which will represent the presence or absence of the argument with a boolean.
parser.add_argument('--arg', action='store_true')
https://docs.python.org/3/library/argparse.html#nargs

How to pass and parse a list of strings from command line with argparse.ArgumentParser in Python?

I want to pass a list of names into my program written in Python from console. For instance, I would like to use a way similar to this (I know it shouldn't work because of bash):
$ python myprog.py -n name1 name2
So, I tried this code:
# myprog.py
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-n', '--names-list', default=[])
args = parser.parse_args()
print(args.names_list) # I need ['name1', 'name2'] here
That led to the error:
usage: myprog.py [-h] [-n NAMES_LIST]
myprog.py: error: unrecognized arguments: name2
I know I could pass the names with quotes "name1 name2" and split it in my code args.names_list.split(). But I'm curious, is there a better way to pass the list of strings via argparse module.
Any ideas would be appreciated.
Thanks!
You need to define --names-list to take an arbitrary number of arguments.
parser.add_argument('-n', '--names-list', nargs='+', default=[])
Note that options with arbitrary number of arguments don't typically play well with positional arguments, though:
# Is this 4 arguments to -n, or
# 3 arguments and a single positional argument, or ...
myprog.py -n a b c d
You need to use nargs:
parser.add_argument('-n', '--names-list', nargs="*")
https://docs.python.org/3/library/argparse.html#nargs
parser.add_argument('-n', '--names-list', default=[], nargs='+')

How can I get argparse to accept "--" as an argument to an option?

I have a command-line option that requires an argument. I would like to be able to supply "--" as the argument, but I can't figure out how to do it.
Sample code: (test-argparse.py)
#!/usr/bin/env python
from __future__ import print_function
import argparse
import sys
def main(argv):
ap = argparse.ArgumentParser()
ap.add_argument("-x", "--foo", metavar="VALUE", default="",
help="Test option.")
args = ap.parse_args(argv[1:])
print(args.foo)
if __name__ == "__main__":
sys.exit(main(sys.argv))
All my attempts to try to pass "--" as an argument fail:
$ test-argparse.py --foo --
usage: test-argparse.py [-h] [-x VALUE]
test-argparse.py: error: argument -x/--foo: expected one argument
$ test-argparse.py --foo -- --
usage: test-argparse.py [-h] [-x VALUE]
test-argparse.py: error: argument -x/--foo: expected one argument
$ test-argparse.py --foo=--
[]
$ test-argparse.py --foo=-- --
usage: test-argparse.py [-h] [-x VALUE]
test-argparse.py: error: unrecognized arguments: --
$ test-argparse.py --foo="--"
[]
$ test-argparse.py '--foo --'
usage: test-argparse.py [-h] [-x VALUE]
test-argparse.py: error: unrecognized arguments: --foo --
$ test-argparse.py -x--
[]
$ test-argparse.py '-x --'
--
The last case is the closest, but it includes the space (and I can't just strip whitespace, because what if I want to allow " " as a value?). Is there any way that I can accomplish this?
That argparse forces argument permutation on clients (leading to unnecessary ambiguity) is very frustrating.
(I am using Python 2.7.12.)
Ideally --foo=-- should work, but the current parser deletes all '--', leaving an empty string in its place, hence the foo=[] result. I proposed a patch a couple of years ago that should have fixed that, but it's caught in the argparse backlog. http://bugs.python.org/issue13922, http://bugs.python.org/issue14364, http://bugs.python.org/issue9571
Python argparse with -- as the value suggests preprocessing sys.argv replacing one or more of the -- with something else.
If you are game for patching your argparse.py file (or subclass the ArgumentParser class), I could revisit my earlier work and suggest a fix. The trick is to accept that =-- but still use the first free -- as the 'rest-are-positionals' flag (and retain any following --). Unfortunately one method that needs to be patched is nested in a much larger one.
There is a specific reason that this doesn't work: -- means "Skip this token and consider the rest of the arguments to be positional, even if they start with a dash."
Many, many programs won't accept -- as an argument, but they will accept -. The single dash is even a standard way of specifying "Use standard input or output" in place of a filename.
So the best thing you can do for the users of your program is probably to not design it to require --, because that's not something that's usually done, and not something that most modern command-line parsing libraries are likely able to parse.
You could use -- as a positional option, so you could probably support this:
--foo -- --
If you make --foo have action='store_true' (i.e. it is an option taking no argument), plus one non-mandatory positional argument. That will probably work, because the first -- means "stop processing dashes as options" and the second is a positional argument.

Can argparse in python 2.7 be told to require a minimum of TWO arguments?

My application is a specialized file comparison utility and obviously it does not make sense to compare only one file, so nargs='+' is not quite appropriate.
nargs=N only excepts a maximum of N arguments, but I need to accept an infinite number of arguments as long as there are at least two of them.
Short answer is you can't do that because nargs doesn't support something like '2+'.
Long answer is you can workaround that using something like this:
parser = argparse.ArgumentParser(usage='%(prog)s [-h] file file [file ...]')
parser.add_argument('file1', nargs=1, metavar='file')
parser.add_argument('file2', nargs='+', metavar='file', help=argparse.SUPPRESS)
namespace = parser.parse_args()
namespace.file = namespace.file1 + namespace.file2
The tricks that you need are:
Use usage to provide you own usage string to the parser
Use metavar to display an argument with a different name in the help string
Use SUPPRESS to avoid displaying help for one of the variables
Merge two different variables just adding a new attribute to the Namespace object that the parser returns
The example above produces the following help string:
usage: test.py [-h] file file [file ...]
positional arguments:
file
optional arguments:
-h, --help show this help message and exit
and will still fail when less than two arguments are passed:
$ python test.py arg
usage: test.py [-h] file file [file ...]
test.py: error: too few arguments
Couldn't you do something like this:
import argparse
parser = argparse.ArgumentParser(description = "Compare files")
parser.add_argument('first', help="the first file")
parser.add_argument('other', nargs='+', help="the other files")
args = parser.parse_args()
print args
When I run this with -h I get:
usage: script.py [-h] first other [other ...]
Compare files
positional arguments:
first the first file
other the other files
optional arguments:
-h, --help show this help message and exit
When I run it with only one argument, it won't work:
usage: script.py [-h] first other [other ...]
script.py: error: too few arguments
But two or more arguments is fine. With three arguments it prints:
Namespace(first='one', other=['two', 'three'])

Categories

Resources