Skip to content

Latest commit

 

History

History
569 lines (449 loc) · 15.6 KB

README.md

File metadata and controls

569 lines (449 loc) · 15.6 KB

bash_option_parser

Capabilities

  • Supports sub option parsing
  • Supports alias names for options
  • Supports optional arguments
  • Supports variable arguments
  • Prints usage and error messages.
  • Easy to use

Requirements

  • Needs at least bash version 4.

Index

Example

This is a simple example to show basic functionailty. Checkout example for a better understanding.

Consider a sample command named sample. It accepts options as per the following usage format:

sample <name1> <name2> [<name3>]
    -s|--search|--find arg1 arg2 [arg3]
    -m|--make|--create

To tell the parser that sample needs 2 arguments and 1 optional argument i.e. <name1>, <name2> and [<name3>], we pass a string like '1 1 -1'.

In '1 1 -1', 1 denotes a mandatory arg and -1 denotes an optional arg. This is called schema in this parser. Similarly, ..., 'S' and 0 are for variable args, suboption and no args respectively. See complete explaination for more details.

We can parse options as per the above criteria as follows:

#! /usr/bin/env bash

source ./option_parser

parse_options \
	'sample'                               '1 1 -1'     \
	'-s'        , '--search' , '--find'    '1 1 -1'     \
	'-m'        , '--make'   , '--create'  '0'          \
	';' \
	"$@"

retval=$?

if [ $retval -ne 0 ]; then
	# will print a meaningful message on error
	option_parser_error_msg "$retval"
	# will print usage of command
	print_usage 'sample'
	exit 1
fi

#
# Now if some options are passed,
# they are stored in OPTIONS[] as the key.
# So let's check if '-s' was passed
#

if [ -n "${OPTIONS[-s]}" ]; then

	echo "-s option was passed"

	# cnt_args_passed contains the number of args the option received
	cnt_args_passed=${ARG_CNT[-s]:-0}

	# print the first arg:
	echo "${ARGS[-s,0]}"

	# print the second arg:
	echo "${ARGS[-s,1]}"

	#
	# As 3rd arg is optional, we can check if
	# cnt_args_passed is equal to 3
	#

	if [ $cnt_args_passed -eq 3 ]; then
		# print the third arg:
		echo "${ARGS[-s,2]}"
	fi

fi

Schema

Corresponding to every option, a schema is passed which determines the patten in which the option receives arguments. There are 4 characters available to define the schema:
     1 : Means argument is necessary
    -1 : Means argument is optional (this can only be last element in schema)
    ...: Means variable length args (this can only be last element in schema)
     0 : Means doesn't receive any argument
     S : Is a sub-command

So, schema '1 1 1 -1' would mean the option needs 3 args and 4th is optional.
So let's say we have 3 options, -a, -b and -c.
-a needs at least one arg and can receive a second arg optionally.
-b doesn't need any args
-c is a sub-command

    Schema for -a = '1 -1'
    Schema for -b = '0'
    Schema for -c = 'S'

parse_options()

After this functions is called, 3 associative arrays are set to hold information about args. These are OPTIONS, ARG_CNT and ARGS. These names can be changed by using parse_options_detailed() instead.

We will use the above example to explain their usage:

parse_options \
	'sample'                               '1 1 -1'     \
	'-s'        , '--search' , '--find'    '1 1 -1'     \
	'-m'        , '--make'   , '--create'  '0'          \
	';' \
	"$@"

Here the format goes as follows:

parse_options \
	     '-s'        ,        '--search'          ,        '--find'         '1 1 -1'
	<option-name> <comma> <alternative-name-1> <comma> <alternative-name-2> <schema>
	<semicolon>
	<args passed by user i.e. $@>

In the above setup, we pass options in order like:

parse_options <option-1> <schema> <option-2> <schema> ; $@

First option, i.e. <option-1> must always be the key name that you want to use to denote the program itself. Like in the above example it is sample.

Comma(,) is used to separate alternative names. Semicolon(;) is used to mark the end of options. After semicolon, $@ is passed.

After the call, the following associative arrays are set:

OPTIONS

If an option is passed, key corresponding to it is set in `OPTIONS[]` and it stores the shift count needed to reach that arg.

