Parsing bash arguments

If you've ever programmed in bash, then you know that command line arguments are accessed numerically with $1 being the first argument, $2 the second, and so on.For years I've written scripts and just accessed the arguments by their position. This means that if I want to skip an argument, I have to pass an empty string. It also means I must not mix up the order of the arguments I pass!

I can finally put those days behind me if I so choose and use switches! A search online, reveals that the question of how to do this is not new and has been answered many times over, but I wanted to abstract the argument parsing code and get it out of my scripts. As I got my first version working, I found that someone else had already done this, but even that code didn't work exactly like I wanted mine too, so I kept working because there's one key bit that everyone seems to ignore. We should be able to use switches with the functions contained in our scripts too!

My Goals 

I wanted something that can
  • parse arguments passed to functions as well as scripts
  • take input in multiple forms:
    • --key value
    • -k value
    • -kVALUE
    • key=value
  • tell me which switches had been used
  • simulateously query multiple aliases that refer to a single switch (eg. -t and --text)
  • convert everything to easy-to-use variables on the fly
  • avoid interference with variable/function names in the primary script
  • expose some simple functions that make all this simple
  • minimize the amount of declaration of available switches

Enter argParser.sh

I did this as much for the coding practice as I did to create something I could use. For this reason, I chose not to build on any other existing code out there that accomplishes the same or similar purposes. I did however draw from Maxim Norin's code for object emulation in bash. I know; I'm reinventing the wheel here. In addition to the scripts others have already written, there's a getopts command that simplifies all of this. Feel free to jump over to my code at GitHub at this point and continue with the readme there, or keep reading here.

My argParser.sh script is able to take input in all the forms specified above and make them easy to query. My script accomplishes this by implementing four useful functions that make it easy to work with command line switches passed in the given scope. By "scope" I refer to the entity that makes calls to the functions for querying switches. This entity could be the main body of the script, a function within the main body, or a function that has been called by another function within the main body.

Implementing argParser.sh script is as easy as downloading it to the same directory as your script and then running one command:
source argParser.sh     # implement code for creating argParser "objects"
As soon as argParser.sh is sourced, my script sets up some functions that can be used to query switches associated with the caller.

Utilizing the switches comes down to four functions:
  • argParser.getSwitches
  • argParser.hasSwitches
  • argParser.getMissingSwitches
  • argParser.getArg
  • argParser.setArgVars

 

argParser.getSwitches

The argParser.getSwitches function simply reports which switches were used when the parent script/function was run/called. If a script was called with
$ ./script.sh -x10 --string "hello world" -n 2
then argParser.getSwitches would return a line that reads
-x --string -n
In the same fashion, if a function named myFunc was called like
myFunc x=4 y=2
then argParser.getSwitches would return
x y


argParser.hasSwitches

The argParser.hasSwitches function can be used to test for the usage of specific switches when the script/function was called. It is useful for two things. It can take a list of switches and determine exactly which were used, returning only those. It can also be used in simple true/false tests (in which case it may be disireable to pipe the output to /dev/null). For example argParser.hasSwitches -t --text would specify if either switch was used at all and return a space-delimited list of the ones that were used.

 

argParser.getMissingSwitches

The argParser.getMissingSwitches function can be used to look for missing switches that should have been included. If the switches -t and -n are required, you can check to see which of them were excluded when the script/function was called. For example, say my script was called like this
$ ./script -x -n
and then within my script I called
argParser.getMissingSwitches -t -n
The output of this call would be -t because -t was excluded when the script was called. If multiple switches are missing, they would all be outputted in a space-delimited list. If there are missing switches, this function evaluates to true. Otherwise, it evaluates to false.

argParser.getArg

This function is particularly useful. It can fetch the value of a single switch, or if provided multiple switches, it will check them in order and return the value of the first switch that was used when running the script/function in question. One example use case would be argParser.getArg -t --text which would first check to see if -t was provided. If it was, it would pass the text assocated with -t and evaluate to true; --text would not be checked. If -t was not provided, argParser.getArg would then check to see if --text was provided. If it was, the function would return the text associated with --text and evaluate to true. If neither -t nor --text was provided, the function would simply evaluate to false.

argParser.setArgVars

This function is handy if you just want to get to work. So long as switch names (without leading dashes) conform to the naming requirements for variables, the switches can be converted to variables and have their values assigned to them. For example, say a script was called,
$ ./script.sh -x10 -y3 --op "add"
Then running,
argParser.setArgVars
would set variables named x, y, and op equal to 10, 3, and add respectively.

 

non-Interference

To avoid overwriting variables and functions used in the parent script, argParser.sh prepends argParser to all variables that are not local variables and argParser. to all function names.


Minimizing Declarations in the Parent Script

I noticed some very lengthy declarations of switches in other solutions to this problem. My solution strives to make this very simple. Here are some examples.
# sample call: $ ./script.sh -x10 -y12 -op add 
source argParser.sh
argParser.setArgVars 
# At this point the following variables are set
#   x=10
#   y=12
#   op=add
 
# sample call: $ ./script.sh -n 5 -t hi
# assume that text could have been added with -t or --text and a number with -n or --number
source argParser.sh
myText=$(argParser.getArg -t --text) || echo "No text was provided"             # sets myText = hi
myNum=$(argParser.getArg -n --number) || echo "You failed to provide a number"  # sets myNum = 5
myOp=$(argParser.getArg -o --op) || echo "No op supplied"                       # echos "no op supplied"
As you can see, there need be no lengthy declarations. You can get right to work with the switches that were used.

 

Handling Arguments of Functions within Scripts

This is one particular feature that goes unaddressed in a number of argument parsing solutions I've seen. Why should an argument parser be limited to the script call? Why should it not also be able to handle switches passed to functions within that same script? By enabling extended debugging (happens automatically in argParser.sh), it is possible to inspect the arguments passed to any current function call, even if you're in the context of a different function that has been called directly or indirectly by the function in question. Here's an example of utilizing my argParser within a function:
#!/bin/bash
# filename: script.sh
# desc: argParser function demo

# include the argument parser
source argParser.sh

# show the switches used when running this script
echo -n "Switches passed to script.sh: "
argParser.getSwitches

# create a function that shows its own switches
myFunc() {
 echo -n "Switches passed to myFunc: "
 argParser.getSwitches
}

# make a call to myFunc
myFunc --string "I am in the function now"

# If we make a call to an argParser function again from here we 
# will find that we have left the function context and are back 
# in the script.sh context
echo -n "script.sh context switches: "
argParser.getSwitches
Sample Code Output:
$ ./script.sh -x10 -y
Switches passed to script.sh: -x -y 
Switches passed to myFunc: --string 
script.sh context switches: -x -y

Comments

Popular posts from this blog

Encrypted Ubuntu Installation with "Manual" Partitioning

Router Firmware -- Dumping and Flashing

Flashing TL-WR703N firmware directly to the flash chip