3
\$\begingroup\$

I know that getopts is available but I wanted to write my own as a way of improving my bash. Below I show two function definitions and an example of the use of them in a bash script. Finally I show the output of the script.

Please comment.

#!/bin/bash
# Produce a string variable which can be used by function isoption
# to give calling scripts an easy way to determine what options are
# are provided in its command line regardless of their order.  
# Call this function as:
#
#   mygetopts "$@"
#
# Non-option arguments in the command line of the caller are
# passed back to the caller in array NONOPTARGS and can be used like this:
#
#   name=NONOPTARGS[0]
#
# NOTE: Option globbing is not allowed

function mygetopts ()
{ 
# Initialize a string variable which will be made to contain all
# option names in "$@".  Since this string will be used by function
# isoption a weird variable name is used to avoid collision with
# variable names in calling script since it cannot be made local.

    O1P2T3I4O5N6="-"    # O1P2T3I4O5N6 is avaiable to caller
    declare -i i=0      # i is local to function

    if [[ "$1" == "-h" || "$1" == "--help" ]]; then
        echo "Usage: $0 \"\$@\"" >&2
        return 1
    fi

    for arg in "$@" ; do    # arg will be each positional parameter
        if [[ ${arg:0:1} == - ]]    # if $arg begins with -
            # Pack all option names separated by -
            then O1P2T3I4O5N6=${O1P2T3I4O5N6}${arg:1}-

            else NONOPTARGS[i++]=$arg
        fi
        #echo "O1P2T3I4O5N6  =$O1P2T3I4O5N6"
        #echo "NONOPTARGS = ${NONOPTARGS[@]}"
    done
    return 0
}

function isoption ()
{

    if [[ $O1P2T3I4O5N6 =~ ^.*-${1}-.*$ ]] 
        then return 0
        else return 1 
    fi
}
#!/bin/bash
# Example of using functions mygetopts and isoption to provide easy
# access to all command line options as well as non-option arguments
# regardless of their order.
#
# To use these functions place the file MYFUNCTIONS in your $HOME 
# directory and source it in your bash script before using the 
# functions.  Alternately it can be sourced by your .bash_profile

set +vx         # Change + to - to enable tracing
echo
echo These $# command line arguments are provided: 
echo "$@"

. ~/MYFUNCTIONS # source the function definitions
echo
echo These functions are defined:
compgen -A function # Show what functions are defined
echo
mygetopts "$@"  # Call mygetopts with all command line arguments

echo mygetopts returns this string with all options separated by -
echo O1P2T3I4O5N6 = $O1P2T3I4O5N6   # Show the string of all options

echo Examples of the use of isoption function:
if isoption f; then echo f found ; else echo f Not found; fi
if isoption z; then echo z found ; else echo z Not found; fi
if isoption abcd; then echo abcd found ; else echo abcd Not found; fi
if isoption abcde; then echo abcde found ; else echo abcde Not found; fi
echo
echo Examples of the accessing of non-option arguments:
inputfilename=${NONOPTARGS[0]}
outputfilename=${NONOPTARGS[1]}
echo inputfilename=$inputfilename 
echo outputfilename=$outputfilename
***13:00:47 617 ~/work>testmygetopts -bde -f infile -abcd -x outfile -y

These 7 command line arguments are provided:

-bde -f infile -abcd -x outfile -y

These functions are defined:

  • isoption
  • mygetopts

mygetopts returns this string with all options separated by -

O1P2T3I4O5N6 = -bde-f-abcd-x-y-

Examples of the use of isoption function:

f found
z Not found
abcd found
abcde Not found

Examples of the accessing of non-option arguments:

inputfilename=infile
outputfilename=outfile
\$\endgroup\$
3
\$\begingroup\$

The bash code all looks reasonably fine to me.

The -h and --help output doesn't look right (it will print the actual parameters passed, but should print the list of allowable options/parameters?)

Options like --this-will-break won't work with --will or --break.

I'm not clear if parameters with spaces in them will work, as in: testmygetopts "this is one argument"

I think 'isoption' could be simplifed to just be the [[ ]] expression (since it will return 0 or 1 already).

\$\endgroup\$
2
\$\begingroup\$

declare -i i=0      # i is local to function

Inside a function declare and local do the same thing, but if you're going to write a code comment saying it's local, don't you think the local keyword is more self-documenting? I'd write it…

local -i i=0

if [[ "$1" == "-h" || "$1" == "--help" ]]; then

This is fine, but you don't actually need double quotes inside a double-bracket test. You don't need the double-equals either. That's up to you.

echo "Usage: $0 \"\$@\"" >&2

Use printf and single quotes for better readability. You immediately know the "$@" won't expand in single quotes, and the "$0" gets expanded before printf interpolates it.

printf 'Usage: %s "$@"\n' "$0" >&2

for arg in "$@" ; do    # arg will be each positional parameter

Same as for arg without the in "$@" part. Up to you.

if [[ ${arg:0:1} == - ]]    # if $arg begins with -

More efficiently written:

if [[ $arg = -* ]]

function isoption ()
{

    if [[ $O1P2T3I4O5N6 =~ ^.*-${1}-.*$ ]] 
        then return 0
        else return 1 
    fi
}

A nice bash feature is that return always returns the value of $?, so you could write this:

[[ $O1P2T3I4O5N6 =~ ^.*-${1}-.*$ ]]
return

But if you prefer explicit, your code is fine.


On to the example part:

echo
echo These $# command line arguments are provided: 
echo "$@"

This use of echo is the only thing I think it actually wrong. Echo takes options (-neE), so you should never give it an unsanitized "$@" to output. Use printf here.

printf '%s ' "$@"; echo

And the rest is fine. Very nice job!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.