~tpapastylianou/process_optargs

A super-simple sourceable function for processing commandline options and arguments in bash

5f04e31 Updated README and source doc to reflect more usage edge-cases

2 months ago

5f04e31 Updated README and source doc to reflect more usage edge-cases

2 months ago

#process_optargs

A super-simple sourceable function for processing commandline options and arguments in bash

Note: Main development for this project is done on sourcehut; however, given the visibility and social nature of github's starring system, if you appreciate this project and would like to express your appreciation and/or promote it on github, then please feel free to star it on Github, here.

#Usage:

Source the process_optargs, error, warning, and is_in functions from the sourceable_functions directory into your own scripts, and then call them as instructed below.

See at the bottom of this file for a simple example.

#Using process_optargs with your own function

process_optargs is used inside a (caller) function (or script) to parse its inputs into options and positional arguments.

The intended way to call process_optargs is with "$@" as the argument, which effectively passes the caller function's inputs down to this function for processing. If an error occurs during the processing of arguments, it will return a non-zero status, which can be intercepted by the caller as appropriate.

This function expects the following variables to be declared and initialized locally in the caller function:

  • An associative array called OPTIONS, initialized as empty
  • An indexed array called ARGS, initialized as empty
  • An indexed array called VALID_FLAG_OPTIONS, initialized as below.
  • An indexed array called VALID_KEYVAL_OPTIONS, initialized as below
  • A variable called COMMAND_NAME, initialized with the caller's name.

The COMMAND_NAME variable should be set to the name of the caller function/command; its purpose is to be used internally to identify the caller by name, in the case of error messages. If it is left empty, a default ("The Caller") is used.

The VALID_FLAG_OPTIONS and VALID_KEYVAL_OPTIONS arrays is where one should specify all flag or keyval options respectively, which are valid options for the caller function. Individual elements of these arrays could be options in either 'short' (e.g. -o), 'long' (e.g. --optionname), or 'short/long' format (e.g. -o/--optionname). These arrays are inspected internally, and serve as a way to confirm whether any flag and key/value options passed to the caller function were valid options for that caller. If the caller takes no flag arguments, set the VALID_FLAG_OPTIONS array to the empty array (i.e. VALID_FLAG_OPTIONS=() ); and similarly for the VALID_KEYVAL_OPTIONS array.

Note that you do not specify valid 'values' in this context, only 'keys'. Any validation of the parsed flags and key/value pairs obtained as options via process_optargs should be performed by the caller after the call to process_optargs. An example which also demonstrates a simple validation scenario is shown below.

The OPTIONS and ARGS arrays should ideally be initialized as empty. If not, process_args will give a warning, but continue anyway. This allows the caller to initialize them with predetermined options / args before the call to process_optargs. process_optargs will then simply append any options and arguments detected to these arrays.

After process_optargs has exited, the OPTIONS associative array in the caller function will have been populated with key/value pairs; in the case of key/value style options, the keys of the associative array will correspond to valid 'option keys' (as specified in the VALID_KEYVAL_OPTIONS array) in either 'short' or 'long' form (with dashes included), depending on the particular form that what was provided to the caller.

Similarly, in the case of flag options, the keys of the OPTIONS associative array will correspond to valid 'option flags' in either 'short' or 'long' form (with dashes included), depending on the particular form that was provided to the caller, and the value always set to 'On'. In this way, you can simply check if a flag appears as a key in the associative array to see if it has been provided or not (if it was not provided, it will simply not exist in the associative array; i.e. do NOT expect it to exist with a value of 'Off' -- in other words, the value of flag-based options will always be set to 'On' but otherwise it is something that can be safely ignored).

If an option in either short or long form appears in both the VALID_FLAG_OPTIONS and VALID_KEYVAL_OPTIONS arrays, process_optargs will exit with an error.

Similar to OPTIONS, after process_optargs has exited, the ARGS array in the caller function will have been populated with positional arguments, i.e. the list of "non-option" inputs passed to the function.

Note that, unlike other argument parsing tool conventions, the 0-index element of the ARGS array will denote the first proper argument passed to the function, and NOT the name of the command (or function) used to call process_optargs. However, if you did want this behaviour for whatever reason, since the command (or function) name used to call process_optargs is meant to be captured in your manually specified COMMAND_NAME variable before the call to process_optargs, you can easily "append" this as the first element (i.e. at the 0-index position) of the ARGS array, by simply overwriting the freshly populated ARGS array as follows:

ARGS=( "$COMMAND_NAME" "${ARGS[@]}" )

