Parsing command line parameters - python

As an example of my problem consider the following situation:
A user launches a program from command line with a syntax similar to this
python prog.py "hi there" hi=Hello there=World!
The output of the program is "Hello World!".
My question refers to the part of parsing the arguments. How can I save the information contained in the "hi=Hello there=World!" part in order to use it? From there I should be able to do something with it.
I don't have the slightest idea of what the parameters are going to be so the solution needs to be as generic as possible.

you have to do something like this... save this as "argtest.py":
import sys
def main(x):
print(x)
if __name__ == "__main__":
print sys.argv #notice this is whatever you put in the arguments
print sys.argv[0] # this is the name of the file you used
main(sys.argv[1])
this will pass the first arg into main()
so if you are running from command line
python argtest.py hello
your output will be
hello
in your main() you would of course define whatever you want to do with the argument
personally i do my parsing in under the if __name__=="__main__": line usually something like arguments = [x.split('=') for x in sys.argv[1:]] #if you want to seperate arguments like "pie=apple fart=stinky"
sys.argv is a list of whatever you put after python argtest.py space seperated
for example if you do python argtest.py apple pie comma poop
sys.argv[1] == 'apple'
sys.argv[2] == 'pie'
sys.argv[3] == 'comma'
sys.argv[4] == 'poop'

Using docopt
install docopt:
$ pip install docopt
Write the code of prog.py:
"""
Usage:
prog.py <greeting> <name>
"""
if __name__ == "__main__":
from docopt import docopt
args = docopt(__doc__)
print args
print "------"
name = args["<name>"]
greeting = args["<greeting>"]
print "{greeting} {name}!".format(greeting=greeting, name=name)
Use it:
First see usage instructions:
$ python prog.py
Usage:
prog.py <greeting> <name>
Then use it:
$ python prog.py Ola Mundi
{'<greeting>': 'Ola',
'<name>': 'Mundi'}
------
Ola Mundi!

I ended up using simple string processing:
import sys
entry = {}
for i in sys.argv[1:]:
pos = i.find("=")
entry[i[:pos]] = i[pos+1:]

Related

Python Command Line Arguments Syntax Error

I am trying to print out the command line arguments, however when I try to have two print statements, I get a syntax error. Is there a reason for that?
This is my terminal input: python3 test.py example_Resume.pdf softwareengineering
Also, how would I deal with a command-line argument that has a space in between?
import sys
def main(argv):
{
print(f"Name of the script : {sys.argv[0]=}")
print(f"Arguments of the script : {sys.argv[1:]=}") // This line errors
}
if __name__ == "__main__":
main(sys.argv[1:])
The best way to deal with command-line arguments is to use argparse package which takes care of shell injection, optional, default arguments.
Documentation: Argpase
Here is a sample I use for projects.
import argparse
# >>>>>>>>>>>>>>>>>>>>>>> ARGUMENT PARSING <<<<<<<<<<<<<<<<<<<<<<<<<<<<
def args():
"""
Used to get the list of arguments paseed to the class
"""
parser = argparse.ArgumentParser()
parser.add_argument('-r','--raw_file',type=str,
help='It is used to get the raw input file for parsing',
required=True)
args = parser.parse_args()
return args.raw_file
def main():
# PARSED RAW FILE
raw_file=self.args()
if __name__ == '__main__':
main()
In addition to what the other commenters pointed out, your issue is with the curly braces.
I tried your code without them:
import sys
def main(argv):
print(f"Name of the script : {sys.argv[0]=}")
print(f"Arguments of the script : {sys.argv[1:]=}")
if __name__ == "__main__":
main(sys.argv[1:])
Curly braces mean something different in Python.
The question about passing the args when you don't use them in main is valid.

How to run a Python file with boolean arguments

