Running two loops simultaneously in bash - bash

I am reading file in which two different types of key-value pairs are there and I have to pass those key-value pairs to another file. For that I have to run two loops which doesn't seems me the optimized approach. Below the code I am trying and it will also clarify you what I am trying to do :
#!/bin/bash
set -e
# Prints command usage
function usage() {
cat <<EOF
Usage: $0 [--in-file <infile>] [--env <env>]
EOF
}
env=""
# Parse Args
POSITIONAL=()
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-i|--in-file)
in_file=$2
shift 2 # past argument and value
;;
-e|--env)
env="$2"
shift 2 # past argument and value
;;
*) # unknown option
echo "unknown option"
shift # past argument
;;
esac
done
set -- "${POSITIONAL[#]}" # restore positional parameters
if [[ ${in_file} == "" ]]; then
echo "ERROR: Missing in-file argument"
usage
exit 1
fi
cat ${in_file}
echo "Unsilencing alarms"
declare -A summary
overall_status_failed="false"
silence_ids=`yq read -j ${in_file} alarm_ids | jq -M -r .[]`
while read -r silence_id; do
echo "Unsilencing ${silence_id} silence id"
if [env != ""]; then
./unsilence_alarm.sh --alarm-id ${silence_id} --env ${env}|| script_failed="true"
else
./unsilence_alarm.sh --alarm-id ${silence_id}|| script_failed="true"
fi
if [[ "${script_failed}" == "true" ]]; then
summary["silence id ${silence_id} "]='Fail'
overall_status_failed="true"
script_failed="false"
else
summary["silence id ${silence_id} "]='Pass'
fi
done <<< "${silence_ids}"
echo "Unsilencing moody alarms"
silence_ids=`yq read -j ${in_file} moody_alarm_ids | jq -M -r .[]`
while read -r silence_id; do
echo "Unsilencing ${silence_id} moody silence id"
if [env != ""]; then
./unsilence_alarm.sh --moody-alarm-id ${silence_id} --env ${env}|| script_failed="true"
else
./unsilence_alarm.sh --moody-alarm-id ${silence_id}|| script_failed="true"
fi
if [[ "${script_failed}" == "true" ]]; then
summary["moody silence id ${silence_id}"]="Fail"
overall_status_failed="true"
script_failed="false"
else
summary["moody silence id ${silence_id}"]="Pass"
fi
done <<< "${silence_ids}"
set +x
echo
echo
echo
echo +------------------------- Unsilence summary --------------------------+
for status in "${!summary[#]}"; do
printf "${status} - ${summary[$status]}\n"
done
echo +----------------------------------------------------------------------+
if [[ "${overall_status_failed}" == "true" ]]; then
exit 1
fi
exit
Can anyone tell me the optimized approach for merging these two while loops? Another file accepts the parameters like:
[--alarm-id <id>] [--moody-alarm-id <id>] [--env <env>]

Combine both while blocks with the help of associative arrays and wrap it in for
Does this work as expected?
...
declare -A flags
flags[alarm_ids]="--alarm-id"
flags[moody_alarm_ids]="--moody-alarm-id"
for alarm_id in alarm_ids moody_alarm_ids; do
silence_ids=`yq read -j ${in_file} ${alarm_id} | jq -M -r .[]`
while read -r silence_id; do
echo "Unsilencing ${silence_id} silence id"
./unsilence_alarm.sh ${flags[$alarm_id]} ${silence_id} ${env:+"--env ${env}"} \
|| script_failed="true"
if [[ "${script_failed}" == "true" ]]; then
summary["silence ${flag[alarm_id]//-/ } ${silence_id} "]='Fail'
overall_status_failed="true"
script_failed="false"
else
summary["silence ${flag[alarm_id]//-/ } ${silence_id} "]='Pass'
fi
done <<< "${silence_ids}"
done
...
Simultaneous would not be easy. If you do need it, wrap the above code snippet in a function without the for loop; and use a parameter to pass the "key" to the the while loop. And then you could start them as background activities with foo &. But that would not make it easier for summary. Since you need to wait for it to complete anyway - synchronous is the way to go.
Also ${env:+"--env ${env}"} expands to the rhs of + if lhs (env) is not null. Using 4-statement long if-else block seemed tedious. I am not sure if this method is frowned upon, but this is how I personally would have done it.

