I'm creating a basic script that should take 3 mandatory command line options and each one must be followed by a value. Like this:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
I'm trying to make sure the user is passing those exact 3 options and their values and nothing else, otherwise I want to print the usage message and exit.
I've been reading on getopts and came up with this:
usage () { echo "Usage : $0 -u <username> -p <password> -f <hostsFile>"; }
if [ $# -ne 6 ]
then
usage
exit 1
fi
while getopts u:p:f: opt ; do
case $opt in
u) USER_NAME=$OPTARG ;;
p) USER_PASSWORD=$OPTARG ;;
f) HOSTS_FILE=$OPTARG ;;
*) usage; exit 1;;
esac
done
echo "USERNAME: $USER_NAME"
echo "PASS: $USER_PASSWORD"
echo "FILE: $HOSTS_FILE"
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case. While that is true for other options such "-a","-b", etc.. does not seem to be the case in this particular case:
$ myscript.sh 1 2 3 4 5 6
Getops does not treat that as invalid input and the script moves on executing the echo commands showing 3 empty variables.
How can I capture the input above as being invalid as it is not in the form of:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
Thanks!
getopts has no concept of "mandatory" options. The colons in u:p:f: mean that, if one of those options happens to be supplied, then an argument to that option is mandatory. The option-argument pairs, however, are always optional.
You can require that the user provide all three though with code such as:
if [ ! "$USER_NAME" ] || [ ! "$USER_PASSWORD" ] || [ ! "$HOSTS_FILE" ]
then
usage
exit 1
fi
Place this code after the while getopts loop.
The Role of *)
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case.
The *) case is executed only if an option other than -u, -p, or -f is supplied. Thus, if someone supplied, for example a -z argument, then that case would run.
Related
I want to call myscript file in this way:
$ ./myscript -s 45 -p any_string
or
$ ./myscript -h #should display help
$ ./myscript #should display help
My requirements are:
getopt here to get the input arguments
check that -s exists, if not return an error
check that the value after the -s is 45 or 90
check that the -p exists and there is an input string after
if the user enters ./myscript -h or just ./myscript then display help
I tried so far this code:
#!/bin/bash
while getopts "h:s:" arg; do
case $arg in
h)
echo "usage"
;;
s)
strength=$OPTARG
echo $strength
;;
esac
done
But with that code I get errors. How to do it with Bash and getopt?
#!/bin/bash
usage() { echo "Usage: $0 [-s <45|90>] [-p <string>]" 1>&2; exit 1; }
while getopts ":s:p:" o; do
case "${o}" in
s)
s=${OPTARG}
((s == 45 || s == 90)) || usage
;;
p)
p=${OPTARG}
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ -z "${s}" ] || [ -z "${p}" ]; then
usage
fi
echo "s = ${s}"
echo "p = ${p}"
Example runs:
$ ./myscript.sh
Usage: ./myscript.sh [-s <45|90>] [-p <string>]
$ ./myscript.sh -h
Usage: ./myscript.sh [-s <45|90>] [-p <string>]
$ ./myscript.sh -s "" -p ""
Usage: ./myscript.sh [-s <45|90>] [-p <string>]
$ ./myscript.sh -s 10 -p foo
Usage: ./myscript.sh [-s <45|90>] [-p <string>]
$ ./myscript.sh -s 45 -p foo
s = 45
p = foo
$ ./myscript.sh -s 90 -p bar
s = 90
p = bar
The problem with the original code is that:
h: expects parameter where it shouldn't, so change it into just h (without colon)
to expect -p any_string, you need to add p: to the argument list
Basically : after the option means it requires the argument.
The basic syntax of getopts is (see: man bash):
getopts OPTSTRING VARNAME [ARGS...]
where:
OPTSTRING is string with list of expected arguments,
h - check for option -h without parameters; gives error on unsupported options;
h: - check for option -h with parameter; gives errors on unsupported options;
abc - check for options -a, -b, -c; gives errors on unsupported options;
:abc - check for options -a, -b, -c; silences errors on unsupported options;
Notes: In other words, colon in front of options allows you handle the errors in your code. Variable will contain ? in the case of unsupported option, : in the case of missing value.
OPTARG - is set to current argument value,
OPTERR - indicates if Bash should display error messages.
So the code can be:
#!/usr/bin/env bash
usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; }
[ $# -eq 0 ] && usage
while getopts ":hs:p:" arg; do
case $arg in
p) # Specify p value.
echo "p is ${OPTARG}"
;;
s) # Specify strength, either 45 or 90.
strength=${OPTARG}
[ $strength -eq 45 -o $strength -eq 90 ] \
&& echo "Strength is $strength." \
|| echo "Strength needs to be either 45 or 90, $strength found instead."
;;
h | *) # Display help.
usage
exit 0
;;
esac
done
Example usage:
$ ./foo.sh
./foo.sh usage:
p) # Specify p value.
s) # Specify strength, either 45 or 90.
h | *) # Display help.
$ ./foo.sh -s 123 -p any_string
Strength needs to be either 45 or 90, 123 found instead.
p is any_string
$ ./foo.sh -s 90 -p any_string
Strength is 90.
p is any_string
See: Small getopts tutorial at Bash Hackers Wiki
Use getopt
Why getopt?
To parse elaborated command-line arguments to avoid confusion and clarify the options we are parsing so that reader of the commands can understand what's happening.
What is getopt?
getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.
getopt can have following types of options.
No-value options
key-value pair options
Note: In this document, during explaining syntax:
Anything inside [ ] is optional parameter in the syntax/examples.
<value> is a place holder, which mean it should be substituted with an actual value.
HOW TO USE getopt?
Syntax: First Form
getopt optstring parameters
Examples:
# This is correct
getopt "hv:t::" -v 123 -t123
getopt "hv:t::" -v123 -t123 # -v and 123 doesn't have whitespace
# -h takes no value.
getopt "hv:t::" -h -v123
# This is wrong. after -t can't have whitespace.
# Only optional params cannot have whitespace between key and value
getopt "hv:t::" -v 123 -t 123
# Multiple arguments that takes value.
getopt "h:v:t::g::" -h abc -v 123 -t21
# Multiple arguments without value
# All of these are correct
getopt "hvt" -htv
getopt "hvt" -h -t -v
getopt "hvt" -tv -h
Here h,v,t are the options and -h -v -t is how options should be given in command-line.
'h' is a no-value option.
'v:' implies that option -v has value and
is a mandatory option. ':' means has a value.
't::' implies that
option -t has value but is optional. '::' means optional.
In optional param, value cannot have whitespace separation with the option. So, in "-t123" example, -t is option 123 is value.
Syntax: Second Form
getopt [getopt_options] [--] optstring parameters
Here after getopt is split into five parts
The command itself i.e. getopt
The getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
--, separates out the getopt_options from the options you want to parse and the allowed short options
The short options, is taken immediately after -- is found. Just like the Form first syntax.
The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.
Examples
getopt -l "name:,version::,verbose" -- "n:v::V" --name=Karthik -version=5.2 -verbose
Syntax: Third Form
getopt [getopt_options] -o|--options optstring [getopt_options] [--] [parameters]
Here after getopt is split into five parts
The command itself i.e. getopt
The getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
The short options i.e. -o or --options. Just like the Form first syntax but with option "-o" and before the "--" (double dash).
--, separates out the getopt_options from the options you want to parse and the allowed short options
The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.
Examples
getopt -l "name:,version::,verbose" -a -o "n:v::V" -- -name=Karthik -version=5.2 -verbose
GETOPT_OPTIONS
getopt_options changes the way command-line params are parsed.
Below are some of the getopt_options
Option: -l or --longoptions
Means getopt command should allow multi-character options to be
recognised. Multiple options are separated by comma.
For example, --name=Karthik is a long option sent in command line. In getopt, usage of long options are like
getopt -l "name:,version" -- "" --name=Karthik
Since name: is specified, the option should contain a value
Option: -a or --alternative
Means getopt command should allow long option to have a single dash
'-' rather than double dash '--'.
Example, instead of --name=Karthik you could use just -name=Karthik
getopt -a -l "name:,version" -- "" -name=Karthik
A complete script example with the code:
#!/bin/bash
# filename: commandLine.sh
# author: #theBuzzyCoder
showHelp() {
# `cat << EOF` This means that cat should stop reading when EOF is detected
cat << EOF
Usage: ./installer -v <espo-version> [-hrV]
Install Pre-requisites for EspoCRM with docker in Development mode
-h, -help, --help Display help
-v, -espo-version, --espo-version Set and Download specific version of EspoCRM
-r, -rebuild, --rebuild Rebuild php vendor directory using composer and compiled css using grunt
-V, -verbose, --verbose Run script in verbose mode. Will print out each step of execution.
EOF
# EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
}
export version=0
export verbose=0
export rebuilt=0
# $# is all command line parameters passed to the script.
# -o is for short options like -v
# -l is for long options with double dash like --version
# the comma separates different long options
# -a is for long options with single dash like -version
options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$#")
# set --:
# If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters
# are set to the arguments, even if some of them begin with a ‘-’.
eval set -- "$options"
while true
do
case "$1" in
-h|--help)
showHelp
exit 0
;;
-v|--version)
shift
export version="$1"
;;
-V|--verbose)
export verbose=1
set -xv # Set xtrace and verbose mode.
;;
-r|--rebuild)
export rebuild=1
;;
--)
shift
break;;
esac
shift
done
Running this script file:
# With short options grouped together and long option
# With double dash '--version'
bash commandLine.sh --version=1.0 -rV
# With short options grouped together and long option
# With single dash '-version'
bash commandLine.sh -version=1.0 -rV
# OR with short option that takes value, value separated by whitespace
# by key
bash commandLine.sh -v 1.0 -rV
# OR with short option that takes value, value without whitespace
# separation from key.
bash commandLine.sh -v1.0 -rV
# OR Separating individual short options
bash commandLine.sh -v1.0 -r -V
The example packaged with getopt (my distro put it in /usr/share/getopt/getopt-parse.bash) looks like it covers all of your cases:
#!/bin/bash
# A small example program for using the new getopt(1) program.
# This program will only work with bash(1)
# An similar program using the tcsh(1) script language can be found
# as parse.tcsh
# Example input and output (from the bash prompt):
# ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long "
# Option a
# Option c, no argument
# Option c, argument 'more'
# Option b, argument ' very long '
# Remaining arguments:
# --> 'par1'
# --> 'another arg'
# --> 'wow!*\?'
# Note that we use `"$#"' to let each command-line parameter expand to a
# separate word. The quotes around '$#' are essential!
# We need TEMP as the `eval set --' would nuke the return value of getopt.
TEMP=$(getopt -o ab:c:: --long a-long,b-long:,c-long:: \
-n 'example.bash' -- "$#")
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around '$TEMP': they are essential!
eval set -- "$TEMP"
while true ; do
case "$1" in
-a|--a-long) echo "Option a" ; shift ;;
-b|--b-long) echo "Option b, argument '$2'" ; shift 2 ;;
-c|--c-long)
# c has an optional argument. As we are in quoted mode,
# an empty parameter will be generated if its optional
# argument is not found.
case "$2" in
"") echo "Option c, no argument"; shift 2 ;;
*) echo "Option c, argument '$2'" ; shift 2 ;;
esac ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
echo "Remaining arguments:"
for arg do echo '--> '"'$arg'" ; done
I know that this is already answered, but for the record and for anyone with the same requeriments as me I decided to post this related answer. The code is flooded with comments to explain the code.
Updated answer:
Save the file as getopt.sh:
#!/bin/bash
function get_variable_name_for_option {
local OPT_DESC=${1}
local OPTION=${2}
local VAR=$(echo ${OPT_DESC} | sed -e "s/.*\[\?-${OPTION} \([A-Z_]\+\).*/\1/g" -e "s/.*\[\?-\(${OPTION}\).*/\1FLAG/g")
if [[ "${VAR}" == "${1}" ]]; then
echo ""
else
echo ${VAR}
fi
}
function parse_options {
local OPT_DESC=${1}
local INPUT=$(get_input_for_getopts "${OPT_DESC}")
shift
while getopts ${INPUT} OPTION ${#};
do
[ ${OPTION} == "?" ] && usage
VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}")
[ "${VARNAME}" != "" ] && eval "${VARNAME}=${OPTARG:-true}" # && printf "\t%s\n" "* Declaring ${VARNAME}=${!VARNAME} -- OPTIONS='$OPTION'"
done
check_for_required "${OPT_DESC}"
}
function check_for_required {
local OPT_DESC=${1}
local REQUIRED=$(get_required "${OPT_DESC}" | sed -e "s/\://g")
while test -n "${REQUIRED}"; do
OPTION=${REQUIRED:0:1}
VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}")
[ -z "${!VARNAME}" ] && printf "ERROR: %s\n" "Option -${OPTION} must been set." && usage
REQUIRED=${REQUIRED:1}
done
}
function get_input_for_getopts {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g"
}
function get_optional {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/[^[]*\(\[[^]]*\]\)[^[]*/\1/g" -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g"
}
function get_required {
local OPT_DESC=${1}
echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/\[[^[]*\]//g" -e "s/[][ -]//g"
}
function usage {
printf "Usage:\n\t%s\n" "${0} ${OPT_DESC}"
exit 10
}
Then you can use it like this:
#!/bin/bash
#
# [ and ] defines optional arguments
#
# location to getopts.sh file
source ./getopt.sh
USAGE="-u USER -d DATABASE -p PASS -s SID [ -a START_DATE_TIME ]"
parse_options "${USAGE}" ${#}
echo ${USER}
echo ${START_DATE_TIME}
Old answer:
I recently needed to use a generic approach. I came across with this solution:
#!/bin/bash
# Option Description:
# -------------------
#
# Option description is based on getopts bash builtin. The description adds a variable name feature to be used
# on future checks for required or optional values.
# The option description adds "=>VARIABLE_NAME" string. Variable name should be UPPERCASE. Valid characters
# are [A-Z_]*.
#
# A option description example:
# OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE"
#
# -a option will require a value (the colon means that) and should be saved in variable A_VARIABLE.
# "|" is used to separate options description.
# -b option rule applies the same as -a.
# -c option doesn't require a value (the colon absense means that) and its existence should be set in C_VARIABLE
#
# ~$ echo get_options ${OPT_DESC}
# a:b:c
# ~$
#
# Required options
REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG"
# Optional options (duh)
OPTIONAL_DESC="P:=>OPT_P_VAR_VALUE|r=>OPT_R_VAR_FLAG"
function usage {
IFS="|"
printf "%s" ${0}
for i in ${REQUIRED_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
printf " %s" "-${i:0:1} $VARNAME"
done
for i in ${OPTIONAL_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
printf " %s" "[-${i:0:1} $VARNAME]"
done
printf "\n"
unset IFS
exit
}
# Auxiliary function that returns options characters to be passed
# into 'getopts' from a option description.
# Arguments:
# $1: The options description (SEE TOP)
#
# Example:
# OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
# OPTIONS=$(get_options ${OPT_DESC})
# echo "${OPTIONS}"
#
# Output:
# "h:f:PW"
function get_options {
echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g"
}
# Auxiliary function that returns all variable names separated by '|'
# Arguments:
# $1: The options description (SEE TOP)
#
# Example:
# OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
# VARNAMES=$(get_values ${OPT_DESC})
# echo "${VARNAMES}"
#
# Output:
# "H_VAR|F_VAR|P_VAR|W_VAR"
function get_variables {
echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g"
}
# Auxiliary function that returns the variable name based on the
# option passed by.
# Arguments:
# $1: The options description (SEE TOP)
# $2: The option which the variable name wants to be retrieved
#
# Example:
# OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
# H_VAR=$(get_variable_name ${OPT_DESC} "h")
# echo "${H_VAR}"
#
# Output:
# "H_VAR"
function get_variable_name {
VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g")
if [[ ${VAR} == ${1} ]]; then
echo ""
else
echo ${VAR}
fi
}
# Gets the required options from the required description
REQUIRED=$(get_options ${REQUIRED_DESC})
# Gets the optional options (duh) from the optional description
OPTIONAL=$(get_options ${OPTIONAL_DESC})
# or... $(get_options "${OPTIONAL_DESC}|${REQUIRED_DESC}")
# The colon at starts instructs getopts to remain silent
while getopts ":${REQUIRED}${OPTIONAL}" OPTION
do
[[ ${OPTION} == ":" ]] && usage
VAR=$(get_variable_name "${REQUIRED_DESC}|${OPTIONAL_DESC}" ${OPTION})
[[ -n ${VAR} ]] && eval "$VAR=${OPTARG}"
done
shift $(($OPTIND - 1))
# Checks for required options. Report an error and exits if
# required options are missing.
# Using function version ...
VARS=$(get_variables ${REQUIRED_DESC})
IFS="|"
for VARNAME in $VARS;
do
[[ -v ${VARNAME} ]] || usage
done
unset IFS
# ... or using IFS Version (no function)
OLDIFS=${IFS}
IFS="|"
for i in ${REQUIRED_DESC};
do
VARNAME=$(echo $i | sed -e "s/.*=>//g")
[[ -v ${VARNAME} ]] || usage
printf "%s %s %s\n" "-${i:0:1}" "${!VARNAME:=present}" "${VARNAME}"
done
IFS=${OLDIFS}
I didn't test this roughly, so I could have some bugs in there.
POSIX 7 example
It is also worth checking the example from the standard: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html
aflag=
bflag=
while getopts ab: name
do
case $name in
a) aflag=1;;
b) bflag=1
bval="$OPTARG";;
?) printf "Usage: %s: [-a] [-b value] args\n" $0
exit 2;;
esac
done
if [ ! -z "$aflag" ]; then
printf "Option -a specified\n"
fi
if [ ! -z "$bflag" ]; then
printf 'Option -b "%s" specified\n' "$bval"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: %s\n" "$*"
And then we can try it out:
$ sh a.sh
Remaining arguments are:
$ sh a.sh -a
Option -a specified
Remaining arguments are:
$ sh a.sh -b
No arg for -b option
Usage: a.sh: [-a] [-b value] args
$ sh a.sh -b myval
Option -b "myval" specified
Remaining arguments are:
$ sh a.sh -a -b myval
Option -a specified
Option -b "myval" specified
Remaining arguments are:
$ sh a.sh remain
Remaining arguments are: remain
$ sh a.sh -- -a remain
Remaining arguments are: -a remain
Tested in Ubuntu 17.10, sh is dash 0.5.8.
getopts and getopt are very limited. While getopt is suggested not to be used at all, it does offer long options. Whereas getopts does only allow single character options such as -a -b. There are a few more disadvantages when using either one.
So I've written a small script that replaces getopts and getopt.
It's a start, it could probably be improved a lot.
Update 08-04-2020: I've added support for hyphens e.g. --package-name.
Usage: "./script.sh package install --package "name with space"
--build --archive"
# Example:
# parseArguments "${#}"
# echo "${ARG_0}" -> package
# echo "${ARG_1}" -> install
# echo "${ARG_PACKAGE}" -> "name with space"
# echo "${ARG_BUILD}" -> 1 (true)
# echo "${ARG_ARCHIVE}" -> 1 (true)
function parseArguments() {
PREVIOUS_ITEM=''
COUNT=0
for CURRENT_ITEM in "${#}"
do
if [[ ${CURRENT_ITEM} == "--"* ]]; then
printf -v "ARG_$(formatArgument "${CURRENT_ITEM}")" "%s" "1" # could set this to empty string and check with [ -z "${ARG_ITEM-x}" ] if it's set, but empty.
else
if [[ $PREVIOUS_ITEM == "--"* ]]; then
printf -v "ARG_$(formatArgument "${PREVIOUS_ITEM}")" "%s" "${CURRENT_ITEM}"
else
printf -v "ARG_${COUNT}" "%s" "${CURRENT_ITEM}"
fi
fi
PREVIOUS_ITEM="${CURRENT_ITEM}"
(( COUNT++ ))
done
}
# Format argument.
function formatArgument() {
ARGUMENT="${1^^}" # Capitalize.
ARGUMENT="${ARGUMENT/--/}" # Remove "--".
ARGUMENT="${ARGUMENT//-/_}" # Replace "-" with "_".
echo "${ARGUMENT}"
}
Turning the huge one-liner in Mark G.'s comment (under Adrian Frühwirth's answer) into a more readable answer - this shows how to avoid using getopts in order to get optional arguments:
usage() {
printf "Usage: %s <req> [<-s|--sopt> <45|90>] [<-p|--popt> <string>]\n" "$0";
return 1;
};
main() {
req="${1:?$(usage)}";
shift;
s="";
p="";
while [ "$#" -ge 1 ]; do
case "$1" in
-s|--sopt)
shift;
s="${1:?$(usage)}";
[ "$s" -eq 45 ] || [ "$s" -eq 90 ] || {
usage;
return 1;
}
;;
-p|--popt)
shift;
p="${1:?$(usage)}"
;;
*)
usage;
return 1
;;
esac;
shift;
done;
printf "req = %s\ns = %s\np = %s\n" "$req" "$s" "$p";
};
main "$#"
As noted in n.caillou's comment:
it will fail if there's no space between the options and their argument.
However, to make it more POSIX compliant (from Mark G.'s other comment):
case "$1" in
-s*)
s=${1#-s};
if [ -z "$s" ];
shift;
s=$1;
fi
I have script where i need to display usage command in case user miss any mandatory information while executing script.
Usage : Script -s <server> -i <instance> -u <user> -p <password> <query> -w <warning value> -c <critical value>
With explanation about all the OPTIONS
I'm getting values from arguments as below variables fashion. But I want this usage with validations in shell script.
SERVER=$1
INSTANCE=$2
USER=$3
DB_PASSWD=$4
QUERY=$5
VAL_WARN=$6
VAL_CRIT=$7
I have tried using getopts, But failed to use since <query> doesn't have a -q parameter before passing the value.
I have tried finding all other ways, But everyone suggested getopts which is not feasible solution for me.
Please help me on this..
Use shift to iterate through all of your arguments, something like:
#!/bin/sh
usage ()
{
echo 'Usage : Script -s <server> -i <instance> -u <user> -p <password>'
echo ' <query> -w <warning value> -c <critical value>'
exit
}
if [ "$#" -ne 13 ]
then
usage
fi
while [ "$1" != "" ]; do
case $1 in
-s ) shift
SERVER=$1
;;
-i ) shift
INSTANCE=$1
;;
-u ) shift
USER=$1
;;
-p ) shift
PASSWORD=$1
;;
-w ) shift
WARNINGVAL=$1
;;
-c ) shift
CRITICVAL=$1
;;
* ) QUERY=$1
esac
shift
done
# extra validation suggested by #technosaurus
if [ "$SERVER" = "" ]
then
usage
fi
if [ "$INSTANCE" = "" ]
then
usage
fi
if [ "$USER" = "" ]
then
usage
fi
if [ "$PASSWORD" = "" ]
then
usage
fi
if [ "$QUERY" = "" ]
then
usage
fi
if [ "$WARNINGVAL" = "" ]
then
usage
fi
if [ "$CRITICVAL" = "" ]
then
usage
fi
echo "ALL IS WELL. SERVER=$SERVER,INSTANCE=$INSTANCE,USER=$USER,PASSWORD=$PASSWORD,QUERY=$QUERY,WARNING=$WARNINGVAL,CRITIC=$CRITICVAL"
Should do the trick.
EDIT: added argument validation in the script as suggested by #technosaurus
getopts is bitching for a good reason. you should change your script's interface to conform to what people expect.
alternatively, you could use getopts twice, first for the pre-query options, shift, then for the rest.
try this out
usage()
{
echo "$0 -s <server> -i <instance> -u <user> -p <password> <query> -w <warning value> -c <critical value>"
}
for i in {0..12}
do
arg=`expr $i +1`
test ! "${!arg}" && usage && break
done
hope this helps
Well, a very caveman like way would be like this
usage ()
{
echo 'Usage : Script -s <server> -i <instance> -u <user> -p <password>'
echo ' <query> -w <warning value> -c <critical value>'
exit
}
[ ${13} ] || usage
This is a non-standard approach, but one that I find very useful. Instead of passing values as arguments to specific flags (which is fairly annoying; the user should not be required to specify every value but reasonable defaults should be supplied), you can simply pass them directly via the environment, so that a typical call would look like:
SERVER=blah INSTANCE=foo Script
It would be nice if you used lower case variable name so the user doesn't have to shout. This allows the script so simply avoid parsing the command line completely, as the values of the variables will be set when the script begins.
I am trying to make a shell script which is designed to be run like this:
script.sh -t application
Firstly, in my script I want to check to see if the script has been run with the -t flag. For example if it has been run without the flag like this I want it to error:
script.sh
Secondly, assuming there is a -t flag, I want to grab the value and store it in a variable that I can use in my script for example like this:
FLAG="application"
So far the only progress I've been able to make on any of this is that $# grabs all the command line arguments but I don't know how this relates to flags, or if this is even possible.
You should read this getopts tutorial.
Example with -a switch that requires an argument :
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
Like greybot said(getopt != getopts) :
The external command getopt(1) is never safe to use, unless you know
it is GNU getopt, you call it in a GNU-specific way, and you ensure
that GETOPT_COMPATIBLE is not in the environment. Use getopts (shell
builtin) instead, or simply loop over the positional parameters.
Use $# to grab the number of arguments, if it is unequal to 2 there are not enough arguments provided:
if [ $# -ne 2 ]; then
usage;
fi
Next, check if $1 equals -t, otherwise an unknown flag was used:
if [ "$1" != "-t" ]; then
usage;
fi
Finally store $2 in FLAG:
FLAG=$2
Note: usage() is some function showing the syntax. For example:
function usage {
cat << EOF
Usage: script.sh -t <application>
Performs some activity
EOF
exit 1
}
Here is a generalized simple command argument interface you can paste to the top of all your scripts.
#!/bin/bash
declare -A flags
declare -A booleans
args=()
while [ "$1" ];
do
arg=$1
if [ "${1:0:1}" == "-" ]
then
shift
rev=$(echo "$arg" | rev)
if [ -z "$1" ] || [ "${1:0:1}" == "-" ] || [ "${rev:0:1}" == ":" ]
then
bool=$(echo ${arg:1} | sed s/://g)
booleans[$bool]=true
echo \"$bool\" is boolean
else
value=$1
flags[${arg:1}]=$value
shift
echo \"$arg\" is flag with value \"$value\"
fi
else
args+=("$arg")
shift
echo \"$arg\" is an arg
fi
done
echo -e "\n"
echo booleans: ${booleans[#]}
echo flags: ${flags[#]}
echo args: ${args[#]}
echo -e "\nBoolean types:\n\tPrecedes Flag(pf): ${booleans[pf]}\n\tFinal Arg(f): ${booleans[f]}\n\tColon Terminated(Ct): ${booleans[Ct]}\n\tNot Mentioned(nm): ${boolean[nm]}"
echo -e "\nFlag: myFlag => ${flags["myFlag"]}"
echo -e "\nArgs: one: ${args[0]}, two: ${args[1]}, three: ${args[2]}"
By running the command:
bashScript.sh firstArg -pf -myFlag "my flag value" secondArg -Ct: thirdArg -f
The output will be this:
"firstArg" is an arg
"pf" is boolean
"-myFlag" is flag with value "my flag value"
"secondArg" is an arg
"Ct" is boolean
"thirdArg" is an arg
"f" is boolean
booleans: true true true
flags: my flag value
args: firstArg secondArg thirdArg
Boolean types:
Precedes Flag(pf): true
Final Arg(f): true
Colon Terminated(Ct): true
Not Mentioned(nm):
Flag: myFlag => my flag value
Args: one => firstArg, two => secondArg, three => thirdArg
Basically, the arguments are divided up into flags booleans and generic arguments.
By doing it this way a user can put the flags and booleans anywhere as long as he/she keeps the generic arguments (if there are any) in the specified order.
Allowing me and now you to never deal with bash argument parsing again!
You can view an updated script here
This has been enormously useful over the last year. It can now simulate scope by prefixing the variables with a scope parameter.
Just call the script like
replace() (
source $FUTIL_REL_DIR/commandParser.sh -scope ${FUNCNAME[0]} "$#"
echo ${replaceFlags[f]}
echo ${replaceBooleans[b]}
)
Doesn't look like I implemented argument scope, not sure why I guess I haven't needed it yet.
Try shFlags -- Advanced command-line flag library for Unix shell scripts.
https://github.com/kward/shflags
It is very good and very flexible.
FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take
a name, default value, help-string, and optional 'short' name (one-letter
name). Some flags have other arguments, which are described with the flag.
DEFINE_string: takes any input, and intreprets it as a string.
DEFINE_boolean: typically does not take any argument: say --myflag to set
FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false.
Alternately, you can say
--myflag=true or --myflag=t or --myflag=0 or
--myflag=false or --myflag=f or --myflag=1
Passing an option has the same affect as passing the option once.
DEFINE_float: takes an input and intreprets it as a floating point number. As
shell does not support floats per-se, the input is merely validated as
being a valid floating point value.
DEFINE_integer: takes an input and intreprets it as an integer.
SPECIAL FLAGS: There are a few flags that have special meaning:
--help (or -?) prints a list of all the flags in a human-readable fashion
--flagfile=foo read flags from foo. (not implemented yet)
-- as in getopt(), terminates flag-processing
EXAMPLE USAGE:
-- begin hello.sh --
! /bin/sh
. ./shflags
DEFINE_string name 'world' "somebody's name" n
FLAGS "$#" || exit $?
eval set -- "${FLAGS_ARGV}"
echo "Hello, ${FLAGS_name}."
-- end hello.sh --
$ ./hello.sh -n Kate
Hello, Kate.
Note: I took this text from shflags documentation
This is my shell script-
if ! options=$(getopt -o : -l along:,blong:,clong: -- "$#")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
--along) echo "--along selected :: $2" ;;
--blong) echo "--blong selected :: $2" ;;
--clong) echo "--clong selected :: $2" ;;
esac
shift
done
when i run the script i get the following output-
./test.sh --along hi --blong hello --clong bye
--along selected :: 'hi'
--blong selected :: 'hello'
--clong selected :: 'bye'
The problem is I don't want to display the arguments with single quotes ('hi', 'hello', 'bye'). What should I do to get rid of those quotes?
Use the option -u or --unquoted for getopt, i.e.
if ! options=$(getopt -u -o : -l along:,blong:,clong: -- "$#")
The manpage of getopt says for -u:
Do not quote the output. Note that whitespace and special
(shell-dependent) characters can cause havoc in this mode (like they
do with other getopt(1) implementations).
I would like to write a script that requires -c and -f where each requires an option.
When I run my script below I get some unexpected errors:
$ ./user.sh -c
./user.sh: option requires an argument -- c
Usage: user.sh -c username -f filename
-c username
-f SSH public key
$ ./user.sh -c gg
Error: You have not given a filename.
In the first case, I would have liked it said I am missing the option for -c and in the second case I would have liked it said I am missing -f.
Question
How do I make such error handing, and what am I doing wrong?
user.sh
#!/bin/bash
usage () {
echo "Usage: user.sh -c username -f filename"
echo " -c username"
echo " -f SSH public key"
echo ""
}
if ! [ "$*" ]; then
usage
exit 1
fi
while getopts "c:f:" opt; do
case $opt in
c) user=$OPTARG;;
f) filename=$OPTARG;;
\?)
echo
usage
exit 1;;
*) echo "Internal error: Unknown option.";;
esac
done
if ! [ $filename ]; then
echo "Error: You have not given a filename."
exit 1
fi
if ! [ $user ]; then
echo "Error: You have not given an username."
exit 1
fi
The c: says 'the -c option must be followed by a username'.
The error message says 'the -c option was not followed by a username'.
Granted, it didn't mention 'username' but that's because it doesn't know what it is that follows the option -c.
The getopts built-in cannot handle mandatory options; you have to code that for yourself by checking that the mandatory options were in fact passed. It also doesn't worry if the same option is specified twice; your code has to deal with that if it matters. (It's easy to let the last specified value take effect.)
Modern style is to avoid option letters before mandatory arguments. I'm not wholly in favour of the change; it means that the ordering of the arguments becomes critical in a way that using option letters to indicate what follows does not. Without option letters, you'd write: ./user.sh username filename, but with option letters, you can write either of these and expect it to work:
./user.sh -c username -f filename
./user.sh -f filename -c username
Note that the onus is on you to worry about extra arguments too. You'll typically use:
shift $(($OPTIND - 1))
to remove the processed arguments, and you can then do:
case "$#" in
(0) : No extra arguments - OK;;
(*) echo "$0: Too many arguments" >&2; exit 1;;
esac
And variations on that theme. Note that the error report is sent to standard error, not to standard output — the >&2 redirection sends standard output (file descriptor 1) to standard error (file descriptor 2) instead.
To avoid ambiguity, I'd code your usage function a little differently:
usage()
{
{
echo "Usage: user.sh -c username -f filename"
echo " -c username Name of user to connect as"
echo " -f filename SSH public key file"
echo ""
} >&2
}
The inner braces do I/O redirection en masse, without starting a subshell. That can be useful when you need to send a number of echo commands to the same place. I've also presented the detail information a little differently, so that a user isn't confused into thinking that 'SSH public key' is three arguments to follow the -f. If there were any pure-option flags, they'd be followed by blanks:
echo " -V Print version information and exit"