I have a .py file with a single function that basically converts a .csv file to a .pkl file. This file, when invoked from the terminal, is supposed to convert the measurements between imperial and metrics depending on whether the input is true or not. For example
python3 file.py imperial_units=True or python3 file.py imperial_units=False and if there are no arguments provided like python3 file.py, imperial units should just default to True:
This is my attempt:
import json, sys
import pandas as pd
def log_to_pickle(units):
...
if units == True: # If units == True, convert all metric to imperial
print('Imperial True')
if imperial_unit == False:
...
elif units == False: # If units == False, convert all imperial to metric
print('Imperial False')
if imperial_unit == True:
...
...
if __name__ == "__main__":
if sys.argv == True or sys.argv == '':
log_to_pickle(True)
else:
log_to_pickle(False)
I've added the print statements inside the if/elif block to see whether my inputs work. I ran python3 file.py imperial_units=True and the output was 'Imperial False'
What am I doing wrong?
There are a few wrong things here but the root cause is that the code doesn't use sys.argv correctly. Try printing sys.argv:
if __name__ == "__main__":
import sys
print(sys.argv)
$ python3 argv.py imperial_units=True
['argv.py', 'imperial_units=True']
You need to parse the command line arguments.
if __name__ == "__main__":
if len(sys.argv) <= 1 or sys.argv[1] == "imperial_units=True":
log_to_pickle(True)
else:
log_to_pickle(False)
Take a look at the argparse package for more robust command line argument handling.
I would use argparse for this type of thing. Use a boolean flag argument. I also like to provide a positive and a negative version for ease of use.
import argparse
...
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog='log-to-pickle')
parser.add_argument('--imperial-units', dest='imperial_units', action='store_true')
parser.add_argument('--no-imperial-units', dest='imperial_units', action='store_false')
parser.set_defaults(imperial_units=True)
args = parser.parse_args()
log_to_pickle(args.imperial_units)
Using argparse you'll get some nice help messages too:
$ python3 file.py -h
usage: log-to-pickle [-h] [--imperial-units] [--no-imperial-units]
optional arguments:
-h, --help show this help message and exit
--imperial-units
--no-imperial-units
Now you can call the application like this:
# These are the same given the default.
$ python3 file.py
$ python3 file.py --imperial-units
Or to do the negative version:
$ python3 file.py --no-imperial-units
If you really want to provide a string argument to the bool flag, then I would make a custom method to transform string to boolean:
import argparse
def bool_arg(val):
return val.lower() in ('y', 'yes', 't', 'true', 'on', '1')
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog='log-to-pickle')
parser.add_argument('--imperial-units', type=bool_arg, default=True)
args = parser.parse_args()
print(args.imperial_units)
Examples:
$ python3 file.py --imperial-units y
True
$ python3 file.py --imperial-units True
True
$ python3 file.py --imperial-units 0
False
sys.argv provides a list of arguments. You can iterate through it to find an argument that contains "imperial_units". You can then process this to get the boolean.
if __name__ == "__main__":
#Find arguments which contain imperial_units
arg = [x.split("=")[1] for x in sys.argv if "imperial_units" in x]
#If the length is 0, no args were found, default to true
#Otherwise use value after = and convert it to bool
arg = True if len(arg) == 0 else bool(arg[0])
log_to_pickle(arg)
argparse results in a more user-friendly program with help text and uses an arguably more standard method of dash-delimited parameters. Since you only want one thing, the unit of measure, I think it is best implemented with a choice flag that includes a default, but you could make a single argument such as --use-metric that defaults to False.
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser(description='CSV to Pickle with unit conversion to metric')
parser.add_argument('-u', '--unit', default='imperial', choices=['imperial', 'metric'],
help='Source unit type (imperial or metric)')
parser.add_argument('from_file', help="CSV file to convert")
parser.add_argument('to_file', help="Converted Pickle file")
args = parser.parse_args()
print(args.unit)
Options for using the program are
$ ./test.py --help
usage: test.py [-h] [-u {imperial,metric}] from_file to_file
CSV to Pickle with unit conversion to metric
positional arguments:
from_file CSV file to convert
to_file Converted Pickle file
optional arguments:
-h, --help show this help message and exit
-u {imperial,metric}, --unit {imperial,metric}
Source unit type (imperial or metric)
$ ./test.py --unit metric aaa.csv aaa.pkl
metric
$ ./test.py -u imperial aaa.csv aaa.plk
imperial
$ ./test.py aaa.csv aaa.pkl
imperial
$ ./test.py --unit=other aaa.csv aaa.pkl
usage: test.py [-h] [-u {imperial,metric}] from_file to_file
test.py: error: argument -u/--unit: invalid choice: 'other' (choose from 'imperial', 'metric')

docopt boolean arg python

