Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm trying to create a python script that'll accept some arguments from the command line. I'm trying to use argparse but can't get it to work properly.
I need it to work similar to the way the aws cli works e.g. aws s3 cp has it's own arguments, aws s3 ls, has it's own etc
ref: https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html
https://docs.aws.amazon.com/cli/latest/reference/s3/ls.html
This is what I have but it always needs the mycmd option
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("mycmd", help="my test cmd")
parser.add_argument("-v", "--verbose", help="Verbose output", action="store_true")
args = parser.parse_args()
if args.mycmd:
print(f"arg is mycmd")
if args.verbose:
print("args v")
End result should be that mycmd1 has arguments xyz, mycmd2 has arguments abc etc and both can be ran from a single python file e.g. python3 somename.py mycmd1 -x ...
You could use the Python Click package, which has explicit support for subcommands:
import click
#click.group()
def cli():
pass
#cli.command()
#click.option('--arg1')
def mycmd1(arg1):
click.echo('My command 1')
if arg1:
click.echo(arg1)
#cli.command()
#click.option('--arg2')
def mycmd2(arg2):
click.echo('My command 2')
if arg2:
click.echo(arg2)
if __name__ == '__main__':
cli()
Usage:
(~)$ python -m click-example --help
Usage: click-example.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
mycmd1
mycmd2
(~)$ python -m click-example mycmd1 --help
Usage: click-example.py mycmd1 [OPTIONS]
Options:
--arg1 TEXT
--help Show this message and exit.
(~)$ python -m click-example mycmd2 --help
Usage: click-example.py mycmd2 [OPTIONS]
Options:
--arg2 TEXT
--help Show this message and exit.
(~)$ python -m click-example mycmd2 --arg1 err
Usage: click-example.py mycmd2 [OPTIONS]
Try "click-example.py mycmd2 --help" for help.
Error: no such option: --arg1
(~)$ python -m click-example mycmd1 --arg1 hello
My command 1
hello
(~)$
I think your code is completely valid. "mycmd" is just the name of your argument. I made some changes to your code to make it clearer:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("mycmd", help="my test cmd")
parser.add_argument("-v", "--verbose", help="Verbose output", action="store_true")
args = parser.parse_args()
if args.mycmd == "mycmd1":
print(f"arg is mycmd1")
elif args.mycmd == "mycmd2":
print(f"arg is mycmd2")
else:
print("arg not allowed")
if args.verbose:
print("args v")
On my machine:
$ python3 test.py mycmd1 -v
arg is mycmd1
args v
$ python3 test.py 12 -v
arg not allowed
args v
$ python3 test.py mycmd2 -v
arg is mycmd2
args v
As per argparse documentation, if you do not state whether a positional argument is optional or not, it will be always needed.
You can state how many times you want your option to be used by nargs. In your case, add nargs='?' to make it optional (interpreted like in regex, ? means "0 or 1").
See also nargs in argparse's documentation - there are nice examples of optional input/output files positional arguments
In your case, you might find useful parent arg parsers. Link to the part of the documentation about parents - just remember to read the part about handling colliding arguments (by default, both parsers will have -h option but you also might need some adjustments with your own arguments).
c.py
import argparse
import sys
parser = argparse.ArgumentParser(
prog='c', description="Description: to do some task",
epilog='Run c.py --help for more information')
subparser = parser.add_subparsers(title="Commands", help="commands")
mycmd_args = subparser.add_parser('mycmd', help='to dome some task',description="To do some task")
mycmd_args.add_argument("--arg1", "-a1", dest="argument1",help="provide argument")
mycmd_args.add_argument("--arg2", "-a2", dest="argument2",help="provide argument")
mycmd1_args = subparser.add_parser('mycmd1', help='to dome some task',description="To do some task")
mycmd1_args.add_argument("--arg1", "-a1", dest="argument1",help="provide argument")
mycmd1_args.add_argument("--arg2", "-a2", dest="argument2",help="provide argument")
if __name__=="__main__":
args=parser.parse_args(sys.argv[1:])
if len(sys.argv) <= 1:
sys.argv.append("-h")
elif sys.argv[1] == "mycmd":
print("mycmd arguemnts")
print(args.argument1)
print(args.argument2)
elif sys.argv[1] == "mycmd1":
print("mycmd1 arguemnts")
print(args.argument1)
print(args.argument2)
else:
sys.argv.append("-h")
output:
C:\Users\jt250054\Desktop>python c.py --help
usage: c [-h] {mycmd,mycmd1} ...
Description: to do some task
optional arguments:
-h, --help show this help message and exit
Commands:
{mycmd,mycmd1} commands
mycmd to dome some task
mycmd1 to dome some task
Run c.py --help for more information
C:\Users\jt250054\Desktop>python c.py mycmd --help
usage: c mycmd [-h] [--arg1 ARGUMENT1] [--arg2 ARGUMENT2]
To do some task
optional arguments:
-h, --help show this help message and exit
--arg1 ARGUMENT1, -a1 ARGUMENT1
provide argument
--arg2 ARGUMENT2, -a2 ARGUMENT2
provide argument
C:\Users\jt250054\Desktop>python c.py mycmd --arg1 argument1
mycmd arguemnts
argument1
None
C:\Users\jt250054\Desktop>python c.py mycmd --arg1 argument1 --arg2 arugment2
mycmd arguemnts
argument1
arugment2
C:\Users\jt250054\Desktop>
Related
I wanted to create an interface with argparse for my script with subcommands; so, if my script is script.py, I want to call it like python script.py command --foo bar, where command is one of the N possible custom commands.
The problem is, I already tried looking for a solution here on StackOverflow, but it seems like everything I tried is useless.
What I have currently is this:
parser = argparse.ArgumentParser()
parser.add_argument("-x", required=True)
parser.add_argument("-y", required=True)
parser.add_argument("-f", "--files", nargs="+", required=True)
# subparsers for commands
subparsers = parser.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", help="help of summary command")
If I try to run it with:
args = parser.parse_args("-x 1 -y 2 -f a/path another/path".split())
I got this error, as it should be: script.py: error: the following arguments are required: command.
If, however, I run this command:
args = parser.parse_args("summary -x 1 -y 2 -f a/path another/path".split())
I got this error, that I shouldn't have: script.py: error: the following arguments are required: -x, -y, -f/--files.
If I put the command at the end, changing also the order of arguments because of -f, it works.
args = parser.parse_args("-x 1 -f a/path another/path -y 2 summary".split())
If I add the parents keyword in subparser, so substitute the summary_parser line with summary_parser = subparsers.add_parser("summary", help=HELP_CMD_SUMMARY, parents=[parser], add_help=False), then I got:
script.py summary: error: the following arguments are required: command when summary is in front of every other argument;
script.py summary: error: the following arguments are required: -x, -y, -f/--files, command when summary is at the end of the args.
My question is, how I have to setup the parsers to have the behaviour script.py <command> <args>? Every command shares the same args, because they are needed to create certain objects, but at the same time every command can needs other arguments too.
Creating another parser helped me getting what I wanted.
The root parser should add all the optional arguments - and also have add_help=False, to avoid an help message conflict -, then another parser - parser2, with a lot of fantasy - will be created.
The second parser will have subparsers, and they all needs to specify as parents the root parser.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument() ...
parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", parents=[parser])
summary_parser.add_argument("v", "--verbose", action="store_true")
# parse args
args = parser2.parse_args()
Now the output will be this:
usage: script.py [-h] {summary} ...
optional arguments:
-h, --help show this help message and exit
Commands:
{summary}
summary For each report print its summary, then exit
usage: script.py summary [-h] -x X -y Y -f FILES [FILES ...] [-v]
optional arguments:
-h, --help show this help message and exit
-x X
-y Y
-f FILES [FILES ...], --files FILES [FILES ...]
-v, --verbose
I'd like to do something like:
my-cli.py todo1 --todo1-option1 --todo1-option2 ...
my-cli.py todo2 --todo2-option1 --todo2-option2 ...
In addition, I'm hoping to declare --todo1-option1 is required if the command is todo1 and it is invalid parameter if I specify parameters for different command (i.e. todo2)
I'm also looking to have different -h for different command (i.e. todo1 or todo2).
I'm sorry if my English is a little broken. I hope you get what I mean.
The way I understand some tutorials, the main python app is the action itself like for example:
cp -f file1 file2
At first I thought there's some clever trick using the add_mutually_exclusive_group but the answer to my question is in fact the subparsers
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')
# A todo1 command
todo1_parser = subparsers.add_parser(
'todo1', help='List contents')
todo1_parser.add_argument(
'--todo1-option1', action='store', required=True,
help='Todo1 Option 1')
todo1_parser.add_argument(
'--todo1-option2', action='store',
help='Todo1 Option 2')
# A todo2 command
todo2_parser = subparsers.add_parser(
'todo2', help='List contents')
todo2_parser.add_argument(
'--todo2-option1', action='store', required=True,
help='Todo2 Option 1')
todo2_parser.add_argument(
'--todo2-option2', action='store',
help='Todo2 Option 2')
print(parser.parse_args())
Test for list of commands:
$ python test_argparse.py
python test_argparse.py usage: test_argparse.py [-h] {todo1,todo2} ...
test_argparse.py: error: too few arguments
Test for Todo1 required parameters:
$ python test_argparse.py todo1
usage: test_argparse.py todo1 [-h] --todo1-option1 TODO1_OPTION1 [--todo1-option2 TODO1_OPTION2]
test_argparse.py todo1: error: the following arguments are required: --todo1-option1
Test for Todo1 required parameters:
$python test_argparse.py todo2
usage: test_argparse.py todo2 [-h] --todo2-option1 TODO2_OPTION1 [--todo2-option2 TODO2_OPTION2]
test_argparse.py todo2: error: the following arguments are required: --todo2-option1
Test for Todo2 shouldn't be availble to Todo1:
$python test_argparse.py todo1 --todo2-option1 aaa
usage: test_argparse.py [-h] {todo1,todo2} ... test_argparse.py: error:
unrecognized arguments: --todo2-option1 aaa
I have a similar problem to the one described in this question, however I have required options before the command and not an argument. I have tried adapting the accepted answer to my situation but can not get it to work, for instance
#! python3
import click
class PerCommandOptWantSubCmdHelp(click.Option):
def handle_parse_result(self, ctx, opts, args):
# check to see if there is a --help on the command line
if any(arg in ctx.help_option_names for arg in args):
# if asking for help see if we are a subcommand name
for arg in opts.values():
if arg in ctx.command.commands:
# this matches a sub command name, and --help is
# present, let's assume the user wants help for the
# subcommand
args = [arg] + args
return super(PerCommandOptWantSubCmdHelp, self).handle_parse_result(ctx, opts, args)
#click.group()
def foo():
pass
#click.group('map')
#click.option('-f', '--force', is_flag=True)
#click.option('-i', '--id')
#click.option('-b', '--base', required=True, cls=PerCommandOptWantSubCmdHelp)
def archive_map(force, id, base):
click.echo('Map called')
volla.add_command(archive_map)
#click.command('bar')
#click.option('-t', '--template', required=True)
#click.option('-p', '--project', required=True)
def bar_command():
pass
archive_map.add_command(bar_command);
if __name__ == '__main__':
foo()
But I still get this behavior
$ ./foo map bar --help
Usage: foo map [OPTIONS] COMMAND [ARGS]...
Try 'foo map --help' for help.
Error: Missing option '-b' / '--base'.
$
Any ideas for what I have misunderstood?
I have done something like this:
class PerCommandOptWantSubCmdHelp(click.Option):
def handle_parse_result(self, ctx, opts, args):
# check to see if there is a --help on the command line
if any(arg in ctx.help_option_names for arg in args):
# if asking for help see if we are a subcommand name
remaining_args = [arg for arg in args if arg not in ctx.help_option_names]
for arg in remaining_args:
if arg in ctx.command.commands:
click.echo(ctx.command.get_help(ctx))
click.echo()
click.echo(f'Command {arg} usage')
click.echo(ctx.command.commands[arg].get_help(ctx))
return super(PerCommandOptWantSubCmdHelp, self).handle_parse_result(ctx, opts, args)
Essentially, if you've invoked the subcommand with help, get the parent's help message, output it, and then output your current subcommand's help message.
Your output will look like this:
Usage: main.py map [OPTIONS] COMMAND [ARGS]...
Options:
-f, --force
-i, --id TEXT
-b, --base TEXT [required]
--help Show this message and exit.
Commands:
bar
Command bar usage:
Usage: main.py map [OPTIONS]
Options:
-t, --template TEXT [required]
-p, --project TEXT [required]
--help Show this message and exit.
Usage: main.py map [OPTIONS] COMMAND [ARGS]...
Try 'main.py map --help' for help.
Error: Missing option '-b' / '--base'.
Does this help?
Can I use argparse to read named command line arguments that do not need to be in a specific order? I browsed through the documentation but most of it focused on displaying content based on the arguments provided (such as --h).
Right now, my script reads ordered, unnamed arguments:
myscript.py foo-val bar-val
using sys.argv:
foo = sys.argv[1]
bar = sys.argv[2]
But I would like to change the input so that it is order agnostic by naming arguments:
myscript.py --bar=bar-val --foo=foo-val
You can use the Optional Arguments like so.
With this program:
#!/usr/bin/env python3
import argparse, sys
parser=argparse.ArgumentParser()
parser.add_argument("--bar", help="Do the bar option")
parser.add_argument("--foo", help="Foo the program")
args=parser.parse_args()
print(f"Args: {args}\nCommand Line: {sys.argv}\nfoo: {args.foo}")
print(f"Dict format: {vars(args)}")
Make it executable:
$ chmod +x prog.py
Then if you call it with:
$ ./prog.py --bar=bar-val --foo foo-val
It prints:
Args: Namespace(bar='bar-val', foo='foo-val')
Command Line: ['./prog.py', '--bar=bar-val', '--foo', 'foo-val']
foo: foo-val
Dict format: {'bar': 'bar-val', 'foo': 'foo-val'}
Or, if the user wants help argparse builds that too:
$ ./prog.py -h
usage: prog.py [-h] [--bar BAR] [--foo FOO]
options:
-h, --help show this help message and exit
--bar BAR Do the bar option
--foo FOO Foo the program
2022-08-30: Updated to Python3 this answer...
The answer is yes. A quick look at the argparse documentation would have answered as well.
Here is a very simple example, argparse is able to handle far more specific needs.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo', '-f', help="a random options", type= str)
parser.add_argument('--bar', '-b', help="a more random option", type= int, default= 0)
print(parser.format_help())
# usage: test_args_4.py [-h] [--foo FOO] [--bar BAR]
#
# optional arguments:
# -h, --help show this help message and exit
# --foo FOO, -f FOO a random options
# --bar BAR, -b BAR a more random option
args = parser.parse_args("--foo pouet".split())
print(args) # Namespace(bar=0, foo='pouet')
print(args.foo) # pouet
print(args.bar) # 0
Off course, in a real script, you won't hard-code the command-line options and will call parser.parse_args() (without argument) instead. It will make argparse take the sys.args list as command-line arguments.
You will be able to call this script this way:
test_args_4.py -h # prints the help message
test_args_4.py -f pouet # foo="pouet", bar=0 (default value)
test_args_4.py -b 42 # foo=None, bar=42
test_args_4.py -b 77 -f knock # foo="knock", bar=77
You will discover a lot of other features by reading the doc ;)
I think it might help you with a simple one
#! /usr/bin/python3
import sys
keys = ["--paramkey=","-p="]
for i in range(1,len(sys.argv)):
for key in keys:
if sys.argv[i].find(key) == 0:
print(f"The Given value is: {sys.argv[i][len(key):]}")
break
Run:
$ ./example.py --paramkey=paramvalue -p=pvalue
Output:
The Given value is: paramvalue
The Given value is: pvalue
I'm trying to have a mutually exclusive group between different groups:
I have the arguments -a,-b,-c, and I want to have a conflict with -a and -b together, or -a and -c together. The help should show something like [-a | ([-b] [-c])].
The following code does not seem to do have mutually exclusive options:
import argparse
parser = argparse.ArgumentParser(description='My desc')
main_group = parser.add_mutually_exclusive_group()
mysub_group = main_group.add_argument_group()
main_group.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
mysub_group.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
mysub_group.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
Parsing different combinations - all pass:
> python myscript.py -h
usage: myscript.py [-h] [-a] [-b] [-c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
> python myscript.py -a -c
> python myscript.py -a -b
> python myscript.py -b -c
I tried changing the mysub_group to be add_mutually_exclusive_group turns everything into mutually exclusive:
> python myscript.py -h
usage: myscript.py [-h] [-a | -b | -c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
-b b help
-c c help
How can I add arguments for [-a | ([-b] [-c])]?
So, this has been asked a number of times. The simple answer is "with argparse, you can't". However, that's the simple answer. You could make use of subparsers, so:
import argparse
parser = argparse.ArgumentParser(description='My desc')
sub_parsers = parser.add_subparsers()
parser_a = sub_parsers.add_parser("a", help='a help')
parser_b = sub_parsers.add_parser("b", help='b help')
parser_b.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
You then get:
$ python parser -h
usage: parser [-h] {a,b} ...
My desc
positional arguments:
{a,b}
a a help
b b help
optional arguments:
-h, --help show this help message and exit
and:
$ python parser b -h
usage: parser b [-h] [-c]
optional arguments:
-h, --help show this help message and exit
-c c help
If you would prefer your arguments as stated in the question, have a look at docopt, it looks lovely, and should do exactly what you want.
argument_groups don't affect the parsing. They just contribute to the help formatting. So defining a group within an mutually_exclusive_group does not help with this problem.
There is a proposed patch, http://bugs.python.org/issue10984, 'argparse add_mutually_exclusive_group should accept existing arguments to register conflicts', that would allow you to define two mutually_exclusive_groups, one [-a | -b] and the other [-a | -c]. Creating a 2nd group that includes an argument (-a) that is already defined is the trivial part of this patch. Producing a meaningful usage line is more difficult, and required a rewrite of several HelpFormatter methods.
import argparse
parser = argparse.ArgumentParser(description='My desc')
group1 = parser.add_mutually_exclusive_group()
action_a = group1.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
group1.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
group2 = parser.add_mutually_exclusive_group()
group2.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
group2._group_actions.append(action_a) # THE KLUDGE
print parser.format_usage()
# usage: stack16769409.py [-h] [-a | -b] [-c]
args = parser.parse_args()
Usage does not show the 2 groups correctly. But it does accept -b -c, while objecting to -a -b and -a -c. But you can write a custom usage line.