I'm developing a simple project with the purpose or learning Python, actually I have version 3.6 and I wanted to build a command line tool to generate password with specific criteria. So fare here is what I got:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "-auto", help="auto mode", action="store_true")
group.add_argument("-m", "-manual", help="manual mode", action="store_true")
parser.parse_args()
the dead end is I have no idea how to limit a command, example -l -lenght to the reqisite of having -m another stop is, how do I define -l so that it can accept a value, for example -l:8 to specify a password of 8 characters, I also want to put a range limit on -l values, example -l:8-256 and finally, I dont understand the difference between - and -- when defining the parameters.
I have already done all the part of generating passwords, just need a way to control its flow from outside so implementing parameters looked like a good way of doing this.
What you are looking for is the choices option.
add_argument('--length', type=int, choices=range(8,257)
This will only accept integer values between 8 and 256 (inclusive).
As for your second question, - indicates the shorthand for an option, which is common in most CLI tools, well -- indicates the long hand. It is common practice is CLI tools to provide both a long and a short hand for options.
You can define a custom type to check for valid values:
from argparse import ArgumentTypeError
def passwd_len(s):
try:
s = int(s)
except ValueError:
raise ArgumentTypeError("'%s' is not an integer" % (s,))
if not (8 <= s <= 256):
raise ArgumentTypeError("%d is not between 8 and 256, inclusive" % (s,))
return s
parser.add_argument("--length", "-l", type=passwd_len)
The difference between -- and - is one of conventions. Historically, options were single-letter arguments prefixed with a -, and groups of options could be combined: -ab was the same as -a -b. In order to
support that convention and allow longer option names, a new convention
of using -- to prefix multi-character names was devised. --length is a single option, not a group of six options -l, -e, -n, -g, -t, and -h. Long options cannot be grouped.
I would define the -a and -m options like this:
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "--auto", help="auto mode", action="store_const", const="auto", dest="mode")
group.add_argument("-m", "--manual", help="manual mode", action="store_const", const="manual", dest="mode")
group.add_argument("--mode", choices=["auto", "manual"])
Now instead of having two Boolean attributes that can never have the same value, you have just one attribute whose value you can check directly. Think of --mode as being the canonical way of choosing a mode, with -a and -m as shortcuts for selecting a specific mode.
Related
This script will print env vars.
Using Python 3.9.
The goal is to able to run any subcommands if desired. The error I am getting is that if any additional short flags are added, the "ignore environment" arg is trying to parse it. I dont want this. Additional short flags go to anything assigned after --eval.
parser.py
import argparse, os
def parseargs(p):
p.usage = '%(prog)s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]'
p.add_argument(
"-i",
"--ignore-environment",
action="store_const",
const=dict(),
dest="env",
help="start with an empty environment",
default=os.environ,
)
p.add_argument(
"--export",
nargs=1,
help="Set argument with --export NAME=VALUE"
)
p.add_argument(
"--eval",
nargs="+",
help="Run any commands with newly updated environment, "
"--eval COMMAND ARGS"
)
return p
Execution as follows
>>> p = argparse.ArgumentParser()
>>> parseargs(p) # assigns arguments to parser
>>> p.parse_args('--export FOO=bar --eval cat test.py'.split()) # This is ok and works correctly. cat is the bash command
Namespace([os.environs..], eval=['cat', 'test.py'], export=['FOO=bar'])
>>>p.parse_args('--export FOO=bar --eval ls -l'.split()) # This is fails
error: unrecognized arguments: -l
How do I get "-l" to be overlook by "-i/ignore environment" but passed to eval, like using cat test.py. I have tried using sub_parser but to no avail. The same result occurs.
The problem is that parse_args tries to identify possible options lexically before ever considering the semantics of any actual option.
Since an option taking a variable number of arguments pretty much has to be the last option used alway, consider making --eval a flag which is used to tell your program how to interpret the remaining positonal arguments. Then ls and -l can be offset by --, preventing parse_args from thinking -l is an undefined option.
p.add_argument(
"--eval",
action='store_true',
help="Run any commands with newly updated environment, "
)
# zero or more, so that you don't have to provide a dummy argument
# when the lack of --eval makes a command unnecessary.
# Wart: you can still use --eval without specifying any commands.
# I don't believe argparse alone is capable of handling this,
# at least not in a way that is simpler than just validating
# arguments after calling parse_args().
p.add_argument('cmd_and_args', nargs='*')
Then your command line could look like
>>> p.parse_args('--export FOO=bar --eval -- ls -l'.split())
or even
>>> p.parse_args('--eval --export FOO=bar -- ls -l'.split())
Later, you'll use the boolean value of args.eval to decide how to treat the list args.cmd_and_args.
Important: One wrinkle with this is that you are attaching these options to arbitrary pre-existing parsers, which may have their own positional arguments defined, so getting this to play nice with the original parser might be difficult, if not impossible.
The other option is to take a single argument to be parsed internally.
p.add_arguments("--eval")
...
args = p.parse_args()
cmd_and_args = shlex.split(args.eval) # or similar
Then
>>> p.parse_args(['--export', 'FOO=bar', '--eval', 'ls -l'])
(Note that using str.split isn't going to work for a command line like --export FOO=bar --eval "ls -l".)
From the Argparse documentation:
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 [...]
So in your case, there are no changes you can make to how you add or define the arguments, but the string you provide to be parsed should have -- preceding the arguments to the eval option, as such:
--export FOO=bar --eval ls -- -l
I am working on a custom Nagios script in which I would like to implement parsing of command line arguments in the same way that is used on existing Nagios Plugin check_disk.
In that plugin you have an option parameter -C to Clear current config and start over with new parameters.
Extracted from check_disk help:
check_disk -w 100 -c 50 -C -w 1000 -c 500 -p /foo -C -w 5% -c 3% -p /bar
----(3)----- ---------(1)---------- --------(2)--------
Checks warning/critical thresholds:
for volume /foo use 1000M and 500M as thresholds
for volume /bar use 5% and 3%
All remaining volumes use 100M and 50M
I have tried with argparse and some parameter as action='append' but repeated arguments are stored in a list and missing ones are not includes as a "None" entry.
I have also tried with parse_known_args hoping to stop parsing on first unknown argument, but I get a namespace with all known arguments and a list of unknown arguments.
I guess that my only option is using regular expressions before parsing the command line arguments.
import re
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('-w', help='warning')
parser.add_argument('-c', help='critical')
parser.add_argument('-p', help='path')
separator=r'-S'
groups = re.split(separator, '|'.join(sys.argv[1:])))
args = []
for idx, group_args in enumerate(groups):
args.append('')
args[idx]=parser.parse_args(group_args.split('|'))
I do not know if argparse can handle this kind of scenario without need to split using regular expressions.
Or if this is the best approach I can find.
This is not related with Using the same option multiple times in Python's argparse because it is not the same case, I have different optional argument not just one option with multiple values.
In example above (3) have not got option -p, (1) and (2) have it. That is one of the difference and one of the problems. If all options were mandatory, it was easy.
To handle this:
check_disk -w 100 -c 50 -C -w 1000 -c 500 -p /foo -C -w 5% -c 3% -p /bar
I can imagine starting with a namespace
args = argparse.Namespace(w:[None], c:[None], p:[None])
args = parser.parse_args(args=args)
and define a couple of custom Action classes.
For `-C` use a class that appends a `None` to each of those 3 attributes
namespace['w'].append(None), etc
For each of `w`, `c` and `p`, an Action class, that replaces the last `None`
with the users value.
In other words, use the C argument to 'reset' by advancing the lists, and then use the others to to adjust the default default values.
Alternatively start with a Namespace(C=[[None, None, None]]), and add append a list with each 'C'. Then 'w' would set the namespace['C'][-1][0] etc. (Or use a list of dicts).
I'm trying to create a terminal application a bit similar to cutechess-cli, which has options like the following:
-epdout FILE Save the end position of the games to FILE in FEN format.
-recover Restart crashed engines instead of stopping the match
-repeat [N] Play each opening twice (or N times). Unless the -noswap...
which can all be done with argparse in Python.
However it also has "named arguments" like the following:
-resign movecount=COUNT score=SCORE [twosided=VALUE]
Adjudicate the game as a loss if an engine's score is
at least SCORE centipawns below zero for at least COUNT
consecutive moves.
-sprt elo0=ELO0 elo1=ELO1 alpha=ALPHA beta=BETA
Use a Sequential Probability Ratio Test as a termination
criterion for the match. This option should only be used...
I can implement that with argparse using nargs='*' and then writing my own parser (maybe just regex). However that doesn't give nice documentation, and if argparse can already do something like this, I would rarther use the builtin approach.
Summary: Does argparse have a concept of named arguments similar to resign and sprt above? And if not, would the best approach be to do this manyally using nargs='*'?
You can use a custom type to split the values and use the metavar argument to give a better description for the value:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--arg', nargs='*', type=lambda text: text.split('=', maxsplit=1), metavar='PARAM-NAME=PARAM-VALUE', help='Some other parameters')
args = parser.parse_args()
args.arg = {k: v for k,v in args.arg}
Which produces:
usage: [-h] [--arg [PARAM-NAME=PARAM-VALUE [PARAM-NAME=PARAM-VALUE ...]]]
optional arguments:
-h, --help show this help message and exit
--arg [PARAM-NAME=PARAM-VALUE [PARAM-NAME=PARAM-VALUE ...]]
Some other parameters
If you wanted to you could avoid the "postprocessing" step to build the dictionary by using a custom Action type. But it seems an overkill to me in this case.
I have a program that stores a few important variables as strings that are necessary for the program to operate properly: DeviceNumber, IPAddress, and Port. These variables are stored in a file, which is loaded by the program.
For debugging purposes, I would like to be able to quickly overwrite the files with command line arguments. The args would all be optional, and if not used then it just uses the variables taken out of the file.
I can do simple positional args using something like DeviceNumber = sys.args[1], and only overwrite vars if the args are present, but this has the problem of not being able to handle if you enter the variables in the wrong order, or if you enter, say, the IPAddress and Port but not the DeviceNumber.
I have been reading through the pyDocs argparse Tutorial and documentation, but it does not seem terribly useful for what I need - it covers mandatory args, and optional flags, but does not seem to allow optional args that depend on a flag to denote their purpose. EDIT: Turns out it does, but the example is not very obvious, so I missed it. Similarly I have had trouble finding applicable questions here on SE.
In short, given a program like
#Default Names loaded from file
DeviceNumber = "2000"
IPAddress = "159.142.32.30"
Port = 80
#if command line args entered:
#overwrite default vars with arg corresponding to each one
#this probably involves argparse, but I don't know how
print "DNumber:"+DeviceNumber+" IP:"+IPAddress+" Port:"+Port
Some examples with different cmd line inputs/outputs would be:
All values are defaults
$ python myfile.py
DNumber:2000 IP:159.142.32.30 Port:80
All values are overridden
$ python myfile.py -n 1701 -ip 120.50.60.1 -p 3000
DNumber:1701 IP:120.50.60.1 Port:3000
Default DNumber, Override IPAddress + Port. Args were specified in different order.
$ python myfile.py -p 123 -ip 124.45.67.89
DNumber:2000 IP:124.45.67.89 Port:123
Default DNumber and IPAddress, Override Port
$ python myfile.py -p 500
DNumber:2000 IP:159.142.32.30 Port:500
You get the idea...
for the syntax of the flag/args relationship, I am also not sure if there is a specific syntax python needs you to use (i.e. -p 500 vs. -p500 v -p=500)
There's a few ways to define default values for arguments specified by argparse. When adding arguments to your parser, you can specify a default value. Simply:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", dest="port", default=500,
type=int, help="specify port")
args = parser.parse_args()
print "You've selected port: %d" % (args.port)
From the above example, it is trivial to extend to allow additional default functionality. Note that dest="port" is already by default due to the naming of the long argument --port. Like so:
parser.add_argument("-p", "--port", dest="port", default=None,
type=int, help="specify port")
args = parser.parse_args()
if args.port is None:
port = read_port_from_file()
print "No optional port specified. Reading value from file."
print "You've selected port: %d" % (args.port)
I am using the OptionParser from optparse module to parse my command that I get using the raw_input().
I have these questions.
1.) I use OptionParser to parse this input, say for eg. (getting multiple args)
my prompt> -a foo -b bar -c spam eggs
I did this with setting the action='store_true' in add_option() for '-c',now if there is another option with multiple argument say -d x y z then how to know which arguments come from which option? also if one of the arguments has to be parsed again like
my prompt> -a foo -b bar -c spam '-f anotheroption'
2.) if i wanted to do something like this..
my prompt> -a foo -b bar
my prompt> -c spam eggs
my prompt> -d x y z
now each entry must not affect the other options set by the previous command. how to accomplish these?
For part 2: you want a new OptionParser instance for each line you process. And look at the cmd module for writing a command loop like this.
You can also solve #1 using the nargs option attribute as follows:
parser = OptionParser()
parser.add_option("-c", "", nargs=2)
parser.add_option("-d", "", nargs=3)
optparse solves #1 by requiring that an argument always have the same number of parameters (even if that number is 0), variable-parameter arguments are not allowed:
Typically, a given option either takes
an argument or it doesn’t. Lots of
people want an “optional option
arguments” feature, meaning that some
options will take an argument if they
see it, and won’t if they don’t. This
is somewhat controversial, because it
makes parsing ambiguous: if "-a" takes
an optional argument and "-b" is
another option entirely, how do we
interpret "-ab"? Because of this
ambiguity, optparse does not support
this feature.
You would solve #2 by not reusing the previous values to parse_args, so it would create a new values object rather than update.