I use following args for my script with doctopt
Usage:
GaussianMixture.py --snpList=File --callingRAC=File
Options:
-h --help Show help.
snpList list snp txt
callingRAC results snp
I would like to add an argument that have a conditional consequence on my script : correct my datas or don't correct my datas. Something like :
Usage:
GaussianMixture.py --snpList=File --callingRAC=File correction(--0 | --1)
Options:
-h --help Show help.
snpList list snp txt
callingRAC results snp
correction 0 : without correction | 1 : with correction
And I would like to add in my script an if in some functions
def func1():
if args[correction] == 0:
datas = non_corrected_datas
if args[correction] == 1:
datas = corrected_datas
But I don't know how to write it in the usage neither in my script.
EDIT:
My original answer didn't take into account OP's requirements for --correction to be mandatory. Syntax was incorrect in my original answer. Here's a tested working example:
#!/usr/bin/env python
"""Usage:
GaussianMixture.py --snpList=File --callingRAC=File --correction=<BOOL>
Options:
-h, --help Show this message and exit.
-V, --version Show the version and exit
--snpList list snp txt
--callingRAC results snp
--correction=BOOL Perform correction? True or False. [default: True]
"""
__version__ = '0.0.1'
from docopt import docopt
def main(args):
args = docopt(__doc__, version=__version__)
print(args)
if args['--correction'] == 'True':
print("True")
else:
print("False")
if __name__ == '__main__':
args = docopt(__doc__, version=__version__)
main(args)
Please let me know if this works for you.
Not all options have to have arguments in docopt. In other words, you can use flag arguments instead. This is the most straightforward way to get boolean values from a user. That being said, you can simply do the following.
"""
Usage:
GaussianMixture.py (--correction | --no-correction)
Options:
--correction With correction
--no-correction Without correction
-h --help Show help.
"""
import docopt
if __name__ == '__main__':
args = docopt.docopt(__doc__)
print(args)

Python3: command not found, when running from cli

I am trying to run my python module as a command, however I am always getting the error: command not found.
#!/usr/bin/env python
import sys
import re
from sys import stdin
from sys import stdout
class Grepper(object):
def __init__(self, pattern):
self.pattern = pattern
def pgreper(self):
y = (str(self.pattern))
for line in sys.stdin:
regex = re.compile(y)
x = re.search(regex, line)
if x:
sys.stdout.write(line)
if __name__ == "__main__":
print("hello")
pattern = str(sys.argv[1])
Grepper(pattern).pgreper()
else:
print("nope")
I am sure whether it has something to do with the line:
if __name__ == "__main__":
However I just can't figure it out, this is a new area for me, and it's a bit stressful.
Your script name should have a .py extension, so it should be named something like pgreper.py.
To run it, you need to do either python pgreper.py pattern_string or if it has executable permission, as explained by Gabriel, you can do ./pgreper.py pattern_string. Note that you must give the script path (unless the current directory is in your command PATH); pgreper.py pattern_string will cause bash to print the "command not found" error message.
You can't pass the pattern data to it by piping, IOW, cat input.txt | ./pgreper.py "pattern_string" won't work: the pattern has to be passed as an argument on the command line. I guess you could do ./pgreper.py "$(cat input.txt)" but it'd be better to modify the script to read from stdin if you need that functionality.
Sorry, I didn't read the body of your script properly. :embarrassed:
I now see that your pgreper() method reads data from stdin. Sorry if the paragraph above caused any confusion.
By way of apology for my previous gaffe, here's a slightly cleaner version of your script.
#! /usr/bin/env python
import sys
import re
class Grepper(object):
def __init__(self, pattern):
self.pattern = pattern
def pgreper(self):
regex = re.compile(self.pattern)
for line in sys.stdin:
if regex.search(line):
sys.stdout.write(line)
def main():
print("hello")
pattern = sys.argv[1]
Grepper(pattern).pgreper()
if __name__ == "__main__":
main()
else:
print("nope")
Make sure you have something executable here : /usr/bin/env.
When you try to run your python module as a command, it will call this as an interpreter. You may need to replace it with /usr/bin/python or /usr/bin/python3 if you don't have an env command.
Also, make sure your file is executable : chmod +x my_module.py and try to run it with ./my_module.py.

What's the best way to parse command line arguments? [duplicate]

