Parsing long command-line arguments not working with getopt - bash

I want to parse long command-line arguments for a large script as part of my current project. I have never tried getopt before but want to try the first time to make the script look tidy.
Before trying to push getopt on that large project script, I thought of checking it first on a sample script.
In the following example script, parsing short command-line arguments work fine but not the long command-line arguments:
#!/bin/bash
options=$(getopt -o d:f:t: -l domain -l from -l to -- "$#")
[ $? -eq 0 ] || {
echo "Incorrect options provided"
exit 1
}
eval set -- "$options"
while true; do
case "$1" in
-d|--domain)
DOMAIN=$2;
shift
;;
-f|--from)
FROM=$2;
shift
;;
-t|--to)
TO=$2;
shift
;;
--)
shift
break
;;
*)
echo "Invalid options!!";
exit 1
;;
esac
shift
done
echo "Domain is $DOMAIN"
echo "From address is $FROM"
echo "To address is $TO"
exit 0;
Output:
# ./getopt_check.bash -d hello.com -f from#test.com -t to#test.com
Domain is hello.com
From address is from#test.com
To address is to#test.com
# ./getopt_check.bash --domain hello.com -f from#test.com -t to#test.com
Invalid options!!
# ./getopt_check.bash --domain hello.com --from from#test.com --to to#test.com
Invalid options!!
I am expecting the same output as well when parsing long command arguments:
Domain is hello.com
From address is from#test.com
To address is to#test.com
When debugging:
# bash -x getopt_check.bash --domain hello.com -f from#test.com -t to#test.com
++ getopt -o d:f:t: -l domain -l from -l to -- --domain hello.com -f from#test.com -t to#test.com
+ options=' --domain -f '\''from#test.com'\'' -t '\''to#test.com'\'' -- '\''hello.com'\'''
+ '[' 0 -eq 0 ']'
+ eval set -- ' --domain -f '\''from#test.com'\'' -t '\''to#test.com'\'' -- '\''hello.com'\'''
++ set -- --domain -f from#test.com -t to#test.com -- hello.com
+ true
+ case "$1" in
+ DOMAIN=-f
+ shift
+ shift
+ true
+ case "$1" in
+ echo 'Invalid options!!'
Invalid options!!
+ exit 1
Here, the issue is passing case switch OR choice -d|--domain ?.

I guess its your getopt syntax. Use :
getopt -o d:f:t: -l domain:,from:,to: -- "$#"
Instead of :
getopt -o d:f:t: -l domain -l from -l to -- "$#"

Related

Trying to set only long options in my Bash script but getting "Error parsing" message

I'm currently trying to set up my machine learning experiment to use Bash long options for Python arguments but am getting a parsing error. My script looks like this:
#! /usr/bin/bash
CUDA_VISIBLE_DEVICES="0,1,2,3"
script_name=$(basename "$0")
long=learning_rate:,batch_size
TEMP=$(getopt --long $long --name "$script_name" -- "$#")
eval set -- "${TEMP}"
while :; do
case "${1}" in
--learning_rate ) LEARNING_RATE=$2; shift 2 ;;
--batch_size ) BATCH_SIZE=$2; shift 2 ;;
* ) echo "Error parsing"; exit 1 ;;
esac
done
python some_script.py \
--learning_rate $LEARNING_RATE \
--batch_size "$BATCH_SIZE"
I'm not that familiar with Bash and am wondering where the code went wrong. How can I fix this issue?
Adding short options seems to fix the issue:
#! /usr/bin/bash
CUDA_VISIBLE_DEVICES="0,1,2,3"
script_name=$(basename "$0")
long=learning_rate:,batch_size:
TEMP=$(getopt --longoptions $long --name "$script_name" -o r:s: -- "$#")
eval set -- "${TEMP}"
while test "$1" != "--"; do
case "${1}" in
--learning_rate) LEARNING_RATE=$2; shift 2 ;;
--batch_size ) BATCH_SIZE=$2; shift 2 ;;
*) echo "Error parsing"; exit 1;;
esac
done
python some_script.py \
--learning_rate $LEARNING_RATE \
--batch_size "$BATCH_SIZE"

How to use getopt long option in bash script

