-
Notifications
You must be signed in to change notification settings - Fork 2
Scripts Usage
This document explains the global and common usage of command line scripts as handled and expected by the library.
For a full example and an help for this tutorial, see
the samples/getopts-test.sh
script.
The library tries to define the most classic usage of the command line scripts. A global overview of this could be:
path/to/script.sh -flags((=)arg) --long-opt(=arg) (--) script args
[1] [2(a)] [2(b)] [2(c)] [3]
We can distinguish in this usage three entities that are explained below:
- the program
- some options with or without argument, required or not
- some arguments to pass to the script, required or not
It determines the executed script and, on some systems, the shell script to use. We can distinguish here an installed program and a local script file. In the first case, you will just write the program's name, in the second, you may write the full relative path to the executed file.
mycommand ...
# or
./path/to/mycommand ...
The path of the script may exist from you current working directory, or could be stored
in a directory that is included in the global $PATH
environment variable, which is usually
a collection of system's and user's bin/
directories. This is the case of programs.
Depending on the system you are running on, you may precede the program name or the script path calling explicitly the shell binary to use to parse it. On Darwin systems for instance, you may run something like:
bash path/to/script.sh
# or
sh path/to/script.sh
Most of programs or scripts may accept options. An option will, for instance, enable a special environment variable (like verbosity), select a new working directory etc. Options can accept arguments, optional or required, depending on script definitions.
We can here distinguish two types of "options": the short options, also called flags, and the long options. Most of the time, the short options are just aliases of long options, which name will have a real meaning, for quick script's usage.
A short option is one single letter prefixed by a dash: -a
. It can accept an argument that
can be written in three equivalent ways:
./path/to/script.sh -a argument
./path/to/script.sh -a=argument
./path/to/script.sh -aargument
The first notation here (a space separator) is not allowed for optional arguments.
The short options can be grouped in a single string like -ab
as long as there is no
argument inside the group. An argument can be added to the last option of a group.
For instance, the followings are equivalent:
./path/to/script.sh -a -b=arg -c
./path/to/script.sh -ac -b=arg
./path/to/script.sh -acb=arg
./path/to/script.sh -ab=arg -c
The short options are also called "flags" because they are a single letter and they often define a simple behavior, such as rendering the script verbose, quiet, interactive etc.
A long option is a multi-letters string prefixed by a double-dash: --my-option
. It can
accept an argument just like the short options, with the difference that the argument MUST
be separated from the option name by the equal sign or the space: --my-option=argument
.
If a long option accepts an optional argument, the separator must ONLY be the equal sign.
The long options can NOT be grouped.
The end of command line options can be explicitly written using the special notation --
(a double dash). This is NOT required but can be useful in some special cases with a long
set of options with arguments, to tell the script that the rest of the line will be global
arguments and not options' ones.
For both short and long options accepting an argument, this argument can be surrounded by quotes:
./path/to/script.sh -a "my arg"
This is NOT required, except in the case where the argument contains a space or a special character (like in the example above). Actually, on certain systems this is not required at all, but it is a good practice to ALWAYS use quotes for complicated arguments to be safely system compliant.
Finally, the command line may contains some arguments that will be handled by the script. The arguments may be distinguished from the options or the options arguments. They often represent the "real" things handled by the script's logic while the options are used to define some special behaviors during this logic. The arguments are just some strings written "as is" at the end of the command line. They are NOT required, can be one, two or more.
It is common to use the pipe method in command line to process a suite of programs. The library proposes a method to get any piped content from a previous command to use something like:
echo "my piped content" | ./path/to/script.sh
To get the piped content in a script, use the read_from_pipe()
method which will
load a caught piped content (if so) in the SCRIPT_PIPED_INPUT
global variable.
Working on the library, I first tried to write global specifications about options and arguments handling following common usages and facilities. These rules are:
- options (short or long) and arguments must be mixable (in any order) keeping in mind
that the double-dash (
--
) means the end of options (but not of arguments) - of course, an option's argument must be written after its option's name
- for short and long options:
- if an argument contains special characters or a space, it must be surrounded between quotes (simples or doubles)
- for short options:
- if the argument is required, it can be written just after the option, separated by an equal sign eventually, or as the next argument, separated by a space
- if the argument is optional, it must be written just after the option, separated by an equal sign eventually (no space is allowed as the argument is optional)
- for long options:
- if the argument is required, it can be written separated from the option by an equal sign or a space
- if the argument is optional, it must be written separated from the option by an equal sign (no space is allowed as the argument is optional)
The methods described in this section will load script options in the SCRIPT_OPTS
variable
array and the arguments in the SCRIPT_ARGS
variable array. As it is not possible to reset
the global command line positional parameters inside a function, you need to manually redefine
them using the set
program
as shown below. Doing so, you can be safely sure after these lines to have the correct options,
loaded with their arguments if so (even if they are multi-words) and the script arguments.
The library can rearrange options and arguments to safely prepare and parse them. To do so,
you need to have the new version of the getopt
program
and write something like:
rearrange_script_options_new "$0" "$@"
[ -n "$SCRIPT_PARAMS" ] && eval set -- "$SCRIPT_PARAMS"
If your system or the one currently in use does not seem to have the new getopt
, the
rearrange_script_options_new
method will fallback to the old rearrange_script_options
one (see below).
In its first version, the library embedded its own re-arrangement system to safely prepare and parse options and arguments. This "old" method is still existing and valid. To use it, you need to write something like:
rearrange_script_options "$@"
if [ "${#SCRIPT_OPTS[@]}" -gt 0 ] && [ "${#SCRIPT_ARGS[@]}" -eq 0 ];
then set -- "${SCRIPT_OPTS[@]}";
elif [ "${#SCRIPT_OPTS[@]}" -eq 0 ] && [ "${#SCRIPT_ARGS[@]}" -gt 0 ];
then set -- "${SCRIPT_ARGS[@]}";
elif [ "${#SCRIPT_OPTS[@]}" -gt 0 ] && [ "${#SCRIPT_ARGS[@]}" -gt 0 ];
then set -- "${SCRIPT_OPTS[@]}" -- "${SCRIPT_ARGS[@]}";
fi
# or, from version 2.1.0(-xxx) of the library
rearrange_script_options "$@"
[ -n "$SCRIPT_PARAMS" ] && eval set -- "$SCRIPT_PARAMS"
The library defines some methods to get and treat script arguments (global arguments, not
options' arguments). It defines and uses a new environment variable named ARGIND
which
indicates the current argument index, just like OPTIND
works for options with
the getopts
bash builtin.
To get and loop over arguments, you can use the get_next_argument
method, which will load
the "next" argument (the first one for a first usage) in the ARGUMENT
variable and increments
ARGIND
by one:
# initially
echo $ARGIND
0
echo $ARGUMENT
''
# first usage
get_next_argument
echo $ARGIND
1
echo $ARGUMENT
first-arg
# then, while a next argument is available
get_next_argument
echo $ARGIND
n+1
echo $ARGUMENT
n-arg
For facility, a get_last_argument
method is defined to get the last one directly. The ARGIND
indexer is not concerned.
On the same model as the internal getopts
method, the library
defines a getargs
method which allows the following while
loop usage:
while getargs MYARG; do
# inside the loop, the `MYARG` variable will contain current argument value
# and the `ARGIND` will contain current argument index
echo "> current argument is ${MYARG} with index ${ARGIND}"
done
For instance, if your script is based on positional arguments, you can write something like:
while getargs MYARG; do
# in the loop, ARGIND is the current argument index + 1 (next argument index)
case $ARGIND in
2) write here what to do with first argument with value $MYARG ;;
3) write here what to do with second argument with value $MYARG ;;
esac
done
To use common options in a script, just call the parse_common_options
method passing it
the script arguments:
#!/bin/bash
source path/to/piwi-bash-library.bash
parse_common_options "$@"
After that, if you want to define and use some custom options, you can write (here for the custom options '-t' and '--test' with arguments):
# any re-arrangement must come before ...
OPTIND=1
while getopts "t:${COMMON_OPTIONS_ALLOWED}" OPTION; do
OPTARG="${OPTARG#=}"
case "$OPTION" in
t)
# your script for option 't' with argument $OPTARG
;;
-) case "$OPTARG" in
test)
# your script for option 'test' with argument "${OPTARG#*=}"
;;
esac
;;
esac
done
To get any long option argument, you can use:
parse_long_option "$OPTARG" "${!OPTIND}"
This will load the long option name in the LONGOPTNAME
variable, its argument (if so)
in the LONGOPTARG
variable and update the OPTIND
variable if necessary.
If you use a library version less than 2.1.0-alpha, you must use:
LONGOPTARG="$(get_long_option_arg "$OPTARG")"
A special parse_common_options_strict
method is defined as an alias of the classic
parse_common_options
but throwing an error for undefined options. To use it, you MUST
define both OPTIONS_ALLOWED
and LONG_OPTIONS_ALLOWED
strings in your script as they
are used to define known options names. You can use the library COMMON_OPTIONS_ALLOWED
and COMMON_LONG_OPTIONS_ALLOWED
and complete them with your own options, like:
OPTIONS_ALLOWED="t:a${COMMON_OPTIONS_ALLOWED}"
LONG_OPTIONS_ALLOWED="test1:,test2::,${COMMON_LONG_OPTIONS_ALLOWED}"
These two variables follow the original getopt
program
specifications:
- the sort options are listed without any separator
- the long options are listed separated by coma
- when an option waits for a required argument, it is suffixed by one colon
- when an option waits for an optional argument, it is suffixed by two colons
In the example above, the parse_common_options_strict
method will consider the -t
option as allowed
but will throw an error with a -z
option that seems not allowed. It works the same way for
long options. More, it will throw an error if the test1
long option does not have an argument
(as it required) but not if the test2
does not (as it optional). It works the same way for short options.
Below is a complete example of a script which first re-arrange the command line call and then
parse the common options throwing an error for unknown options (taken from the samples/getopts-test.sh
test script).
OPTIONS_ALLOWED="t:a${COMMON_OPTIONS_ALLOWED}"
LONG_OPTIONS_ALLOWED="test1:,test2::,${COMMON_LONG_OPTIONS_ALLOWED}"
rearrange_script_options_new "$0" "$@"
[ -n "$SCRIPT_PARAMS" ] && eval set -- "$SCRIPT_PARAMS"
parse_common_options_strict "$@"
# loop over options
OPTIND=1
while getopts ":${OPTIONS_ALLOWED}" OPTION; do
OPTARG="$(get_option_arg "${OPTARG:-}")"
case "$OPTION" in
# case of long options
-) parse_long_option "$OPTARG" "${!OPTIND}"
case "$LONGOPTNAME" in
*) echo " - [${OPTIND}] long option '${LONGOPTNAME}' with arg '${LONGOPTARG}'";;
\?) echo " - [${OPTIND}] unknown long option '${LONGOPTNAME}'";;
esac
;;
# case of short options
*) echo " - [${OPTIND}] option '$OPTION' with arg '$OPTARG'";;
\?) echo " - [${OPTIND}] unknown option '$OPTION'";;
esac
done
# loop over arguments
while [[ "$ARGIND" -lt "${#SCRIPT_ARGS[@]}" ]]; do
get_next_argument
echo " - [${ARGIND}] argument is '$ARGUMENT'"
done
Below is a complete example of a script which first re-arrange the command line call and then
parse the common options throwing an error for unknown options (taken from the samples/getopts-test-old.sh
test script).
OPTIONS_ALLOWED="t:a${COMMON_OPTIONS_ALLOWED}"
LONG_OPTIONS_ALLOWED="test:,${COMMON_LONG_OPTIONS_ALLOWED}"
rearrange_script_options "$@"
if [ "${#SCRIPT_OPTS[@]}" -gt 0 ] && [ "${#SCRIPT_ARGS[@]}" -eq 0 ];
then set -- "${SCRIPT_OPTS[@]}";
elif [ "${#SCRIPT_OPTS[@]}" -eq 0 ] && [ "${#SCRIPT_ARGS[@]}" -gt 0 ];
then set -- "${SCRIPT_ARGS[@]}";
elif [ "${#SCRIPT_OPTS[@]}" -gt 0 ] && [ "${#SCRIPT_ARGS[@]}" -gt 0 ];
then set -- "${SCRIPT_OPTS[@]}" -- "${SCRIPT_ARGS[@]}";
fi
parse_common_options_strict
# own options usage
OPTIND=1
while getopts ":at:${OPTIONS_ALLOWED}" OPTION; do
OPTARG="${OPTARG#=}"
case "$OPTION" in
t) _echo " - option 't': receiving argument \"${OPTARG}\"";;
a) echo " - test option A";;
-) # for long options with argument, use fct 'get_long_option_arg ( $arg )'
LONGOPTARG="$(get_long_option_arg "$OPTARG")"
case "$OPTARG" in
test*) _echo " - option 'test': receiving argument '${LONGOPTARG}'";;
?) echo " - unknown long option '$OPTARG'";;
esac ;;
?) echo " - unknown option '$OPTION'";;
esac
done
# arguments usage
lastarg=$(get_last_argument)
echo " - last argument is '${lastarg}'"
get_next_argument
echo " - first argument is '$ARGUMENT'"
get_next_argument
echo " - next argument is '$ARGUMENT'"
echo " - final 'ARGIND' is: $ARGIND"
Documentation page for the Piwi Bash Library.
(c) 2013-2015 Pierre Cassat - Paris, France - Some rights reserved.
This documentation is licensed under the Creative Commons - Attribution - Share Alike - Unported - version 3.0 license.
Installation & Usage
Using the library in your scripts