Argparse: either (x and y) OR (z) - python

I have the following code:
def main():
argParser = argparse.ArgumentParser(description = 'DESCRIPTION',epilog = 'Please see README.MD for guidance on how to use this script')
argParser.add_argument('-i', '--inFile', action='store',type=str, required = True, help = 'Input config file')
argParser.add_argument('-o', '--outFile', action = 'store', default = sys.stdout, type = argparse.FileType('w'), required = False, help = 'Output VCD File')
# generate_config_group = argParser.add_mutually_exclusive_group()
argParser.add_argument('-g', '--generate-config', action='store_true',help='Generate empty config File to fill')
args = argParser.parse_args()
print("\nExecuting Script:" + str(sys.argv[0]))
print(" config file:" + str(args.inFile))
print(" vcd outFile:" + str(args.outFile.name))
if args.generate-config:
if generate_config():
return 'config generated. see sample-config.ini. You will need to edit these values for this script to run successfully'
I want it so that you either need BOTH -i and -o arguments OR the -g argument. Right now if you supply -g, it still gets mad that you haven't supplied -i and -o. What is the way to do this? I have heard subparsers might be the way, but I see that with those, you call the command differently - script.py subparser_name -flags.
I don't want that. I only want -o and -i and -g as possible flags.

Why not update the IF at the end to something like:
import warnings
if (args.generate-config) or (args.inFile and args.outFile):
if generate_config():
return 'config generated. see sample-config.ini. You will need to edit these values for this script to run successfully'
else:
warnings.warn("When executing script i and o or g are required.")

Related

Subprocess call invalid argument or option not found

