You could potentially take advantage of associative arrays in bash to help obtain your goal.
declare - A opts = ($(getopts.py $ @))
cd $ {
opts[dir]
}
complex_function $ {
opts[append]
}
$ {
opts[overwrite]
}
$ {
opts[recurse]
}\
$ {
opts[verbose]
}
$ {
opts[args]
}
To make this work, getopts.py
should be a python script that parses and sanitizes your arguments. It should print a string like the following:
[dir] = /tmp [append] = foo
[overwrite] = bar[recurse] = baz[verbose] = fizzbuzz[args] = "a b c d"
Returned from getopts.py
:
[__error__] = true
If you would rather work with the exit code from getopts.py
, you could play with eval
:
getopts = $(getopts.py $ @) || exit 1
eval declare - A opts = ($getopts)
Alternatively:
getopts = $(getopts.py $ @)
if [
[$ ? -ne 0]
];
then
exit 1;
fi
eval declare - A opts = ($getopts)
bashparse.py
#!/usr/bin/env python import optparse, sys from pipes import quote '' ' Uses Python 's optparse library to simplify command argument parsing. Takes in a set of optparse arguments, separated by newlines, followed by command line arguments, as argv[2] and argv[3: ] and outputs a series of bash commands to populate associated variables. '' ' class _ThrowParser(optparse.OptionParser): def error(self, msg): "" "Overrides optparse's default error handling and instead raises an exception which will be caught upstream "" " raise optparse.OptParseError(msg) def gen_parser(usage, opts_ls): '' 'Takes a list of strings which can be used as the parameters to optparse' s add_option function. Returns a parser object able to parse those options '' ' parser = _ThrowParser(usage = usage) for opts in opts_ls: if opts: # yes, I know it 's evil, but it' s easy eval('parser.add_option(%s)' % opts) return parser def print_bash(opts, args): '' 'Takes the result of optparse and outputs commands to update a shell' '' for opt, val in opts.items(): if val: print('%s=%s' % (opt, quote(val))) print("set -- %s" % " ".join(quote(a) for a in args)) if __name__ == "__main__": if len(sys.argv) < 2: sys.stderr.write("Needs at least a usage string and a set of options to parse") sys.exit(2) parser = gen_parser(sys.argv[1], sys.argv[2].split('\n')) (opts, args) = parser.parse_args(sys.argv[3: ]) print_bash(opts.__dict__, args)
Example usage:
#!/bin/bash usage = "[-f FILENAME] [-t|--truncate] [ARGS...]" opts = ' "-f" "-t", "--truncate", action = "store_true" ' echo "$(./bashparse.py " $usage " " $opts " " $ @ ")" eval "$(./bashparse.py " $usage " " $opts " " $ @ ")" echo echo OUTPUT echo $f echo $ @ echo $0 $2
Which, if run as: ./run.sh one -f 'a_filename.txt' "two' still two" three
outputs the following (notice that the internal positional variables are still correct):
f = a_filename.txt set--one 'two' "'" ' still two' three OUTPUT a_filename.txt one two ' still two three . / run.sh two ' still two
The original premise of my question assumes that delegating to Python is the right approach to simplify argument parsing. If we drop the language requirement we can actually do a decent job* in Bash, using getopts
and a little eval
magic:
main() {
local _usage = 'foo [-a] [-b] [-f val] [-v val] [args ...]'
eval "$(parse_opts 'f:v:ab')"
echo "f=$f v=$v a=$a b=$b -- $#: $*"
}
main "$@"
The ArgumentParser object will hold all the information necessary to parse the command line into Python data types.,This page contains the API reference information. For a more gentle introduction to Python command-line parsing, have a look at the argparse tutorial.,In a script, parse_args() will typically be called with no arguments, and the ArgumentParser will automatically determine the command-line arguments from sys.argv.,Normally, when you pass an invalid argument list to the parse_args() method of an ArgumentParser, it will exit with error info.
import argparse
parser = argparse.ArgumentParser(description = 'Process some integers.')
parser.add_argument('integers', metavar = 'N', type = int, nargs = '+',
help = 'an integer for the accumulator')
parser.add_argument('--sum', dest = 'accumulate', action = 'store_const',
const = sum,
default = max,
help = 'sum the integers (default: find the max)')
args = parser.parse_args()
print(args.accumulate(args.integers))
$ python prog.py - h
usage: prog.py[-h][--sum] N[N...]
Process some integers.
positional arguments:
N an integer
for the accumulator
options:
-h, --help show this help message and exit
--sum sum the integers(
default: find the max)
$ python prog.py 1 2 3 4 4 $ python prog.py 1 2 3 4--sum 10
$ python prog.py a b c
usage: prog.py[-h][--sum] N[N...]
prog.py: error: argument N: invalid int value: 'a'
>>> parser = argparse.ArgumentParser(description = 'Process some integers.')
>>> parser.add_argument('integers', metavar = 'N', type = int, nargs = '+',
...help = 'an integer for the accumulator') >>>
parser.add_argument('--sum', dest = 'accumulate', action = 'store_const',
...
const = sum,
default = max,
...help = 'sum the integers (default: find the max)')
March 12, 2018 at 9:22 pm,March 12, 2018 at 6:00 pm,March 12, 2018 at 5:23 pm,March 12, 2018 at 4:15 pm
You can absolutely hardcode paths if you wish. If you’re using a Jupyter Notebook this is also advisable. For any other readers interesting in trying this approach I would suggest taking this code:
# construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i", "--input", required = True, help = "path to input image") ap.add_argument("-o", "--output", required = True, help = "path to output image") args = vars(ap.parse_args())
And replacing it with:
args = {
"input": "path/to/input_image.jpg",
"output": "path/to/output_image.jpg"
}
Using “sudo” will install the package into the system Python virtual environment which you do not want. Try this instead:
$ workon your_env_name $ pip install imutils
Are you using Python virtual environments? If so, make sure you install it into the proper virtual environment:
$ workon your_env_name $ pip install imutils