Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Python to parse complex arguments to shell script

When I'm writing shell scripts, I often find myself spending most of my time (especially when debugging) dealing with argument processing. Many scripts I write or maintain are easily more than 80% input parsing and sanitization. I compare that to my Python scripts, where argparse handles most of the grunt work for me, and lets me easily construct complex option structures and sanitization / string parsing behavior.

I'd love, therefore, to be able to have Python do this heavy lifting, and then get these simplified and sanitized values in my shell script, without needing to worry any further about the arguments the user specified.

To give a specific example, many of the shell scripts where I work have been defined to accept their arguments in a specific order. You can call start_server.sh --server myserver --port 80 but start_server.sh --port 80 --server myserver fails with You must specify a server to start. - it makes the parsing code a lot simpler, but it's hardly intuitive.

So a first pass solution could be something as simple as having Python take in the arguments, sort them (keeping their parameters next to them) and returning the sorted arguments. So the shell script still does some parsing and sanitization, but the user can input much more arbitrary content than the shell script natively accepts, something like:

# script.sh -o -aR --dir /tmp/test --verbose

#!/bin/bash

args=$(order.py "$@")
# args is set to "-a --dir /tmp/test -o -R --verbose"

# simpler processing now that we can guarantee the order of parameters

There's some obvious limitations here, notably that parse.py can't distinguish between a final option with an argument and the start of indexed arguments, but that doesn't seem that terrible.

So here's my question: 1) Is there any existing (Python preferably) utility to enable CLI parsing by something more powerful than bash, which can then be accessed by the rest of my bash script after sanitization, or 2) Has anyone done this before? Are there issues or pitfalls or better solutions I'm not aware of? Care to share your implementation?


One (very half-baked) idea:

#!/bin/bash

# Some sort of simple syntax to describe to Python what arguments to accept
opts='
"a", "append", boolean, help="Append to existing file"
"dir", str, help="Directory to run from"
"o", "overwrite", boolean, help="Overwrite duplicates"
"R", "recurse", boolean, help="Recurse into subdirectories"
"v", "verbose", boolean, help="Print additional information"
'

# Takes in CLI arguments and outputs a sanitized structure (JSON?) or fails
p=$(parse.py "Runs complex_function with nice argument parsing" "$opts" "$@")
if [ $? -ne 0 ]; exit 1; fi # while parse outputs usage to stderr

# Takes the sanitized structure and an argument to get
append=$(arg.py "$p" append)
overwrite=$(arg.py "$p" overwrite)
recurse=$(arg.py "$p" recurse)
verbose=$(arg.py "$p" verbose)

cd $(python arg.py "$p" dir)

complex_function $append $overwrite $recurse $verbose

Two lines of code, along with concise descriptions of the arguments to expect, and we're on to the actual script behavior. Maybe I'm crazy, but that seems way nicer than what I feel like I have to do now.


I've seen Parsing shell script arguments and things like this wiki page on easy CLI argument parsing, but many of these patterns feel clunky and error prone, and I dislike having to re-implement them every time I write a shell script, especially when Python, Java, etc. have such nice argument processing libraries.

like image 705
dimo414 Avatar asked Jul 27 '12 05:07

dimo414


People also ask

How do I pass multiple arguments to a shell script?

To pass multiple arguments to a shell script you simply add them to the command line: # somescript arg1 arg2 arg3 arg4 arg5 … To pass multiple arguments to a shell script you simply add them to the command line: # somescript arg1 arg2 arg3 arg4 arg5 …

Can I use Python for shell scripting?

Python allows you to execute shell commands, which you can use to start other programs or better manage shell scripts that you use for automation. Depending on our use case, we can use os. system() , subprocess. run() or subprocess.

How do I parse bash script arguments?

We can use the getopts program/ command to parse the arguments passed to the script in the command line/ terminal by using loops and switch-case statements. Using getopts, we can assign the positional arguments/ parameters from the command line to the bash variables directly.

Can shell script accept arguments?

Passing arguments. The capability of passing arguments to the script brings dynamic features to the scripts. In shell scripting, the arguments are provided to the script at the time of execution/running of the command.


2 Answers

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"

You could set aside values for checking that the options were able to be properly parsed and sanitized as well.

Returned from getopts.py:

[__error__]=true

Added to bash script:

if ${opts[__error__]}; then
    exit 1
fi

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)
like image 74
Swiss Avatar answered Oct 19 '22 23:10

Swiss


Having the very same needs, I ended up writing an optparse-inspired parser for bash (which actually uses python internally); you can find it here:

https://github.com/carlobaldassi/bash_optparse

See the README at the bottom for a quick explanation. You may want to check out a simple example at:

https://github.com/carlobaldassi/bash_optparse/blob/master/doc/example_script_simple

From my experience, it's quite robust (I'm super-paranoid), feature-rich, etc., and I'm using it heavily in my scripts. I hope it may be useful to others. Feedback/contributions welcome.

like image 25
Carlo Baldassi Avatar answered Oct 20 '22 01:10

Carlo Baldassi