I'm trying to call ffmpeg command using subprocess.call() on linux, but I'm unable to get the arguments right. Before hand, I used os.system and it worked, but this method is not recommended.
Using arguments with a dash such as "-i" gets me this error
Unrecognized option 'i "rtsp://192.168.0.253:554/user=XXX&password=XXX&channel=0&stream=0.sdp?real_stream"'.
Error splitting the argument list: Option not found
Using arguments without dash like "i" gets me this error
[NULL # 0x7680a8b0] Unable to find a suitable output format for 'i rtsp://192.168.0.253:554/user=admin&password=&channel=0&stream=0.sdp?real_stream'
i rtsp://192.168.0.253:554/user=XXX&password=XXX&channel=0&stream=0.sdp?real_stream: Invalid argument
Here's the code
class IPCamera(Camera):
"""
IP Camera implementation
"""
def __init__(self,
path='\"rtsp://192.168.0.253:554/'
'user=XXX&password=XXX&channel=0&stream=0.sdp?real_stream\"'):
"""
Constructor
"""
self.path = path
def __ffmpeg(self, nb_frames=1, filename='capture%003.jpg'):
"""
"""
ffm_input = "-i " + self.path
ffm_rate = "-r 5"
ffm_nb_frames = "-vframes " + str(nb_frames)
ffm_filename = filename
if platform.system() == 'Linux':
ffm_path = 'ffmpeg'
ffm_format = '-f v4l2'
else:
ffm_path = 'C:/Program Files/iSpy/ffmpeg.exe'
ffm_format = '-f image2'
command = [ffm_path, ffm_input, ffm_rate, ffm_format, ffm_nb_frames, ffm_filename]
subprocess.call(command)
print(command)
BTW, I'm running this command on a MT7688.
Thanks
You have to split the options:
command = [ffm_path, '-i', ffm_input, '-r', ffm_rate, '-f', ffm_format, '-vframes', ffm_nb_frames, ffm_filename]
The ffm_input, ffm_rate, ffm_format should only contain the value:
ffm_input = self.path
ffm_rate = '5'
ffm_nd_frames = str(nb_frames)
ffm_format = 'v412' if platform.system() == 'Linux' else 'image2'
When you pass a list no parsing is done so -r 5 is taken as a single argument but the program expects you to provide two separate arguments -r followed by 5.
Basically if you put them as a single element in the list it's as if you quoted them on the command line:
$ echo "-n hello"
-n hello
$ echo -n hello
hello$
In the first example echo sees a single argument -n hello. Since it does not match any option it just prints it. In the second case echo sees two arguments -n and hello, the first is the valid option to suppress end of line and as you can see the prompt is printed right after hello and not on its own line.

Error handling with verbose output

Im trying to implement the --verbose option in my script. The idea is to turn on extra printing of errors etc for debugging, but for some reason it doesnt seem to work. Ive tried a few variations of the if verbose statement but no joy. Im hoping someone could point me in the right direction?
CLI EXAMPLE
./attack2.py -f wordfile.txt -d google.com --verbose 1
CLI OUTPUT
unknown#ubuntu:~$ ./attack2.py -f wordfile.txt -d google.com --verbose 1
173.194.34.149
173.194.34.130
unknown#ubuntu:~$
ARG PRINT
{'--domain': 'google.com',
'--file': 'wordfile.txt',
'--help': False,
'--thread': False,
'--verbose': True,
'10': False,
'<1>': '1'}
CODE
#!/usr/bin/python
"""
Description:
Basic Domain bruteforcer
Usage:
attack2.py (-f <file>) (-d <domain>) [-t 10] [-v <1>]
attack2.py -h | --help
Arguments:
-f --file File to read potential Sub-domains from. (Required)
-d --domain Domain to bruteforce. (Required)
Options:
-h --help Show this screen.
-p --proxy Proxy address and port. [default: http://127.0.0.1:8080] (Optional)
-t --thread Thread count. (Optional)
-v --verbose Turn debug on. (Optional)
"""
import socket
from docopt import docopt
def fread(dwords):
flist = open(dwords).readlines()
return [s.replace('\n', '.') for s in flist]
def subcheck(subdomain, domain, verbose):
vdomain = {}
for sub in subdomain:
try:
check = socket.gethostbyname(sub + domain)
vdomain[sub + domain] = check
except socket.gaierror, e:
if verbose == True:
print arguments
print e, sub + domain
else:
pass
return vdomain
if __name__ == "__main__":
arguments = docopt(__doc__, version='0.1a')
fread(arguments['--file'])
returned_list = fread(arguments['--file'])
returned_domains = subcheck(returned_list, arguments['--domain'], ['--verbose'])
The below line in function subcheck
returned_domains = subcheck(returned_list, arguments['--domain'], ['--verbose'])
should be
returned_domains = subcheck(returned_list, arguments['--domain'], arguments['--verbose'])
You forgot to pass the verbose param from arguments, instead you passed a list
It seems I was calling the subcheck incorrectly.
Code should have looked like this
Working CODE
if __name__ == "__main__":
arguments = docopt(__doc__, version='0.1a')
fread(arguments['--file'])
returned_list = fread(arguments['--file'])
returned_domains = subcheck(returned_list, arguments['--domain'], arguments['--verbose'])
print returned_domains

Python: Can optparse have the ACTION attribute to act both like STORE and STORE_TRUE?

I am using optparse to get command line input.
Lets say that I am running a script demo.py and it creates some output. But unless I specify the command line input, the output is not written to a file.
I am trying to do the following:
python demo.py in command line should run the script, but not write the output anywhere.
python demo.py -o in command line should write the output to my default file name output.txt.
python demo.py -o demooutput.txt in command line should write the output to file demooutput.txt.
PS: I would not prefer to switch to argparse from optparse.
You can use optparse-callbacks to achieve this.
Here is how it wiill work for your use case.
parser.add_option("-o", action="callback", dest="output", callback=my_callback)
def my_callback(option, opt, value, parser):
if len(parser.rargs) > 0:
next_arg = parser.rargs[0]
if not next_arg.startswith("-"):
# Next argument is not another option
del parser.rargs[0]
setattr(parser.values, option.dest, next_arg)
return
# If not processed, set the default value
setattr(parser.values, option.dest, "output.txt")
I don't think there is unfortunately - the only way I can think of is hacking around the problem by adding your own logic statements. The following code should do the trick.
import re, sys
import optparse from OptionParser
usage = "usage: %prog [options] arg"
parser = OptionParser(usage)
if '-f' in argv:
a = argv.index('-f')
if (a != len(argv)-1) and re.search('[.]txt', argv[a+1]):
parser.add_option("-f", "--foo", dest="foo")
else:
parser.add_option("-f", dest="foo", action="store_true")
This doesn't answer the direct question, 'how to define an Action...', but it handles the inputs in a simple way.
Set '-o' to be 'store_true'. If True check the 'args' variable for a file name.
(options, args) = parser.parse_args()
if options.o:
if args:
dest = args[0]
else:
dest = 'output.txt'
else:
dest = ''
(In argparse the equivalent would be to define a positional argument with nargs='?'.)
If these are the only arguments, you could also get by with checking for the filename without requiring the `-o'.
Another possibility - 'store_const', with the positional 'filename' having priority:
parser = optparse.OptionParser()
parser.add_option('-o',dest='dest',action='store_const', const='output.txt', default='')
(options, args) = parser.parse_args()
if args:
options.dest = args[0]
print options

getopt() not enforcing required arguments?

I'm having problems with this getopt() code in a script that I'm writing which does some simple file manipulation given 2 required parameters (input filename and output filename) and/or 2 optional/situational arguments (debug or help).
Code is:
def main(argv):
try:
opts, args = getopt.getopt(argv, "i:o:dh", ["input-file=", "output-file=", "debug", "help"])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
sys.exit()
elif opt in ("-d", "--debug"):
global _debug
_debug = 1
elif opt in ("-i", "--input-file"):
u_input_file_name = arg
elif opt in ("-o", "--output-file"):
u_output_file_name = arg
According to the getopt() documentation:
options that require an argument followed by a colon (':'; i.e., the same format that Unix getopt() uses).
The problem is that as I understand it, the variables/args followed by a : should be enforced as required ... but the options i and o are not being enforced. Running this snippet garners an error about u_input_file_name being referenced before being assigned:
[tdelane#fbsd81-1 ~/python]$ ./inco_add_cm_mpscli.py -o google
Traceback (most recent call last):
File "./inco_add_cm_mpscli.py", line 57, in <module>
main(sys.argv[1:])
File "./inco_add_cm_mpscli.py", line 25, in main
infile = open(u_input_file_name, 'r')
UnboundLocalError: local variable 'u_input_file_name' referenced before assignment
What am I doing wrong?
An option followed by a colon only means that it needs an argument. It doesn't mean that the option is enforced. You should write your own code to enforce the existence of options/arguments.
Just as a note, I found that argparse is simpler and more useful than getopt, and it support required arguments.
http://docs.python.org/2/howto/argparse.html#id1
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
Command Line
$ python prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
In case this is useful to anyone. Here is a boiler plate that I use for creating a python script with command line options. It handles required options. If a required option is not specified, then the script will terminate with an error.
#!/usr/bin/python
import os
import sys
import getopt
import logging
# This will get the name of this file
script_name = os.path.basename(__file__)
default_loglevel = 'info'
##
# #brief Help document for this script. See the main function below.
#
help = f'''
{script_name} -c com_port [-o output_file] [--loglevel level]
Reads the temperature data from a radio. The temperature data is output in csv form.
examples:
Read table from radio attached to com4 and write the table to the file
output.csv.
{script_name} -c com4 -o output.csv
Read table from radio attached to com3 and write the table to stdout.
You can use IO redirection to send the contents where every you want.
# just print to the terminal
{script_name} -c com3
# redirect to another file
{script_name} -c com3 > somefile.csv
# filter out temperatures that are -100
{script_name} -c com3 | grep -v '^-100'
-c com_port
--com_port comport
Name of the COM port attached to the radio
-o output_file
--output output_file
If specified write the table data to the given file. If not specified
the data will be written to stdout.
--loglevel critical | error | warning | info | debug | notset
Control the verbosity of the script by setting the log level. Critical
is the least verbose and notset is the most verbose.
The default loglevel is {default_loglevel}.
These values correspond directly to the python logging module levels.
(i.e. https://docs.python.org/3/howto/logging.html#logging-levels)
-h
--help
print this message
'''
def print_help():
print(help, file=sys.stderr)
class RequiredOptions:
'''Just something to keep track of required options'''
def __init__(self, options=[]):
self.required_options = options
def add(self, option):
if option not in self.required_options:
self.required_options.append(option)
def resolve(self, option):
if option in self.required_options:
self.required_options.remove(option)
def optionsResolved(self):
if len(self.required_options):
return False
else:
return True
def main(argv):
# Use the logging module to print non table data. These prints will be sent
# to stderr. The verbosity of the script can by adjusted via the setLevel
# method.
#
logging.getLogger().setLevel(default_loglevel.upper())
try:
opts, args = getopt.getopt(argv,"hc:o:",["help", "com_port=", "output=","loglevel="])
except getopt.GetoptError as e:
print_help()
logging.exception(e)
sys.exit(2)
# This can be overridden with the --output option.
#
output_file = sys.stdout
# As the required options are encountered they are removed from this list.
# After all of the args have been processed, require_options should be
# empty.
#
required_options = RequiredOptions([ 'com_port' ])
for opt, arg in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
elif opt in ("-o", "--output"):
output_file = open(arg, 'w')
elif opt in ("-c", "--com_port"):
com_port = arg
required_options.resolve('com_port')
elif opt in ("--loglevel"):
# Convert to uppercase
#
loglevel = arg.upper()
logging.getLogger().setLevel(loglevel)
else:
print_help()
# Verify that all of the required options have been specified
#
if not required_options.optionsResolved():
print_help()
logging.error("The following required options were not specified:" + ' '.join(required_options.required_options))
# indicate that there was an error by returning a non-zero value.
#
sys.exit(1)
# Now do your work
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')
logging.critical('critical message')
if __name__ == "__main__":
main(sys.argv[1:])
I would just create global variable like argbit and use bitwise operation instead of flags for each arg. I used like:
argbit=1
for each arg loop:
case arg1: #mandatory
argbit <<= 1
do stuff and break
case arg2: #optional
do stuff and break
now based on your args it will be left shifted so at end just check its value
if argbit != value:
usage_and_exit()
if you have two mandatory args its value will be 4 like 2 ^ n.
Ran into the same problem, here's how I solved it
try:
required_argument1 # If argument is missing, it will error, we
# catch that error below in -except
required_argument2
required_argument3
except Error as e: # Whatever your error is (mine was a keyError)
print( 'argument ' + str(e) + is missing )

Python argpase: Handling unknown amount of parameters/options/etc

in my script I try to wrap the bazaar executable. When I read certain options meant for bzr my script will react on that. In any case all arguments are then given to the bzr executable. Of course I don't want to specify all arguments that bzr can handle inside
my script.
So, is there a way to handle an unknown amount of arguments with argpase?
My code currently looks like this:
parser = argparse.ArgumentParser(help='vcs')
subparsers = parser.add_subparsers(help='commands')
vcs = subparsers.add_parser('vcs', help='control the vcs',
epilog='all other arguments are directly passed to bzr')
vcs_main = vcs.add_subparsers(help='vcs commands')
vcs_commit = vcs_main.add_parser('commit', help="""Commit changes into a
new revision""")
vcs_commit.add_argument('bzr_cmd', action='store', nargs='+',
help='arugments meant for bzr')
vcs_checkout = vcs_main.add_parser('checkout',
help="""Create a new checkout of an existing branch""")
The nargs option allows as many arguments as I want of course. But not another unknown optional argument (like --fixes or --unchanged).
The simple answer to this question is the usage of the argparse.ArgumentParser.parse_known_args method. This will parse the arguments that your wrapping script knowns about and ignore the others.
Here is something I typed up based on the code that you supplied.
# -*- coding: utf-8 -*-
import argparse
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', help='commands')
vcs = subparsers.add_parser('vcs', help='control the vcs')
vcs_main = vcs.add_subparsers(dest='vcs_command', help='vcs commands')
vcs_commit = vcs_main.add_parser('commit',
help="Commit changes into a new revision")
vcs_checkout = vcs_main.add_parser('checkout',
help="Create a new checkout of an "
"existing branch")
args, other_args = parser.parse_known_args()
if args.command == 'vcs':
if args.vcs_command == 'commit':
print("call the wrapped command here...")
print(" bzr commit %s" % ' '.join(other_args))
elif args.vcs_command == 'checkout':
print("call the wrapped command here...")
print(" bzr checkout %s" % ' '.join(other_args))
return 0
if __name__ == '__main__':
main()

Categories

Resources