For example, if we called the sample command as:

sample 1 2 -m -s 1 2 3

It will set OPTIONS array as:

OPTIONS[sample] = 0
OPTIONS[-m] = 2
OPTIONS[-s] = 3

They are generally used just to check if the particular option was passed or not. Like to check if -s is passed, we do [ -n "${OPTIONS[-s]}" ]. Their value which contains the number of shift needed to reach them only comes handy in case of suboptions. To see how this can be used in suboptions, check the code in this example.

One special key, error_opt is used to store any errors that occured while parsing user args. This is used by option_parser_error_msg to print a relevant error message.

ARG_CNT

If the passed option received arguments, this array stores the number of args received corresponding to that options.

For example, if we called the sample command as:

sample 1 2 -m -s 1 2 3

It will set ARG_CNT array as:

ARG_CNT[sample] = 2
ARG_CNT[-m] = # NOT SET BECAUSE IT DIDN'T RECEIVE ANY ARGUMENTS
ARG_CNT[-s] = 3

ARGS

This array is set in a 2D-array-like format to store the args received.

For example, if we called the sample command as:

sample 11 22 -m -s 1 2 3

It will set ARGS array as:

ARGS[sample,0] = 11
ARGS[sample,1] = 22

ARGS[-s,0] = 1
ARGS[-s,1] = 2
ARGS[-s,2] = 3

parse_options() is defined as follows:

parse_options() {
	parse_options_detailed ',' 'OPTIONS' 'ARG_CNT' 'ARGS' '0' ';' '--' 'error_opt' \
	    'OPTION_DATA' 'ALIAS_DATA' "$@"
}

option_parser_error_msg()

This can be used for printing error messages in case the args passed by the user are in wrong format. This function takes the exit status of parse_options as argument and prints the corresponding error message. This uses OPTIONS[error_opt] to check if any error occured.

option_parser_error_msg() is defined as follows:

option_parser_error_msg() {
	option_parser_error_msg_detailed "$1" 'OPTIONS' 'ARG_CNT' 'ARGS' 'error_opt'
}

print_usage()

This prints the usage of the command. It takes one argument which is the name of the program. This should be same as what you passed as first option to parse_options(). Like in the above example, if command is used incorrectly, following code will be called:

print_usage 'sample'

This will print:

sample <arg1> <arg2> [<arg3>] 
    -m|--make|--create 
    -s|--search|--find <arg1> <arg2> [<arg3>] 

print_usage() is defined as follows:

print_usage() {                                                
	print_usage_detailed "$1" 'OPTION_DATA' 'ALIAS_DATA'
}

Arguments accepted by parse_options_detailed(), option_parser_error_msg_detailed() and print_usage_detailed() are explained below. These can be used if you want to change the separator, associative array name, etc.

parse_options_detailed()

param1: Alias Separator

As you can see in the above example, alias names of args are separated by a ','. param1 tells what separator you want to use to separate alias names.

param2: Associative array name for storing passed options

  1. This is the name of the array that would be declared global and associative by the function. All the passed options are stored as keys in it and can be checked by -n or -z in if condition.
  2. If a suboption is encountered, it stores the number of shifts needed to reach the args of suboption as the value corresponding to name of the option as the key.
  3. This name should not be any of the reserved associative array names.

param3: Associative array name for storing argument count of passed options

  1. This is the name of the array that would be declared global and associative by the function. All options that received args have the the arg count stored as value in this array corresponding to name of the option as the key.
  2. This can be used with options that receive optional args and the arg count to be received is not certain.
  3. This name should not be any of the reserved associative array names.

param4: Associative array name for storing arguments of passed options

This is the name of the array that would be declared global and associative by the function. All options that received arguments store them in it in a 2D-array-like format. e.g., if some option -s received 2 args, then the args will be stored as the following keys:

[-s,0]=arg1
[-s,1]=arg2

This name should not be any of the reserved associative array names.

param5: shift count

  1. This is the number of shifts made before parsing args. e.g., If cmdline args were like:
    command -v --new "file" create hello world lalala
    
    and you want to parse args after 'create', so nshift should be 4. If would shift initial 4 args and start parsing after create.
  2. This is useful for parsing subcommands. We get the number of shifts required from value of OPTIONS[] as discussed in param2.