This question already has answers here:
How to read/process command line arguments?
(22 answers)
Closed 8 months ago.
What's the easiest, tersest, and most flexible method or library for parsing Python command line arguments?
argparse is the way to go. Here is a short summary of how to use it:
1) Initialize
import argparse
# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')
2) Add Arguments
# Required positional argument
parser.add_argument('pos_arg', type=int,
help='A required integer positional argument')
# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
help='An optional integer positional argument')
# Optional argument
parser.add_argument('--opt_arg', type=int,
help='An optional integer argument')
# Switch
parser.add_argument('--switch', action='store_true',
help='A boolean switch')
3) Parse
args = parser.parse_args()
4) Access
print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)
5) Check Values
if args.pos_arg > 10:
parser.error("pos_arg cannot be larger than 10")
Usage
Correct use:
$ ./app 1 2 --opt_arg 3 --switch
Argument values:
1
2
3
True
Incorrect arguments:
$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'
$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10
Full help:
$ ./app -h
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
Optional app description
positional arguments:
pos_arg A required integer positional argument
opt_pos_arg An optional integer positional argument
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG An optional integer argument
--switch A boolean switch
This answer suggests optparse which is appropriate for older Python versions. For Python 2.7 and above, argparse replaces optparse. See this answer for more information.
As other people pointed out, you are better off going with optparse over getopt. getopt is pretty much a one-to-one mapping of the standard getopt(3) C library functions, and not very easy to use.
optparse, while being a bit more verbose, is much better structured and simpler to extend later on.
Here's a typical line to add an option to your parser:
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
It pretty much speaks for itself; at processing time, it will accept -q or --query as options, store the argument in an attribute called query and has a default value if you don't specify it. It is also self-documenting in that you declare the help argument (which will be used when run with -h/--help) right there with the option.
Usually you parse your arguments with:
options, args = parser.parse_args()
This will, by default, parse the standard arguments passed to the script (sys.argv[1:])
options.query will then be set to the value you passed to the script.
You create a parser simply by doing
parser = optparse.OptionParser()
These are all the basics you need. Here's a complete Python script that shows this:
import optparse
parser = optparse.OptionParser()
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
options, args = parser.parse_args()
print 'Query string:', options.query
5 lines of python that show you the basics.
Save it in sample.py, and run it once with
python sample.py
and once with
python sample.py --query myquery
Beyond that, you will find that optparse is very easy to extend.
In one of my projects, I created a Command class which allows you to nest subcommands in a command tree easily. It uses optparse heavily to chain commands together. It's not something I can easily explain in a few lines, but feel free to browse around in my repository for the main class, as well as a class that uses it and the option parser
Using docopt
Since 2012 there is a very easy, powerful and really cool module for argument parsing called docopt. Here is an example taken from its documentation:
"""Naval Fate.
Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)
So this is it: 2 lines of code plus your doc string which is essential and you get your arguments parsed and available in your arguments object.
Using python-fire
Since 2017 there's another cool module called python-fire. It can generate a CLI interface for your code with you doing zero argument parsing. Here's a simple example from the documentation (this small program exposes the function double to the command line):
import fire
class Calculator(object):
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
From the command line, you can run:
> calculator.py double 10
20
> calculator.py double --number=15
30
The new hip way is argparse for these reasons. argparse > optparse > getopt
update: As of py2.7 argparse is part of the standard library and optparse is deprecated.
I prefer Click. It abstracts managing options and allows "(...) creating beautiful command line interfaces in a composable way with as little code as necessary".
Here's example usage:
import click
#click.command()
#click.option('--count', default=1, help='Number of greetings.')
#click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
It also automatically generates nicely formatted help pages:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
Pretty much everybody is using getopt
Here is the example code for the doc :
import getopt, sys
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
except getopt.GetoptError:
# print help information and exit:
usage()
sys.exit(2)
output = None
verbose = False
for o, a in opts:
if o == "-v":
verbose = True
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-o", "--output"):
output = a
So in a word, here is how it works.
You've got two types of options. Those who are receiving arguments, and those who are
just like switches.
sys.argv is pretty much your char** argv in C. Like in C you skip the first element which is the name of your program and parse only the arguments : sys.argv[1:]
Getopt.getopt will parse it according to the rule you give in argument.
"ho:v" here describes the short arguments : -ONELETTER. The : means that -o accepts one argument.
Finally ["help", "output="] describes long arguments ( --MORETHANONELETTER ).
The = after output once again means that output accepts one arguments.
The result is a list of couple (option,argument)
If an option doesn't accept any argument (like --help here) the arg part is an empty string.
You then usually want to loop on this list and test the option name as in the example.
I hope this helped you.
Use optparse which comes with the standard library. For example:
#!/usr/bin/env python
import optparse
def main():
p = optparse.OptionParser()
p.add_option('--person', '-p', default="world")
options, arguments = p.parse_args()
print 'Hello %s' % options.person
if __name__ == '__main__':
main()
Source: Using Python to create UNIX command line tools
However as of Python 2.7 optparse is deprecated, see: Why use argparse rather than optparse?
Lightweight command line argument defaults
Although argparse is great and is the right answer for fully documented command line switches and advanced features, you can use function argument defaults to handles straightforward positional arguments very simply.
import sys
def get_args(name='default', first='a', second=2):
return first, int(second)
first, second = get_args(*sys.argv)
print first, second
The 'name' argument captures the script name and is not used. Test output looks like this:
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20
For simple scripts where I just want some default values, I find this quite sufficient. You might also want to include some type coercion in the return values or command line values will all be strings.
Just in case you might need to, this may help if you need to grab unicode arguments on Win32 (2K, XP etc):
from ctypes import *
def wmain(argc, argv):
print argc
for i in argv:
print i
return 0
def startup():
size = c_int()
ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
ref = c_wchar_p * size.value
raw = ref.from_address(ptr)
args = [arg for arg in raw]
windll.kernel32.LocalFree(ptr)
exit(wmain(len(args), args))
startup()
Argparse code can be longer than actual implementation code!
That's a problem I find with most popular argument parsing options is that if your parameters are only modest, the code to document them becomes disproportionately large to the benefit they provide.
A relative new-comer to the argument parsing scene (I think) is plac.
It makes some acknowledged trade-offs with argparse, but uses inline documentation and wraps simply around main() type function function:
def main(excel_file_path: "Path to input training file.",
excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
existing_model_path: "Path to an existing model to refine."=None,
batch_size_start: "The smallest size of any minibatch."=10.,
batch_size_stop: "The largest size of any minibatch."=250.,
batch_size_step: "The step for increase in minibatch size."=1.002,
batch_test_steps: "Flag. If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."
pass # Implementation code goes here!
if __name__ == '__main__':
import plac; plac.call(main)
I prefer optparse to getopt. It's very declarative: you tell it the names of the options and the effects they should have (e.g., setting a boolean field), and it hands you back a dictionary populated according to your specifications.
http://docs.python.org/lib/module-optparse.html
I think the best way for larger projects is optparse, but if you are looking for an easy way, maybe http://werkzeug.pocoo.org/documentation/script is something for you.
from werkzeug import script
# actions go here
def action_foo(name=""):
"""action foo does foo"""
pass
def action_bar(id=0, title="default title"):
"""action bar does bar"""
pass
if __name__ == '__main__':
script.run()
So basically every function action_* is exposed to the command line and a nice
help message is generated for free.
python foo.py
usage: foo.py <action> [<options>]
foo.py --help
actions:
bar:
action bar does bar
--id integer 0
--title string default title
foo:
action foo does foo
--name string
consoleargs deserves to be mentioned here. It is very easy to use. Check it out:
from consoleargs import command
#command
def main(url, name=None):
"""
:param url: Remote URL
:param name: File name
"""
print """Downloading url '%r' into file '%r'""" % (url, name)
if __name__ == '__main__':
main()
Now in console:
% python demo.py --help
Usage: demo.py URL [OPTIONS]
URL: Remote URL
Options:
--name -n File name
% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'
% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
Here's a method, not a library, which seems to work for me.
The goals here are to be terse, each argument parsed by a single line, the args line up for readability, the code is simple and doesn't depend on any special modules (only os + sys), warns about missing or unknown arguments gracefully, use a simple for/range() loop, and works across python 2.x and 3.x
Shown are two toggle flags (-d, -v), and two values controlled by arguments (-i xxx and -o xxx).
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
# Parse command line
skip = 0
for i in range(1, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))
The goal of NextArg() is to return the next argument while checking for missing data, and 'skip' skips the loop when NextArg() is used, keeping the flag parsing down to one liners.
I extended Erco's approach to allow for required positional arguments and for optional arguments. These should precede the -d, -v etc. arguments.
Positional and optional arguments can be retrieved with PosArg(i) and OptArg(i, default) respectively.
When an optional argument is found the start position of searching for options (e.g. -i) is moved 1 ahead to avoid causing an 'unexpected' fatal.
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
def PosArg(i):
'''Return positional argument'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
return sys.argv[i]
def OptArg(i, default):
'''Return optional argument (if there is one)'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
if sys.argv[i][:1] != '-':
return True, sys.argv[i]
else:
return False, default
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
options_start = 3
# --- Parse two positional parameters ---
n1 = int(PosArg(1))
n2 = int(PosArg(2))
# --- Parse an optional parameters ---
present, a3 = OptArg(3,50)
n3 = int(a3)
options_start += int(present)
# --- Parse rest of command line ---
skip = 0
for i in range(options_start, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("Number 1 = %d" % n1)
print("Number 2 = %d" % n2)
print("Number 3 = %d" % n3)
print("Debug = %d" % debug)
print("verbose = %d" % verbose)
print("infile = %s" % infile)
print("outfile = %s" % outfile)

Categories

Resources