I want to write one script something like below with the long option to be provided by user in cmd line argument.
example:
./script.sh --user username --branch branchname --start_time yyy-mm-dd
If possible ignore the order. Not sure if we can apply the logic to ignore the order.
Also, force user to provide the value otherwise throw error message that missing value.
Pasting code block
script_name=$(basename "$0")
short=u:c:e:p:b:t:n:
long=user:,component:,engagement:,product:,branch:,tag:,name:,help
TEMP=$(getopt -o $short --long $long --name "$script_name" -- "$#")
eval set -- "${TEMP}"
while :; do
case "${1}" in
-u | --user ) user="$2"; shift 2 ;;
-c | --component ) comp="$2"; COMP=$(echo $comp | tr [:upper:] [:lower:]) ; shift 2 ;;
-e | --engagement ) eng="$2"; ENG=$(echo $eng | tr [:lower:] [:upper:]) ; shift 2 ;;
-p | --product ) product="$2"; PRODUCT=$(echo $product | tr [:lower:] [:upper:]) ; shift 2 ;;
-b | --branch ) branch="$2"; shift 2 ;;
-t | --tag ) tag="$2"; shift 2 ;;
-n | --name ) name="$2"; shift 2 ;;
--help ) usage; exit 0 ;;
-- ) shift; break ;;
* ) echo "invalid option"; exit 1 ;;
esac
done
script_name=$(basename "$0")
short=u:b:s:
long=user:,branch:,start_time:,help
read -r -d '' usage <<EOF
Manually written help section here
EOF
TEMP=$(getopt -o $short --long $long --name "$script_name" -- "$#")
eval set -- "${TEMP}"
while :; do
case "${1}" in
-u | --user ) user=$2; shift 2 ;;
-b | --branch ) branch=$2; shift 2 ;;
-s | --start_time ) start_time=$2; shift 2 ;;
--help ) echo "${usage}" 1>&2; exit ;;
-- ) shift; break ;;
* ) echo "Error parsing"; exit 1 ;;
esac
done
Set your short and long options. A colon implies it needs an option argument.
Short options are written with no delimiter, long options are comma delimited.
The getopt command makes sure items come in an easily parsable order. In this example, we use a case statement to parse each option. All options are parsed first and then removed from $#.
What's left are the passed arguments that is not part of any defined option.

Convert for loop to while loop in bash script