param6: terminator

This is used to mark the end of the options. Like in the above example, ';' is used as the terminator.

param7: No option indicator

This tells to treat following arg as a normal arg even if it's an option.
e.g., Let's say it is '--'.
If there are 2 options -n and -m where -n takes an arg,
it can be that arg received by -n has the name "-m",
then on cmdline, it can be done like:

command -n -- -m

"-m" will be treated as arg to -n
If the arg itself was '--', then it could have been
achieved like:

command -n -- --

param8: error opt

This is the key that would be used in param2 array to store the name of the option which caused the error.

param9: option_data_array_name

This is the "name" of the associative array that would store all the valid options along with their schema. This is meant to be passed to parse_options_detailed() so that it can use option data to print the usage. This name should not be any of the reserved associative array names. e.g.,

parse_options \
  'self'                         '1 1 -1' \
  'c'    , 'create'              'S'      \
  '-e'   , '--eat'               '1 1 -1' \
  '-n'   , '--new'               '1'      \
  '-v'   , '--version' , '--ver' '0'      \
  ';'                                     \
  "$@"

As parse_options() uses the name OPTION_DATA by default, after calling this OPTION_DATA would look like:

OPTION_DATA[self] = '1 1 -1'
OPTION_DATA[c] = 'S'
OPTION_DATA[-e] = '1 1 -1'
OPTION_DATA[-n] = '1'
OPTION_DATA[-v] = '0'

param10: alias_data_array_name

This is the "name" of the associative array that would store all the alias names corresponding to the option in a 2D array like format. This is meant to be passed to parse_options_detailed() so that it can use alias data to print the usage. This name should not be any of the reserved associative array names. e.g.,

parse_options \
  'self'                         '1 1 -1' \
  'c'    , 'create'              'S'      \
  '-e'   , '--eat'               '1 1 -1' \
  '-n'   , '--new'               '1'      \
  '-v'   , '--version' , '--ver' '0'      \
  ';'                                     \
  "$@"

As parse_options() uses the name ALIAS_DATA by default, after calling this ALIAS_DATA would look like:

ALIAS_DATA[self,-1] = 0

ALIAS_DATA[c,-1] = 1 # -1 index stores alias count
ALIAS_DATA[c,0] = create

ALIAS_DATA[-e,-1] = 1
ALIAS_DATA[-e,0] = --eat

ALIAS_DATA[-n,-1] = 1
ALIAS_DATA[-n,0] = --new

ALIAS_DATA[-v,-1] = 2
ALIAS_DATA[-v,0] = --version
ALIAS_DATA[-v,1] = --ver

self key name

  1. The first key that is passed to parse_options is for main command itself.
  2. If the options passed don't belong to a particular option and instead are arg to main command itself, details corresponding to these are stored in param2, param3 and param4.
  3. For example, if this arg was 'self', then in arrays param2, param3 and param4, 'self' would be used as the key to store args to the main arg that don't belong to any particular option.

retval

  1. SUCCESS: Returns 0
  2. FAILURE: It sets the key param8 in param1 array to the option name for which the error occured and returns:
    • 1 : insufficient args are supplied for some particular option
    • 2 : invalid schema passed to function
    • 3 : extra arg supplied

option_parser_error_msg_detailed()

param1: error_code

Error code returned by parse_options_detailed()

param2: options_array_name

This is the same options_array_name that was passed to parse_options_detailed()

param3: args_cnt_array_name

This is the same args_cnt_array_name that was passed to parse_options_detailed()

param4 : args_array_name

This is the same args_array_name that was passed to parse_options_detailed()

print_usage_detailed()

Prints usage as per option data and alias data.

param1: self_key_name

It is the key used to address the main command. This is the first option passed to parse_options_detailed()

param2: option_data_array_name

This is the same name that is passed to parse_options_detailed(). Array with this name is populated by parse_options_detailed() and then can be passed to this function

param3: alias_data_array_name

This is the same name that is passed to parse_options_detailed(). Array with this name is populated by parse_options_detailed() and then can be passed to this function

Reserved associative array names

Following associative array names are reserved and shouldn't be used as custom names for arrays:
  • option_array
  • alias_array