using python to parse complex arguments to shell script

  • Last Update :
  • Techknowledgy :

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 "$@"

Suggestion : 2

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)')

Suggestion : 3

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