I have a for loop as follows:
mapfile -t ipLast < SippIPs.txt
echo " ---- SIPp ---- "
echo "Please give count of SIPps needed to generate calls.."
read -p 'Sipps count starts from: ' start; read -p 'Sipps count ends on: ' end
if [[ -z $start ]] || [[ -z $end ]]; then echo "User pressed ENTER with no input text"; fi
for ((j=$start; j<=$end; j++)); do
sipps=${j[#]}
ipList=(${ipLast[sipps-1]})
if [[ "$end" -eq 0 ]]; then
echo "No way it cannot end on 0"
exit
fi
echo " ---- Launching SIPp $sipps ---- "
sshpass -p "root12" ssh -tt -o StrictHostKeyChecking=no root#$ipList <<EOF1
pkill -f sipp
screen -S sipp -d -m bash -c 'cd /usr/local/src/sipp-3.3; ulimit -Hn 65535; ulimit -Sn 65535; ./sipp -i $ipList -mi $ipList -sf HA_demo.xml -inf HA_demo.csv 10.171.0.231:5060 -p 5060 -r 1 -rp 1s -l 1 -m 1 -watchdog_minor_threshold 1500 -watchdog_major_threshold 4000 -watchdog_major_maxtriggers 30 -trace_err -aa -d 350s -oocsn ooc_default -t u1 -trace_screen -skip_rlimit && exec bash'
exit
EOF1
done
I want to convert the for loop to while loop such that after giving the count it returns to the loop to ask the user if they want to quit or keep launching SIPp.
My try:
mapfile -t ipLast < SippIPs.txt
read -p 'Sipps count starts from: ' start; read -p 'Sipps count ends on: ' end
j=$start
while (j<=$end); do
sipps=${j[#]}
ipList=(${ipLast[sipps-1]})
if [[ "$end" -eq 0 ]]; then
echo "No way it cannot end on 0"
exit
fi
echo " ---- Launching SIPp $sipps ---- "
sshpass -p "root12" ssh -tt -o StrictHostKeyChecking=no root#$ipList <<EOF1
pkill -f sipp
screen -S sipp -d -m bash -c 'cd /usr/local/src/sipp-3.3; ulimit -Hn 65535; ulimit -Sn 65535; ./sipp -i $ipList -mi $ipList -sf HA_demo.xml -inf HA_demo.csv 10.171.0.231:5060 -p 5060 -r 1 -rp 1s -l 1 -m 1 -watchdog_minor_threshold 1500 -watchdog_major_threshold 4000 -watchdog_major_maxtriggers 30 -trace_err -aa -d 350s -oocsn ooc_default -t u1 -trace_screen -skip_rlimit && exec bash'
exit
j++
EOF1
done
The immediate problem is that you need double ((...)) for an arithmetic evaluation. The expression (j<=$end) attempts to run the program j in a subshell with input from the file =$end.
Also, executing j++ on the remote host obviously will not update the local variable j even if you fix the syntax. You apparently want something like
j=$start
while ((j<=$end)); do
:
sshpass -p "root12" ssh -tt -o StrictHostKeyChecking=no root#$ipList <<EOF1
:
EOF1
((j++))
done
The expression sipps=${j[#]} looks quite odd; j is not an array.
Anyway, there is no particular need to switch from a for loop to a while loop here; just break out of the loop if the user indicates that they want to stop looping.
The screen shenanigans look like you are probably Doing It Wrong but without more information about "it" it's hard for us how to suggest any changes. The exec bash is dubious; what should this code actually accomplish?
exit is redundant at the end of a script; the shell will always exit when it finishes executing the script. Perhaps review Pass commands as input to another command (su, ssh, sh, etc)
As always, you will want to avoid using read -p to receive parameters; a much superior design is to have the caller specify the start and end of the range to scan as command-line arguments. Then start will be $1 and end will be $2 (assuming you have no other command-line processing).
So this is what I was looking for:
while :
mapfile -t ipLast < SippIPs.txt
read -p 'Sipps count starts from: ' start; read -p 'Sipps count ends on: ' end
if [[ -z $start ]] || [[ -z $end ]]; then echo "User pressed ENTER with no input text"; fi
echo " ---- SIPp ---- "
echo "Please give count of SIPps needed to generate calls.."
do
for ((j=$start; j<=$end; j++)); do
sipps=${j[#]}
ipList=(${ipLast[sipps-1]})
if [[ "$end" -eq 0 ]]; then
echo "No way it cannot end on 0"
exit
fi
echo " ---- Launching SIPp $sipps ---- "
sshpass -p "root12" ssh -tt -o StrictHostKeyChecking=no root#$ipList <<EOF1
pkill -f sipp
screen -S sipp -d -m bash -c 'cd /usr/local/src/sipp-3.3; ulimit -Hn 65535; ulimit -Sn 65535; ./sipp -i $ipList -mi $ipList -sf HA_demo.xml -inf HA_demo.csv 10.171.0.231:5060 -p 5060 -r 1 -rp 1s -l 1 -m 1 -watchdog_minor_threshold 1500 -watchdog_major_threshold 4000 -watchdog_major_maxtriggers 30 -trace_err -aa -d 350s -oocsn ooc_default -t u1 -trace_screen -skip_rlimit && exec bash'
exit
EOF1
done
read -p 'Do you want to perform failover..(y/n)' cc
if [[ $cc == "y" || $cc == "y" ]]; then
exit
fi
done
Thanks for the help!

How to use getopt with long options in Bash?

I have the following code in Bash:
declare {BPM_USERNAME,BPM_PASSWORD,HOST,TARGET_IP,OVERRIDE_STATUS}=''
OPTS=`getopt -a --longoptions username:,password:,csc:,ip:,override: -n "$0" -- "$#"`
eval set -- "$OPTS"
if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi
while true; do
echo ""
echo $OPTS
echo $1
echo $2
case "$1" in
--username )
BPM_USERNAME=$2
shift 2
;;
--password )
BPM_PASSWORD=$2
shift 2
;;
--csc )
HOST=$2
shift 2
;;
--ip )
TARGET_IP=$2
shift 2
;;
--override )
OVERRIDE_STATUS=$2
shift 2
;;
--)
shift
echo "Breaking While loop"
break
;;
*)
echo ""
echo "Error in given Parameters. Undefined: "
echo $*
echo ""
echo "Usage: $0 [--username BPM_USERNAME] [--password BPM_PASSWORD] [--ip IP ADDRESS_OF_VyOS/BPM] [--csc CLIENT_SHORT_CODE] [--override TRUE/FALSE]"
exit 1
esac
done
I give Bash the following command (name of script is UpdateSSL.sh):
./UpdateSSL.sh -username bpmadmin -password bpmadmin -ip 10.91.201.99 -csc xyz -override False
But instead of parsing the options, I get back the following result (showing that the while loop goes to the *) case):
'bpmadmin' --password 'bpmadmin' --ip '10.91.201.99' --csc 'xyz' --override 'False' --
bpmadmin
--password
Error in given Parameters. Undefined:
bpmadmin --password bpmadmin --ip 10.91.201.99 --csc xyz --override False --
Usage: ./UpdateSSL.sh [--username BPM_USERNAME] [--password BPM_PASSWORD] [--ip IP ADDRESS_OF_VyOS/BPM] [--csc CLIENT_SHORT_CODE] [--override TRUE/FALSE]
I don't know what I'm doing wrong.
The answer is actually at the very end of the man page:
The syntax if you do not want any short option variables at all is not very intuitive (you have to set them explicitly to the empty string).
In order to make getopt run with no short options, you have to manually specify -o '' as the first argument. I made some other changes, and the below works on my system (see *** markers):
#!/bin/bash
# *** Make sure you have a new enough getopt to handle long options (see the man page)
getopt -T &>/dev/null
if [[ $? -ne 4 ]]; then echo "Getopt is too old!" >&2 ; exit 1 ; fi
declare {BPM_USERNAME,BPM_PASSWORD,HOST,TARGET_IP,OVERRIDE_STATUS}=''
OPTS=$(getopt -o '' -a --longoptions 'username:,password:,csc:,ip:,override:' -n "$0" -- "$#")
# *** Added -o '' ; surrounted the longoptions by ''
if [[ $? -ne 0 ]] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi
# *** This has to be right after the OPTS= assignment or $? will be overwritten
set -- $OPTS
# *** As suggested by chepner
while true; do
# ... no changes in the while loop
done