Related

Free Ansible / Puppet / Chef alternative

This is a question about your opinion on a script I wrote.
At my workplace I wrote a bash-script to execute commands on Linux-Systems (redhat). Since we got Ansible and Puppet nobody was interessted in the script. And to be true: if you got Ansible or Puppet and you are working in a team, it isnt advised to use such a solution... BUT (and thats why I want to post it here and ask for your opinion/improvements on it) if you just want to manage a few Linux hosts and dont want to buy any licence etc. this script may help you out.
the script is called "rotator" and it works like this:
It takes a serverlist and a functionfile. It breaks the Serverlist up into a given maximum number of tmp-lists and executes the commands in the functionfile in the background. Then it waits for all the parallel processes to finish. At the end it checks if all the hosts were visited.
code of rotator.sh:
\#!/bin/bash
usage ()
{
echo ""
echo ""
echo ""
echo "ERROR: $1"
echo ""
echo "You need to provide a serverlist and a"
echo "function file as parameter."
echo "e.g.: sh check_rpm.sh \<server.list\> \<funktions-file.sh\>"
echo "note that the master pushes the config to the client mashines"
echo ""
echo ""
exit 1
}
if \[\[ $# -eq 0 || "$#" == "-h" || "$#" == "--help" || "$#" == "-?" \]\]
then
usage "missing parameter"
exit 1
fi
\######################
# Variablendeklaration
\######################
basedir=$(dirname $0)
hosts=$(cat $1)
funcfile=$2
myhostname=$(hostname -a)
maxlists=100
i=0
j=0
templist=${basedir}/tmp/templist${j}
num=$(cat $1 | wc -l)
length=0
divisor=1
\###################################
# determine num of temp-lists
\###################################
if \[\[ num -gt $maxlists \]\]
then
length=$((num/$maxlists))
((length++))
else
length=1
fi
\######################################
# delete existing old temp lists
\######################################
if \[ ! -z "$(ls -A tmp)" \];
then
rm ./tmp/\*
fi
\######################
# create temp lists
\######################
echo "cutting hostlist"
for host in $hosts;
do
if \[\[ ! "${host^^}" == "${myhostname^^}" \]\]
then
echo $host \>\> $templist
((i++))
fi
if \[\[ $i -eq $length \]\]
then
((j++))
templist=${basedir}/tmp/templist${j}
i=0
fi
done
if \[\[ $j -eq 0 \]\]
then
j=1
fi
\##################################################
# start func-file for all lists and remember PID
\##################################################
k=0
for ((i=0;i\<$j;i++));
do
sleep 0.2
sh $funcfile ${basedir}/tmp/templist${i} &
pids\[${k}\]=$!
((k++))
done
\######################################
# wait till all processes terminate
\######################################
echo "waiting till all processes are done"
for pid in ${pids\[\*\]};
do
wait $pid
done
\#######################
# cleanup temp lists
\#######################
for ((i=0;i\<$j;i++));
do
rm -f ${basedir}/tmp/templist${i}
done
\##########################################
# determine if all hosts were visited
\##########################################
echo "determine if all hosts were visited..."
checkhosts=$(cat tmp/check.list)
done=false
absenthosts=""
for host in $hosts
do
for server in $checkhosts
do
host=$(echo ${host^^})
server=$(echo ${server^^})
if \[\[ "$host" == "$server" \]\]
then
done=true
fi
done
if \[\[ "$done" == "false" \]\]
then
absenthosts+="$host "
else
done=false
fi
done
rm tmp/check.list
\############################################
# tell user which hosts were not visited
\############################################
echo "the following hosts werent visited and need to be handled manually"
echo ""
for host in $absenthosts
do
echo "$host"
done
**# END OF SCRIPT**
Here is an example of a function-file:
\#!/bin/bash
#
#
#
# ================||
# PLEASE BE AWARE ||
# ================||
# Add the code, which should be executed within the marked space
# instead of using "ssh" use $SSHOPTS
#
#
hosts=$(cat $1)
SSHOPTS="ssh -q -o BatchMode=yes -o ConnectTimeout=10"
for host in $hosts
do
$SSHOPTS $host "hostname" \>\> /opt/c2tools/serverliste_kontrollskripten/rotator/tmp/check.list
#
# MARK: add code after this mark
#
#
# MARK: add code before this mark
#
done
**# END OF SCRIPT**
I would say, that if you combine the rotator with cron-job, you could more or less use it as an free alternative to puppet or ansible, with the bonus that you can write the function-files in the bash scripting language.
Please share your opinion
The script runs, but could be improved

Create a script that makes two touch command in the right order always

For performing one task, I need to make two touch commands in a precise order:
touch aaaa-ref-bbb.done
touch cccc-grp-dddd.done
Beinng aaaa .. dddd any kind of strings. The first string contains "-ref-" and the second string contains "-done-"
I want to make a script that applies both touch commands, independently of the orders that the parameters are passed.
For instance (parameters in the wrong order)
./script.sh bla-grp-bla bleh-ref-bleh
Will produce an output of
touch bleh-ref-bleh
touch bla-grp-bla
If the parameters are written in the right order, the touch commands follow the right order.
I have done several tries and each change goes closer to the goal, but now I'm stuck.
Could you help with this?
#### tool for touch debug mode (set -x / set +x)
#!/bin/bash
#
#### USAGE
##### Constants
#start debug code
exec 5> >(logger -t $0)
BASH_XTRACEFD="5"
PS4='$LINENO: '
set -x
FIRSTPARAM=$1
SECONDPARAM=$2
echo $FIRSTPARAM
echo $SECONDPARAM
dotouch()
{
if [[ "$FIRSTPARAM" =~ 'ref' ]]; then
echo 'correct order, processing...'
sleep 3
firsttouch = $FIRSTPARAM'.done'
secondtouch = $SECONDPARAM'.done'
echo $firsttouch
touch $firsttouch
sleep 1
touch $secondtouch
echo "touch was" $1 $2
else
secondtouch = $FIRSTPARAM'.done'
firstouch = $SECONDPARAM'.done'
touch $firsttouch
sleep 1
touch $secondtouch
echo "touch was" $2 $1
fi
}
if [ "$FIRSTPARAM" =~ "ref" ] || [ "$FIRSTPARAM" =~ "grp" ]; then
dotouch()
echo "touch commands executed"
exit 0
else
echo "Usage: $0 [xxxx_ref_xxxx.tar] [xxxx_grp_yyyy.tar] "
exit 1
fi
exit 0
#end debug code
set +x
You are defining 2sttouch and 1ndtouch variables and using firsttouch and secondtouch. You should use the same variable names.
Let's start by putting your shebang line in the right place, and drastically simplifying the code;
#!/bin/bash
#### tool for touch debug mode (set -x / set +x)
exec 5> >(logger -t $0)
BASH_XTRACEFD="5"
PS4='$LINENO: '
set -x
FIRSTPARAM=$1
SECONDPARAM=$2
echo $FIRSTPARAM
echo $SECONDPARAM
dotouch() {
touch "$1"
echo "$Just touched $1"
return
}
case $FIRSTPARAM in
*"ref"*) dotouch $FIRSTPARAM'.done' ; dotouch $SECONDPARAM'.done' ;;
*"grp"*) dotouch $SECONDPARAM'.done' ; dotouch $FIRSTPARAM'.done' ;;
*) echo "Usage: $0 [xxxx_ref_xxxx.tar] [xxxx_grp_yyyy.tar] " ; exit 1 ;;
esac
exit 0
#end debug code
set +x
There was no need for most of that.
The problem is you are not considering the different cases on the main if before entering to the dotouch function. In your expression you are only evaluating the first parameter so you don't really know the content of the second parameter.
My suggestion is:
Create a doTouch function that simply touches 2 received parameters in the order they are received.
Add the different cases on the main code (if there are more, add more elif statements).
Here is the code (without the debug annotations):
#!/bin/bash
#######################################
# Script function helpers
#######################################
doTouch() {
local ref=$1
local grp=$2
echo "Touching $ref"
touch "$ref"
echo "Touching $grp"
touch "$grp"
echo "Touching order was: $ref $grp"
}
usage() {
echo "Usage: $0 [xxxx_ref_xxxx.tar] [xxxx_grp_yyyy.tar]"
}
#######################################
# Main
#######################################
# Retrieve parameters
FIRSTPARAM=$1
SECONDPARAM=$2
echo $FIRSTPARAM
echo $SECONDPARAM
# Check parameter order and touch
if [[ $FIRSTPARAM == *"ref"* ]] && [[ $SECONDPARAM == *"grp"* ]]; then
doTouch $FIRSTPARAM $SECONDPARM
elif [[ $SECONDPARAM == *"ref"* ]] && [[ $FIRSTPARAM == *"grp"* ]]; then
doTouch $SECONDPARM $FIRSTPARAM
else
usage
exit 1
fi
# Regular exit
exit 0

