I have the following bash script:
#!/bin/bash
function usage() {
printf "\n"
echo "Updates a Lambda with env vars stored in a Secrets Manager secret. Complete drop n' swap."
printf "\n"
echo "Syntax: bash $0 -a <ARN> -s <secretName> [-h]"
echo "options:"
echo "a the ARN of the Lambda to update"
echo "s the name of the secret in Secrets Manager to use"
echo "h display help"
printf "\n"
exit
}
while getopts ":has:" option; do
case $option in
h) # display help
usage
;;
a) # ARN of the lambda
arn=${OPTARG}
[[ -z "$arn" ]] && usage
;;
s) # Secrets Manager secret (name)
secretName=${OPTARG}
[[ -z "$secretName" ]] && usage
;;
*) # catchall
usage
esac
done
echo "all good! arn = $arn and secret = $secretName"
When I run this I get:
myuser#mymachine myapp % bash myscript.sh -a abba -s babba
Updates a Lambda with env vars stored in a Secrets Manager secret. Complete drop n' swap.
Syntax: bash myscript.sh -a <ARN> -s <secretName> [-h]
options:
a the ARN of the Lambda to update
s the name of the secret in Secrets Manager to use
h display help
I was expecting all of the arguments (parsed by getopts) to be valid and to see the "all good!..." output instead of the usage output.
Where am I going awry? Am I using/parsing getopts incorrectly, or am I invoking the script with argument incorrectly? Or both?! Thanks in advance!
Since -a is supposed to have an associated value you want to append a : to the a option, ie:
# change this:
while getopts ":has:" option
# to this:
while getopts ":ha:s:" option
^--------------
Related
I have created a bash script which takes 2 command line arguments. It works absolutely fine.
I want to go a step further, and want to show the types of arguments it takes. i.e. if we run my_script.bash --help it should tell the desired arguments in its order.
my_script.bash is as follows
#!/bin/bash
X="$1" ## Name
Y="$2" ## address
echo " Mr./Ms. $X lives in $Y ."
Since it takes two arguments name and address, I want to show these when this bash is executed without any inputs or number of inputs or using my_script.bash --help command.
ie executing ./my_script.bash or ./my_script.bash --help should show like below
$ ./my_script.bash
>>this script takes two arguments. please enter **Name** and **address**
Since these arguments are position specific so we cannot change the positions of Name and Address. It would be great if we could pass the arguments by defining --name --address.
$ ./my_script.bash --address Delhi --name Gupta
>> Mr./Ms. Gupta lives in Delhi .
Any help would be appreciated.
A basic option parsing loop uses while and case, and looks like this:
print_help ()
{
cat <<-EOF
${0##*/} - process name and address
--name NAME your name
--address ADDRESS your address
--help this help
EOF
}
die ()
{
echo "$#" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case $1 in
--help)
print_help
exit
;;
--name)
shift || die "$1: requires input"
name=$1
;;
--address)
shift || die "$1: requires input"
address=$1
;;
*)
die "$1: invalid argument"
esac
shift
done
The order of options doesn't matter.
You can test for [[ $1 == --help ]], but since your script has 2 required arguments, you could simply print the help whenever the number of arguments is not equal 2:
if (( $# != 2 ))
then
echo You have to provide 2 arguments 1>&2
exit 1
fi
I have a script that starts with getopts and looks as follows:
USAGE() { echo -e "Usage: bash $0 [-w <in-dir>] [-o <out-dir>] [-c <template1>] [-t <template2>] \n" 1>&2; exit 1; }
if (($# == 0))
then
USAGE
fi
while getopts ":w:o:c:t:h" opt
do
case $opt in
w ) BIGWIGS=$OPTARG
;;
o ) OUTDIR=$OPTARG
;;
c ) CONTAINER=$OPTARG
;;
t ) TRACK=$OPTARG
;;
h ) USAGE
;;
\? ) echo "Invalid option: -$OPTARG exiting" >&2
exit
;;
: ) echo "Option -$OPTARG requires an argument" >&2
exit
;;
esac
done
more commands etc
echo $OUTDIR
echo $CONTAINER
I am fairly new to getopts. I was doing some testing on this script and at some stage, I didn't need/want to use the -c argument [-c ]. In other words, I was trying to test another specific part of the script not involving the $CONTAINER variable at all. Therefore, I simply added # in front of all commands with the $CONTAINER and did some testing which was fine.
When testing the script without using $CONTAINER, I typed:
bash script.bash -w mydir -o myoutdir -t mywantedtemplate
However, I was wondering, given my getopts command I didn't get a warning. In other words, why did I not get a warning asking for -c argument. Is this possible? Does the warning only occur if I type:
bash script.bash -w mydir -o myoutdir -t mywantedtemplate -c
UPDATE
After doing some testing, I think that is it:
If you don't explicitly write "-c", getopts won't "ask" you for it and give you an error (unless your script is doing something with it - i.e. if you haven't put # in front of each command using this argument)
You only get an error if you put "-c "
Is this correct?
getopts does not warn when some options are not used (i.e. they are optional). Usually that's a good thing because some options (e.g. -h) are not used with other options. There is no way to specify mandatory options directly with the Bash builtin getopts. If you want mandatory options then you will need to write code to check that they have been used. See bash getopts with multiple and mandatory options. Also (as you have found), you won't get an error if you fail to write code to handle options specified in the optstring (first) argument to getopts.
You could get a kind of automatic warning for mandatory arguments by using the nounset setting in your Bash code (with set -o nounset or set -u). That would cause warnings to be issued for code like echo $CONTAINER if the -c option is not specified so $CONTAINER is not set. However, using the nounset option would mean that all of your code needs to be written more carefully. See How can I make bash treat undefined variables as errors?, including the comments and "Linked" answers, for more information.
You might use this script:
printf captures the stdout and put into the stderr and then back to the stdout also captures the exit code, so we can handle if the code is greater than 0.
some_command() {
echo 'this is the stdout'
echo 'this is the stderr' >&2
exit 1
}
run_command() {
{
IFS=$'\n' read -r -d '' stderr;
IFS=$'\n' read -r -d '' stdout;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(some_command)" "${?}" 1>&2) 2>&1)
}
echo 'Run command:'
if ! run_command; then
## Show the values
typeset -p stdout stderr
else
typeset -p stdout stderr
fi
Just replace some_command with getopts ":w:o:c:t:h"
Purpose
This script is meant to copy one's public ssh identity file to many hosts that are listed in a file
Problem
The script is not properly reading IP's from the file. I believe this is because it is not reading the lines of the file. When I echo out the line (IP) it is reading, it is blank.
Code
#!/bin/bash
usage="Usage: $(basename "$0") [-h|-u|-i|-p|-f] -- Copys your identity key to a list of remote hosts
where:
-h Show this help text
-u Username of ssh user
-i Location of identity file (Default: /home/$USER/.ssh/id_rsa.pub)
-p Password (not secure)
-f Location of hosts file (Default: ./inventory)"
# Default location for files
CURR_DIR="$(cd "$(dirname "$0")" && pwd)"
HOSTS_FILE=${CURR_DIR}/inventory
IDENT_FILE=/home/$USER/.ssh/id_rsa.pub
# required_flag=false
while getopts 'hu:i:p:f:' option; do
case $option in
# Help
h) echo "$usage"; exit;;
# Hosts file
f) HOSTS_FILE=$OPTARG;;
# Password
p) PASSWORD=$OPTARG;;
# Indentity file
i) IDENT_FILE=$OPTARG; echo "$IDENT_FILE";;
# Username
u) USERNAME=$OPTARG;;
# Missing args
:) printf "Option -%s\n requires an argument." "-$OPTARG" >&2; echo "$usage" >&2; exit 1;;
# Illegal option
\?) printf "Illegal option: -%s\n" "$OPTARG" >&2; echo "$usage" >&2; exit 1;;
esac
done
shift "$((OPTIND-1))"
# Decrements the argument pointer so it points to next argument.
# $1 now references the first non-option item supplied on the command-line
#+ if one exists.
# if ! $required_flag && [[ -d $1 ]]
# then
# echo "You must specify a hosts file. -h for more help." >&2
# exit 1
# fi
while IFS= read -r line;
do
echo "Current IP: " "$IP"
echo "Current Username: " "$USERNAME"
echo "Current Identity: " "$IDENT_FILE"
echo "Current Hosts: " "$HOSTS_FILE"
echo "Current Password: " "$PASSWORD"
sshpass -p "$PASSWORD" ssh-copy-id -i "$IDENT_FILE" "$USERNAME"#"$IP"
done < $HOSTS_FILE
Output
$ ./autocopyid -u user -p password
Current IP:
Current Username: user
Current Identity: /home/user/.ssh/id_rsa.pub
Current Hosts: /home/user/inventory
Current Password: password
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/user/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: ERROR: ssh: Could not resolve hostname : Name or service not known
You are not using the variable line for anything. Assuming $HOSTS_FILE points to a file that contains one IP address per line, you now have the IP address in variable line instead of IP in your loop. But since you use the variable name IP in the body of the loop, you should use that in the read statement, too.
So try
while IFS= read -r IP
instead of
while IFS= read -r line
I have a script on my local machine, but need to run it on a remote machine without copying it over there (IE, I can't sftp it over and just run it there)
I currently have the following functioning command
echo 'cd /place/to/execute' | cat - test.sh | ssh -T user#hostname
However, I also need to provide a commandline argument to test.sh.
I tried just adding it after the .sh, like I would for local execution, but that didn't work:
echo 'cd /place/to/execute' | cat - test.sh "arg" | ssh -T user#hostname
"cat: arg: No such file or directory" is the resulting error
You need to override the arguments:
echo 'set -- arg; cd /place/to/execute' | cat - test.sh | ssh -T user#hostname
The above will set the first argument to arg.
Generally:
set -- arg1 arg2 arg3
will overwrite the $1, $2, $3 in bash.
This will basically make the result of cat - test.sh a standalone script that doesn't need any arguments`.
Depends on the complexity of the script that you have. You might want to rewrite it to be able to use rpcsh functionality to remotely execute shell functions from your script.
Using https://gist.github.com/Shadowfen/2b510e51da6915adedfb saved into /usr/local/include/rpcsh.inc (for example) you could have a script
#!/bin/sh
source /usr/local/include/rpcsh.inc
MASTER_ARG=""
function ahelper() {
# used by doremotely just to show that we can
echo "master arg $1 was passed in"
}
function doremotely() {
# this executes on the remote host
ahelper $MASTER_ARG > ~/sample_rpcsh.txt
}
# main
MASTER_ARG="newvalue"
# send the function(s) and variable to the remote host and then execute it
rpcsh -u user -h host -f "ahelper doremotely" -v MASTER_ARG -r doremotely
This will give you a ~/sample_rpcsh.txt file on the remote host that contains
master arg newvalue was passed in
Copy of rpcsh.inc (in case link goes bad):
#!/bin/sh
# create an inclusion guard (to prevent multiple inclusion)
if [ ! -z "${RPCSH_GUARD+xxx}" ]; then
# already sourced
return 0
fi
RPCSH_GUARD=0
# rpcsh -- Runs a function on a remote host
# This function pushes out a given set of variables and functions to
# another host via ssh, then runs a given function with optional arguments.
# Usage:
# rpcsh -h remote_host -u remote_login -v "variable list" \
# -f "function list" -r mainfunc [-- param1 [param2]* ]
#
# The "function list" is a list of shell functions to push to the remote host
# (including the main function to run, and any functions that it calls).
#
# Use the "variable list" to send a group of variables to the remote host.
#
# Finally "mainfunc" is the name of the function (from "function list")
# to execute on the remote side. Any additional parameters specified (after
# the --)gets passed along to mainfunc.
#
# You may specify multiple -v "variable list" and -f "function list" options.
#
# Requires that you setup passwordless access to the remote system for the script
# that will be running this.
rpcsh() {
if ! args=("$(getopt -l "host:,user:,pushvars:,pushfuncs:,run:" -o "h:u:v:f:r:A" -- "$#")")
then
echo getopt failed
logger -t ngp "rpcsh: getopt failed"
exit 1
fi
sshvars=( -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null )
eval set -- "${args[#]}"
pushvars=""
pushfuncs=""
while [ -n "$1" ]
do
case $1 in
-h|--host) host=$2;
shift; shift;;
-u|--user) user=$2;
shift; shift;;
-v|--pushvars) pushvars="$pushvars $2";
shift; shift;;
-f|--pushfuncs) pushfuncs="$pushfuncs $2";
shift; shift;;
-r|--run) run=$2;
shift; shift;;
-A) sshvars=( "${sshvars[#]}" -A );
shift;;
-i) sshvars=( "${sshvars[#]}" -i $2 );
shift; shift;;
--) shift; break;;
esac
done
remote_args=( "$#" )
vars=$([ -z "$pushvars" ] || declare -p $pushvars 2>/dev/null)
ssh ${sshvars[#]} ${user}#${host} "
#set -x
$(declare -p remote_args )
$vars
$(declare -f $pushfuncs )
$run ${remote_args[#]}
"
}
I want to design a shell script as a wrapper for a couple of scripts. I would like to specify parameters for myshell.sh using getopts and pass the remaining parameters in the same order to the script specified.
If myshell.sh is executed like:
myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
All of the above should be able to call as
test.sh param1 param2 param3
Is it possible to utilize the options parameters in the myshell.sh and post remaining parameters to underlying script?
I wanted to do something similar to the OP, and I found the relevant information I required here and here
Essentially if you want to do something like:
script.sh [options] ARG1 ARG2
Then get your options like this:
while getopts "h:u:p:d:" flag; do
case "$flag" in
h) HOSTNAME=$OPTARG;;
u) USERNAME=$OPTARG;;
p) PASSWORD=$OPTARG;;
d) DATABASE=$OPTARG;;
esac
done
And then you can get your positional arguments like this:
ARG1=${#:$OPTIND:1}
ARG2=${#:$OPTIND+1:1}
More information and details are available through the link above.
myshell.sh:
#!/bin/bash
script_args=()
while [ $OPTIND -le "$#" ]
do
if getopts h:d:s: option
then
case $option
in
h) host_name="$OPTARG";;
d) wait_time="$OPTARG";;
s) script="$OPTARG";;
esac
else
script_args+=("${!OPTIND}")
((OPTIND++))
fi
done
"$script" "${script_args[#]}"
test.sh:
#!/bin/bash
echo "$0 $#"
Testing the OP's cases:
$ PATH+=:. # Use the cases as written without prepending ./ to the scripts
$ myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
./test.sh param1 param2 param3
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
./test.sh param1 param2 param3
$ myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
./test.sh param1 param2 param3
What's going on:
getopts will fail if it encounters a positional parameter. If it's used as a loop condition, the loop would break prematurely whenever positional parameters appear before options, as they do in two of the test cases.
So instead, this loop breaks only once all parameters have been processed. If getopts doesn't recognize something, we just assume it's a positional parameter, and stuff it into an array while manually incrementing getopts's counter.
Possible improvements:
As written, the child script can't accept options (only positional parameters), since getopts in the wrapper script will eat those and print an error message, while treating any argument like a positional parameter:
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a
./test.sh param1 param2 opt1 param3
If we know the child script can only accept positional parameters, then myshell.sh should probably halt on an unrecognized option. That could be as simple as adding a default last case at the end of the case block:
\?) exit 1;;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a
If the child script needs to accept options (as long as they don't collide with the options in myshell.sh), we could switch getopts to silent error reporting by prepending a colon to the option string:
if getopts :h:d:s: option
Then we'd use the default last case to stuff any unrecognized option into script_args:
\?) script_args+=("-$OPTARG");;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./test.sh param1 param2 -a opt1 param3
Mix opts and args :
ARGS=""
echo "options :"
while [ $# -gt 0 ]
do
unset OPTIND
unset OPTARG
while getopts as:c: options
do
case $options in
a) echo "option a no optarg"
;;
s) serveur="$OPTARG"
echo "option s = $serveur"
;;
c) cible="$OPTARG"
echo "option c = $cible"
;;
esac
done
shift $((OPTIND-1))
ARGS="${ARGS} $1 "
shift
done
echo "ARGS : $ARGS"
exit 1
Result:
bash test.sh -a arg1 arg2 -s serveur -c cible arg3
options :
option a no optarg
option s = serveur
option c = cible
ARGS : arg1 arg2 arg3
getopts won't parse the mix of param1 and -n options.
It is much better to put param1-3 into options like others.
Furthermore you can use already existing libraries such as shflags. It is pretty smart and it is easy to use.
And the last way is to write your own function to parse params without getopts, just iterating all params through case construction. It is the hardest way but it is the only way to match your expectations exactly.
I thought up one way that getopts can be extended to truly mix options and positional parameters. The idea is to alternate between calling getopts and assigning any positional parameters found to n1, n2, n3, etc.:
parse_args() {
_parse_args 1 "$#"
}
_parse_args() {
local n="$1"
shift
local options_func="$1"
shift
local OPTIND
"$options_func" "$#"
shift $(( OPTIND - 1 ))
if [ $# -gt 0 ]; then
eval test -n \${n$n+x}
if [ $? -eq 0 ]; then
eval n$n="\$1"
fi
shift
_parse_args $(( n + 1 )) "$options_func" "$#"
fi
}
Then in the OP's case, you could use it like:
main() {
local n1='' n2='' n3=''
local duration hostname script
parse_args parse_main_options "$#"
echo "n1 = $n1"
echo "n2 = $n2"
echo "n3 = $n3"
echo "duration = $duration"
echo "hostname = $hostname"
echo "script = $script"
}
parse_main_options() {
while getopts d:h:s: opt; do
case "$opt" in
d) duration="$OPTARG" ;;
h) hostname="$OPTARG" ;;
s) script="$OPTARG" ;;
esac
done
}
main "$#"
Running it shows the output:
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
n1 = param1
n2 = param2
n3 = param3
duration = waittime
hostname = hostname
script = test.sh
Just a proof of concept, but maybe it's useful to someone.
Note: there's a gotcha if one function that uses parse_args calls another function that uses parse_args and the outer function declares e.g. local n4='', but the inner one doesn't and 4 or more positional parameters are passed to the inner function
Just mashed up a quickie, which easily handles a mixture of options and positional-parameters (leaving only positional-params in $#):
#!/bin/bash
while [ ${#} -gt 0 ];do OPTERR=0;OPTIND=1;getopts "p:o:hvu" arg;case "$arg" in
p) echo "Path: [$OPTARG]" ;;
o) echo "Output: [$OPTARG]" ;;
h) echo "Help" ;;
v) echo "Version" ;;
\?) SET+=("$1") ;;
*) echo "Coding error: '-$arg' is not handled by case">&2 ;;
esac;shift;[ "" != "$OPTARG" ] && shift;done
[ ${#SET[#]} -gt 0 ] && set "" "${SET[#]}" && shift
echo -e "=========\nLeftover (positional) parameters (count=$#) are:"
for i in `seq $#`;do echo -e "\t$i> [${!i}]";done
Sample output:
[root#hots:~]$ ./test.sh 'aa bb' -h -v -u -q 'cc dd' -p 'ee ff' 'gg hh' -o ooo
Help
Version
Coding error: '-u' is not handled by case
Path: [ee ff]
Output: [ooo]
=========
Leftover (positional) parameters (count=4) are:
1> [aa bb]
2> [-q]
3> [cc dd]
4> [gg hh]
[root#hots:~]$
Instead of using getopts, you can directly implement your own bash argument parser. Take this as a working example. It can handle simultaneously name and position arguments.
#!/bin/bash
function parse_command_line() {
local named_options;
local parsed_positional_arguments;
yes_to_all_questions="";
parsed_positional_arguments=0;
named_options=(
"-y" "--yes"
"-n" "--no"
"-h" "--help"
"-s" "--skip"
"-v" "--version"
);
function validateduplicateoptions() {
local item;
local variabletoset;
local namedargument;
local argumentvalue;
variabletoset="${1}";
namedargument="${2}";
argumentvalue="${3}";
if [[ -z "${namedargument}" ]]; then
printf "Error: Missing command line option for named argument '%s', got '%s'...\\n" "${variabletoset}" "${argumentvalue}";
exit 1;
fi;
for item in "${named_options[#]}";
do
if [[ "${item}" == "${argumentvalue}" ]]; then
printf "Warning: Named argument '%s' got possible invalid option '%s'...\\n" "${namedargument}" "${argumentvalue}";
exit 1;
fi;
done;
if [[ -n "${!variabletoset}" ]]; then
printf "Warning: Overriding the named argument '%s=%s' with '%s'...\\n" "${namedargument}" "${!variabletoset}" "${argumentvalue}";
else
printf "Setting '%s' named argument '%s=%s'...\\n" "${thing_name}" "${namedargument}" "${argumentvalue}";
fi;
eval "${variabletoset}='${argumentvalue}'";
}
# https://stackoverflow.com/questions/2210349/test-whether-string-is-a-valid-integer
function validateintegeroption() {
local namedargument;
local argumentvalue;
namedargument="${1}";
argumentvalue="${2}";
if [[ -z "${2}" ]];
then
argumentvalue="${1}";
fi;
if [[ -n "$(printf "%s" "${argumentvalue}" | sed s/[0-9]//g)" ]];
then
if [[ -z "${2}" ]];
then
printf "Error: The %s positional argument requires a integer, but it got '%s'...\\n" "${parsed_positional_arguments}" "${argumentvalue}";
else
printf "Error: The named argument '%s' requires a integer, but it got '%s'...\\n" "${namedargument}" "${argumentvalue}";
fi;
exit 1;
fi;
}
function validateposisionaloption() {
local variabletoset;
local argumentvalue;
variabletoset="${1}";
argumentvalue="${2}";
if [[ -n "${!variabletoset}" ]]; then
printf "Warning: Overriding the %s positional argument '%s=%s' with '%s'...\\n" "${parsed_positional_arguments}" "${variabletoset}" "${!variabletoset}" "${argumentvalue}";
else
printf "Setting the %s positional argument '%s=%s'...\\n" "${parsed_positional_arguments}" "${variabletoset}" "${argumentvalue}";
fi;
eval "${variabletoset}='${argumentvalue}'";
}
while [[ "${#}" -gt 0 ]];
do
case ${1} in
-y|--yes)
yes_to_all_questions="${1}";
printf "Named argument '%s' for yes to all questions was triggered.\\n" "${1}";
;;
-n|--no)
yes_to_all_questions="${1}";
printf "Named argument '%s' for no to all questions was triggered.\\n" "${1}";
;;
-h|--help)
printf "Print help here\\n";
exit 0;
;;
-s|--skip)
validateintegeroption "${1}" "${2}";
validateduplicateoptions g_installation_model_skip_commands "${1}" "${2}";
shift;
;;
-v|--version)
validateduplicateoptions branch_or_tag "${1}" "${2}";
shift;
;;
*)
parsed_positional_arguments=$((parsed_positional_arguments+1));
case ${parsed_positional_arguments} in
1)
validateposisionaloption branch_or_tag "${1}";
;;
2)
validateintegeroption "${1}";
validateposisionaloption g_installation_model_skip_commands "${1}";
;;
*)
printf "ERROR: Extra positional command line argument '%s' found.\\n" "${1}";
exit 1;
;;
esac;
;;
esac;
shift;
done;
if [[ -z "${g_installation_model_skip_commands}" ]];
then
g_installation_model_skip_commands="0";
fi;
}
You would call this function as:
#!/bin/bash
source ./function_file.sh;
parse_command_line "${#}";
Usage example:
./test.sh as 22 -s 3
Setting the 1 positional argument 'branch_or_tag=as'...
Setting the 2 positional argument 'skip_commands=22'...
Warning: Overriding the named argument '-s=22' with '3'...
References:
example_installation_model.sh.md
Checking for the correct number of arguments
https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
An example of how to use getopts in bash
There are some standards for unix option processing, and in shell programming, getopts is the best way of enforcing them. Almost any modern language (perl, python) has a variant on getopts.
This is just a quick example:
command [ options ] [--] [ words ]
Each option must start with a dash, -, and must consist of a single character.
The GNU project introduced Long Options, starting with two dashes --,
followed by a whole word, --long_option. The AST KSH project has a getopts that also supports long options, and long options starting with a single dash, -, as in find(1) .
Options may or may not expect arguments.
Any word not starting with a dash, -, will end option processing.
The string -- must be skipped and will end option processing.
Any remaining arguments are left as positional parameters.
The Open Group has a section on Utility Argument Syntax
Eric Raymond's The Art of Unix Programming has a chapter on traditional unix choices for option letters and their meaning.
You can try this trick: after while loop with optargs, just use this snippet
#shift away all the options so that only positional agruments
#remain in $#
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$#"
However, this approach has a bug:
all the options after the first positional argument are ingored by getopts and are considered as positional arguments - event those that are correct (see sample output: -m and -c are among positional arguments)
Maybe it has even more bugs...
Look at the whole example:
while getopts :abc opt; do
case $opt in
a)
echo found: -a
;;
b)
echo found: -b
;;
c)
echo found: -c
;;
\?) echo found bad option: -$OPTARG
;;
esac
done
#OPTIND-1 now points to the first arguments not beginning with -
#shift away all the options so that only positional agruments
#remain in $#
for (( i=0; i<OPTIND-1; i++)); do
shift
done
POSITIONAL="$#"
echo "positional: $POSITIONAL"
Output:
[root#host ~]# ./abc.sh -abc -de -fgh -bca haha blabla -m -c
found: -a
found: -b
found: -c
found bad option: -d
found bad option: -e
found bad option: -f
found bad option: -g
found bad option: -h
found: -b
found: -c
found: -a
positional: haha blabla -m -c