How to use getopt with this several values?

I try to achieve a script with multi options. I started with the doc, get some errors, went to the browser. Read some links and find this on SO : Using getopts in bash shell script to get long and short command line options.
So I read it and rewrote my script. I made a mistake somewhere. Where am I wrong ?
SH
#!/bin/sh
TEMP=`getopt -o vfts: --long verbose,format,type,style: \
-n 'opt2' -- "$#"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
eval set -- "$TEMP"
VERBOSE=false
FORMAT=
TYPE=
STYLE=
while true; do
case "$1" in
-v | --verbose ) VERBOSE=true; shift ;;
-f | --format ) FORMAT="$2"; shift 2 ;;
-t | --type ) TYPE="$2"; shift 2 ;;
-s | --style ) STYLE="$2"; shift 2 ;;
-- ) shift; break ;;
-*) break ;;
* ) break ;;
esac
done
echo "verbose = $VERBOSE"
echo "format = $FORMAT"
echo "type = $TYPE"
echo "style = $STYLE"
Output
> ./opt2.sh -v -f fofo -t toto -s soso
verbose = true // ok
format = -t // should be fofo
type = // should be toto
style = soso // ok
Your options string is wrong, it should be vf:t:s:. The colon indicates a required argument which each of your options except for v has. Also need to adjust your long options string accordingly.
You could have done some debugging yourself, quite easily:
$ set -- -v -f fofo -t toto -s soso
$ TEMP=$(getopt -o vfts: --long verbose,format,type,style: -- "$#")
$ echo "$TEMP"
-v -f -t -s 'soso' -- 'fofo' 'toto'
Hmm, your -f and -t arguments are disconnected. Make them required
$ TEMP=$(getopt -o vf:t:s: --long verbose,format:,type:,style: -- "$#")
$ echo "$TEMP"
-v -f 'fofo' -t 'toto' -s 'soso' --
To demonstrate that the commas apparently are not strictly required in the --long definition:
$ TEMP=$(getopt -o vf:t:s: --long verbose,format:type:style: -- "$#")
$ echo $?; echo "$TEMP"
0
-v -f 'fofo' -t 'toto' -s 'soso' --

Resources