Bash pass argument for array selection

I'm trying to pass the second argument to get an array and loop trough but im getting this error: ${$2[#]}: bad substitution
my code is:
/etc/init.d/displaycameras start c1
#!/bin/bash
dis1cam1="screen -dmS dis1cam1 sh -c 'omxplayer --avdict rtsp_transport:tcp --win \"0 0 640 428\" rtsp://myvideo --live -n -1'";
camera_feeds=('c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)' 'c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)')
for elt in "${camera_feeds[#]}";do eval $elt;done
# Start displaying camera feeds
case "$1" in
start)
for i in "${$2[#]}"
do
eval eval '$'$i
done
echo "Camera Display 1 Started"
;;
Is there a way to pass the 2nd argument to call the c2 set ?
in this way is working perfect:
#!/bin/bash
dis1cam1="screen -dmS dis1cam1 sh -c 'omxplayer --avdict rtsp_transport:tcp --win \"0 0 640 428\" rtsp://myvideo --live -n -1'";
camera_feeds=('c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)' 'c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)')
for elt in "${camera_feeds[#]}";do eval $elt;done
# Start displaying camera feeds
case "$1" in
start)
for i in "${c1[#]}"
do
eval eval '$'$i
done
echo "Camera Display 1 Started"
;;
I would strongly advise implementing this differently.
#!/usr/bin/env bash
die() { echo "$*" >&2; exit 1; }
[[ $BASH_VERSION = [0-3]* ]] && die "Bash 4.3 or newer needed"
[[ $BASH_VERSION = 4.[0-2].* ]] && die "Bash 4.3 or newer needed"
dis1cam1() { : "code to start camera dis1cam1 here"; )
dis1cam2() { : "code to start camera dis1cam2 here"; )
# ...etc...
camera_feeds__c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)
camera_feeds__c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)
# here, we're showing the iterate-over-all-feeds case
# you can just set var=camera_feeds__c1 yourself if you prefer
for var in "${!camera_feeds__#}"; do # var will be camera_feeds__c1 or camera_feeds__c2
feed_name=${var#camera_feeds__} # feed_name will be "c1" or "c2"
declare -n camera_feeds=$var
for i in "${camera_feeds[#]}"; do
echo "Starting $i in feed $feed_name" >&2
"$i" # look up and run code in variable named in $i
done
unset -n camera_feeds
done
"${camera_feeds__#}" expands to the list of shell variables whose names start with camera_feeds__; this is thus the name of our two arrays.
declare -n camera_feeds=$var then makes camera_feeds an alias for the array presently being iterated over, such that for i in "${camera_feeds[#]}" iterates over that array.
unset -n camera_feeds clears this association.

Dealing with multiple flags via getopt

I am using a script which should be invoked using only a single parameter to each flag as follows:
./testit.sh -n 123 -t tvar -b bvar -s svar my_program.exe flags to my program
where all flags are just flags to the script, and the script will launch my_program.exe with the flags flags to my program. The simple method being used to do this is getopts as follows:
#!/bin/bash
# contents of "test_it.sh"
echo "\$* = "$*
getopt_out=`getopt t:T:b:B:s:S:n:N $*`
echo "getopt_out = "$getopt_out
echo "\$* = "$*
set -- `getopt t:T:b:B:s:S:n:N $*`
echo "\$* = "$*
while [ $1 != -- ]; do
shift
done
shift
echo "**********************************************************************************"
echo "*** The program I want to run is: "$*
echo "**********************************************************************************"
where the (correct) output is:
**********************************************************************************
*** The program I want to run is: my_program.exe flags to my program
**********************************************************************************
I, however, need to send multiple numerical options to -n as follows:
./testit.sh -n 123 456 789 -t tvar -b bvar -s svar my_program.exe flags to my program
which gives incorrect output:
**********************************************************************************
*** The program I want to run is: 456 789 my_program.exe flags to my program
**********************************************************************************
How can I, from within 'testit.sh', get rid of those extra numbers? I am able to deal with them (log their values) at the beginning of the script so that they are no longer needed. Since the testit.sh script relies on shift, is there a way for me to completely disregard (throw out) the numeric values without messing up the flow of the commands, so that shift can still be used?
EDIT: Upon further investigation of my original script, I noticed that the output of getopts is different than the one posted in my minimal example. I have updated the minimal example along with the proposed workaround in an answer, though I would appreciated other (probably more canonical and/or correct) methods for dealing with this).
It turns out the original script which was causing me problems actually used POSIXLY_CORRECT=1 which slightly changes the order of -- in the output. I have posted the new code (including POSIXLY_CORRECT=1), along with my workaround for anyone who is interested:
#!/bin/sh
echo "\$* = "$*
POSIXLY_CORRECT=1
export POSIXLY_CORRECT
getopt_out=`getopt t:T:b:B:s:S:n:N: $*`
echo "getopt_out = "$getopt_out
set -- `getopt t:T:b:B:s:S:n:N: $*`
echo "\$* = "$*
double_dash_too_soon=false
while [ $1 != -- ]; do
echo "\$1 = "$1
case $1 in
-t)
# process this flag
shift
;;
-b)
# process this flag
shift
;;
-s)
# process this flag
shift
;;
-n)
This_num=$2
echo "This_num = "$This_num
shift
while [[ $2 != "-"[a-z,A-Z] ]] ; do
echo "Dealing with number!"
if [[ $1 == "--" ]]; then
echo "WARNING: '--' encountered during numeric reads!!" >&2
double_dash_too_soon=true
fi
shift
done
;;
esac
shift
if [ "$double_dash_too_soon" == true ]; then
double_dash_too_soon=false
echo "(1) \$* -> "$*
set -- `getopt t:T:b:B:s:S:n:N $*`
echo "(2) \$* -> "$*
fi
done
shift
echo "**********************************************************************************"
echo "*** The program I want to run is: "$*
echo "**********************************************************************************"

