how to make a generic Bash function that can accept information via positional and named arguments

765 Views Asked by At

I'm writing a large number of Bash functions that are to be capable of acquiring different pieces of information from the command line passed using positional arguments or named arguments (provided by getopts). The idea is that positional arguments are to be used largely for briskness and direct human control while named arguments are to be used largely for clarity and control by other functions. To illustrate how I am thinking about this, consider a function that can convert from one thing to another. This function can be used, broadly speaking, in a quick mode or in an advanced mode. In the quick mode, few arguments are specified and, generally, are positional arguments, while in the advanced mode, many arguments can be specified and, generally, are named arguments. For examples...

quick mode

This mode can be used in a manner such as the following:

function fileName1 fileName2

It converts one file to another using internal assumptions and measurements made autonomously.

advanced mode

This mode can be used in a manner such as the following:

function -i fileName1 -o fileName2 -m -r 100 -v

This mode can be used also in a manner such as the following:

function -m -v fileName1 fileName2 -r 100

Note that -v accepts no argument. It is an option only (specifying something such as verbosity).


So, the logic in this case would be that the first positional argument is assumed to be fileName1 and the second positional argument is assumed to be fileName2 unless either of these file names are specified using the -i or -o options, in which case they are used because they are given higher priority (and the first and second positional arguments are ignored).

I ask for suggestions and guidance in implementing these types of requirements in as general a way as possible because this approach is to be applied to a library of over 150 functions.

3

There are 3 best solutions below

1
On

Be aware that if you use getopts, all positional arguments must come after any options. A second caveat is that if you use getopts to parse arguments to a function, you must reset the global OPTIND variable to 1 after parsing any options. Otherwise I think the solution is straightforward:

argparse.sh

#!/usr/bin/env bash

func() {
    local args=$*
    local input output verbose
    local r="unset" m=0 verbose=0

    while getopts i:o:mr:v opt; do
        case "$opt" in
            i) input="$OPTARG" ;;
            o) output="$OPTARG" ;;
            r) r="$OPTARG" ;;
            m) m=1 ;;
            v) verbose=1 ;;
        esac
    done
    shift $((OPTIND-1))
    OPTIND=1    # reset global variable

    [[ $input ]]  || input=$1
    [[ $output ]] || output=$2

    echo "=== func $args ==="
    echo "input: $input"
    echo "output: $output"
    echo "verbose: $verbose"
    echo "r: $r"
    echo "m: $m"
    echo
}

func fileName1 fileName2
func -i fileName1 -o fileName2 -m -r 100 -v
func -m -v -r 100 fileName1 fileName2

Output

=== func fileName1 fileName2 ===
input: fileName1
output: fileName2
verbose: 0
r: unset
m: 0

=== func -i fileName1 -o fileName2 -m -r 100 -v ===
input: fileName1
output: fileName2
verbose: 1
r: 100
m: 1

=== func -m -v -r 100 fileName1 fileName2 ===
input: fileName1
output: fileName2
verbose: 1
r: 100
m: 1
1
On

I used @getopt@ to parse command line arguments. I do not remember right now if it solves your problem. But it was useful for me.

http://linux.die.net/man/1/getopt

1
On

adapted from http://rsalveti.wordpress.com/2007/04/03/bash-parsing-arguments-with-getopts/

How about always trying to getopts first, and if that fails revert to just using $1/$2/etc.

TEST=
SERVER=
PASSWD=
VERBOSE=
while getopts “ht:r:p:v” OPTION
do
     case $OPTION in
         h)
             usage
             exit 1
             ;;
         t)
             TEST=$OPTARG
             ;;
         r)
             SERVER=$OPTARG
             ;;
         p)
             PASSWD=$OPTARG
             ;;
         v)
             VERBOSE=1
             ;;
         ?)
             usage
             exit
             ;;
     esac
done

if [[ -z $TEST ]] || [[ -z $SERVER ]] || [[ -z $PASSWD ]]
then
     // Set variables from $1/$2/etc....
fi