#Calling your custom function with options and input arguments

At the point of use, when passing inputs to your caller function, you can combine short flags together (e.g. -abc instead of -a -b -c), use short keyval options with or without a space (e.g. -d1 or -d 1 ), and long keyval options using either a space or an equals sign without spaces (i.e. both --name George and --name=George are fine).

Notes:

  • As is typical of many unix tools, the special value -- causes any subsequent inputs to be interpreted explicitly as arguments (i.e. even if they start with dashes and are valid option names).

  • A single dash (i.e. -) by itself is not special in any way, and is treated as a normal argument (this is desired behaviour; many unix programs which expect a filename as an argument, traditionally accept - as a special "filename", denoting that the input is to be read from the stdin instead.)

  • If a keyval option is provided in both short and long form at the same time, process_optargs will exit with an error, to prevent ambiguity.

  • If a flag-based option is provided in both short and long forms, while in principle there is no potential for ambiguity (since they would both simply be set to 'On'), in practice process_optargs will also treat this as an error, to prevent situations where you'd end up with the same option appearing twice in the associative array (and thus requiring validation a second time, which could waste computational time, or even cause unintended bugs).

  • Contrary to the above, if a keyval or flag type option is provided two or more times, but in the same form (i.e. all short, or all long), then this is a valid invocation; the value used for that option is the one given last. This is intentional behaviour, since occasionally it is necessary to override previous options in this manner (e.g. in the case of aliases).

  • Since you cannot have spaces surrounding the equals sign when assigning a value to a key/value style option, specifying --optioname= as an input assigns the empty string as a valid value to that option. If you want to pass the empty string as a value using a key/value style option without an equals sign, then you have to do so explicitly, e.g. --optionname "".

  • Similarly, with short-form options, an empty string must be passed as -i "" explicitly; passing -i"" is invalid and won't work (since -i"" will get simplified to -i by the shell)

#The is_in, error, and warning functions

proces_optargs depends on the error, warning, and is_in functions, also included in the sourceable_functions directory (i.e. these need to be sourced alongside process_optargs in your project, for process_optargs to work). However, these functions are interesting in their own right, and you may well find them useful for validating inputs in your own script. Therefore, these are also described briefly below.

Obviously, if you prefer sourcing everything in a single line instead of sourcing each of the four functions individually, feel free to concatenate all of them to a single file and source only that in your project instead.

#is_in

Given N input arguments, this function checks if the 1st input reoccurs in the remaining N-1 arguments.

The intended way to call this function is with an 'exploded' array variable as the second argument, effectively checking if the first input is a member of the array, e.g.:

Elements=( 1 3 5 7 9 )
is_in 5 "${Elements[@]}"   # succeeds
is_in 6 "${Elements[@]}"   # fails

#error

A useful, 'libstderred.so' compatible function for safely echoing error messages to the stderr stream in red. E.g.:

error "This will be printed to the stderr stream in red"

Note: If you don't have libstderred installed, this will still work but errors will not be coloured in red.

#warning

Same as error, but printed in blue instead of red. E.g.:

warning "This will be printed to the stderr stream in blue"

Depends on the error function (i.e., it needs to be sourced alongside)

#Example use:

source /path/to/process_optargs
source /path/to/error
source /path/to/warning
source /path/to/is_in

function myfunction () {    # The function whose options/args we want to process

 # Initialise the necessary variables that will be checked / populated by process_optargs
   local -A OPTIONS=()
   local -a ARGS=()
   local -a VALID_FLAG_OPTIONS=( -h/--help -v --version )    # note -v and --version represent separate flags here! (e.g. '-v' could be for 'verbose')
   local -a VALID_KEYVAL_OPTIONS=( -r/--repetitions )
   local COMMAND_NAME="myfunction"

 # Perform the processing to populate the OPTIONS and ARGS arrays.
   process_optargs "$@" || exit 1

 # Validate parsed options and arguments
   if is_in '-h' "${!OPTIONS[@]}" || is_in '--help' "${!OPTIONS[@]}"
   then display_help
   fi

   if   is_in '-r'            "${!OPTIONS[@]}"; then REPS="${OPTIONS[-r]}"
   elif is_in '--repetitions' "${!OPTIONS[@]}"; then REPS="${OPTIONS[--repetitions]}"
   fi

   if   test "${#ARGS[@]}" -lt 1
   then error "myfunction requires at least one non-option arguments"
        exit 1
   fi

   # ...etc
}

# Now you can call your function with options / input arguments, e.g.:
myfunction "hello" --repetitions 5