BASH getopt command returns its own parameters instead of command line parameters

I am creating a BASH script and working with the BASH getopt command to parse command line arguments. Instead of returning the arguments provided on the command line, getopt returns the arguments that were supplied to the getopt command. I am not sure what could be going on, as the code that I have was working perfect, and seemingly out of nowhere, it has stoped working correctly (and no, I haven't updated anything or changed any code or environment settings). I can't use getopts (with the extra 's') because it is, for some unknown reason, not installed on the machine that will be running this script.
Even though the script is supplied with zero command line arguments, the getopt command is for some reason returning all of the arguments that I have supplied, minus the -o flag, instead of the expected -- value indicating the end of the options. The code that I have is as follows:
SHORT_OPTS=":hvso:l:d:n:p:t:"
LONG_OPTS="help,version,submit-job,output:,library:,job-dir:"
LONG_OPTS="${LONG_OPTS},num-nodes:,num-procs:,max-time:"
OPTS=$(getopt -o "${SHORT_OPTS}" -l "${LONG_OPTS}" -a -n "${PROG_NAME}" -- "${#}")
# Check for invalid command line options and arguments
if [[ ${?} -ne ${SUCCESS} ]] ; then
echo -e "${PROG_NAME}: error: Invalid option or argument\n" >&2
usage ; exit ${FAILURE}
else
echo "BEFORE $#"
eval set -- ${OPTS}
echo "AFTER $#"
fi
# Process command line options and their arguments
while true ; do
case "${1}" in
-h | --help)
# Display script usage information and exit
usage ; exit ${SUCCESS} ;;
-v | --version)
# Display script version information and exit
echo "${PROG_NAME} v${PROG_VERSION}" ; exit ${SUCCESS} ;;
-s | --submit-job)
# Enable automatic submission of the Moab job
JOB_AUTO_SUBMIT="${PREF_YES}" ; shift 1 ;;
-o | --output)
# Set the base name for output file names
TARGET="${2}" ; shift 2 ;;
-l | --library)
# Set the library to use for NWChem atomic configurations
NW_LIB="${2}" ; shift 2 ;;
-d | --job-dir)
# Ensure the specified directory for the Moab job exists
if [[ -e "${2}" ]] ; then
JOB_WORK_DIR=$(resolvePath "${2}") ; shift 2
else
echo -e "${PROG_NAME}: error: -d ${2}: No such directory\n"
usage ; exit ${FAILURE}
fi ;;
-n | --num-nodes)
# Ensure the number of compute nodes is greater than zero
if positiveInt "${2}" ; then
JOB_NODES="${2}" ; shift 2
else
echo -n "${PROG_NAME}: error: -n ${1}: Number of "
echo -e "job nodes must be a positive integer\n"
usage ; exit ${FAILURE}
fi ;;
-p | --num-procs)
# Ensure the number of processors per node is greater than zero
if positiveInt "${2}" ; then
JOB_PROCS="${2}" ; shift 2
else
echo -n "${PROG_NAME}: error: -p ${2}: Number of "
echo -e "processors per node must be a positive integer\n"
usage ; exit ${FAILURE}
fi ;;
-t | --max-time)
# Ensure the maximum job runtime is in the correct format
if [[ "${2}" == [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ]] ; then
JOB_MAX_TIME="${2}" ; shift 2
else
echo -n "${PROG_NAME}: error: -t ${2}: Invalid time "
echo -e "format, please use hh:mm:ss format\n"
usage ; exit ${FAILURE}
fi ;;
--)
# No more options to process
shift ; break ;;
esac
done
# Check to see if POTCAR and CONTCAR locations were specified
if [[ ${#} -eq 2 ]] ; then
# Regular expressions for identifying POTCAR and CONTCAR files
PCAR_REGEX="[Pp][Oo][Tt][Cc][Aa][Rr]"
CCAR_REGEX="[Cc][Oo][Nn][Tt][Cc][Aa][Rr]"
# Attempt to identify POTCAR and CONTCAR argument ordering
if [[ ${1} =~ ${PCAR_REGEX} && ${2} =~ ${CCAR_REGEX} ]] ; then
POTCAR="${1}" ; CONTCAR="${2}" ; shift 2
else
POTCAR="${2}" ; CONTCAR="${1}" ; shift 2
fi
# Accept exactly two or zero command line arguments
elif [[ ${#} -ne 0 ]] ; then
echo "${PROG_NAME}: error: ${#}: Invalid argument count, expected [2|0]"
echo "$#"
exit ${FAILURE}
fi
Given this code, and running the application, I get the following output:
BEFORE
AFTER -- :hvso:l:d:n:p:t: -l help,version,submit-job,output:,library:,job-dir:,num-nodes:,num-procs:,max-time: -a -n vasp2nwchem --
vasp2nwchem: error: 7: Invalid argument count, expected [2|0]
:hvso:l:d:n:p:t: -l help,version,submit-job,output:,library:,job-dir:,num-nodes:,num-procs:,max-time: -a -n vasp2nwchem --
So, the code enters the while loop portion of the code, jumps to the last case, and shifts off the first --, leaving me with all of the arguments that I supplied to getopt, minus the -o flag.
Any light that anyone could shed on this conundrum would be immensely appreciated, because it is seriously about to send me over the edge, especially since this code was functional no less than thrity minutes ago, and has now stopped working entirely!!!
I don't see anything wrong. I have GNU getopt installed as /usr/gnu/bin/getopt (and BSD getopt in /usr/bin), so this script (chk.getopt.sh) is almost equivalent to the start of yours, though I do set the PROG_NAME variable. This is more or less the SSCCE (Short, Self-Contained, Complete Example) for your rather substantial script.
#!/bin/bash
PROG_NAME=$(basename $0 .sh)
SHORT_OPTS=":hvso:l:d:n:p:t:"
LONG_OPTS="help,version,submit-job,output:,library:,job-dir:"
LONG_OPTS="${LONG_OPTS},num-nodes:,num-procs:,max-time:"
OPTS=$(/usr/gnu/bin/getopt -o "${SHORT_OPTS}" -l "${LONG_OPTS}" -a -n "${PROG_NAME}" -- "$#")
# Check for invalid command line options and arguments
if [[ ${?} -ne ${SUCCESS} ]] ; then
echo -e "${PROG_NAME}: error: Invalid option or argument\n" >&2
usage ; exit ${FAILURE}
else
echo "BEFORE $#"
eval set -- ${OPTS}
echo "AFTER $#"
fi
When I run it, this is the output:
$ bash -x chk.getopt.sh -ooutput -nnumber -pperhaps -ppotato -- -o oliphaunt
++ basename chk.getopt.sh .sh
+ PROG_NAME=chk.getopt
+ SHORT_OPTS=:hvso:l:d:n:p:t:
+ LONG_OPTS=help,version,submit-job,output:,library:,job-dir:
+ LONG_OPTS=help,version,submit-job,output:,library:,job-dir:,num-nodes:,num-procs:,max-time:
++ /usr/gnu/bin/getopt -o :hvso:l:d:n:p:t: -l help,version,submit-job,output:,library:,job-dir:,num-nodes:,num-procs:,max-time: -a -n chk.getopt -- -ooutput -nnumber -pperhaps -ppotato -- -o oliphaunt
+ OPTS=' -o '\''output'\'' -n '\''number'\'' -p '\''perhaps'\'' -p '\''potato'\'' -- '\''-o'\'' '\''oliphaunt'\'''
+ [[ 0 -ne '' ]]
+ echo 'BEFORE -ooutput' -nnumber -pperhaps -ppotato -- -o oliphaunt
BEFORE -ooutput -nnumber -pperhaps -ppotato -- -o oliphaunt
+ eval set -- -o ''\''output'\''' -n ''\''number'\''' -p ''\''perhaps'\''' -p ''\''potato'\''' -- ''\''-o'\''' ''\''oliphaunt'\'''
++ set -- -o output -n number -p perhaps -p potato -- -o oliphaunt
+ echo 'AFTER -o' output -n number -p perhaps -p potato -- -o oliphaunt
AFTER -o output -n number -p perhaps -p potato -- -o oliphaunt
$
This all looks correct; the options before the double dash have been split from their arguments, and the ones after the double dash are OK.
So, it is not obvious that there's a problem with your code. Even with an empty string as the program name, it worked OK for me.
May be you should show the output of this much of the script on your machine?

Resources