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 "**********************************************************************************"
Related
I have a bash script myscript.sh.
I mean to call another script, command or builtin from within it, e.g., diff.
I mean to pass options to myscript.sh, some of which would be passed to diff when calling it.
The way I implemented this is by setting up an option string optstring via getopt, and then using
eval "diff ${optstring} ${file} ${TRG_DIR}/${filebase2}"
So far, it worked, but I do not know if this is prone to issues when passing arguments with wildcards, etc. At any rate, ...
Is there a better way to do it?
The way I set up optstring is
set -o errexit -o noclobber -o nounset -o pipefail
params="$(getopt -o qy --long brief,side-by-side,suppress-common-lines --name "$0" -- "$#")"
if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi
echo params=$params
echo params=$#
eval set -- "$params"
optstring=""
# These variables are likely not needed
brief=false
sbs=false
scl=false
#while false ; do
while true ; do
case "$1" in
-q|--brief)
optstring=${optstring}" -q"
brief=true
echo "brief"
shift
;;
-y|--side-by-side)
optstring=${optstring}" -y"
sbs=true
echo "side-by-side"
shift
;;
--suppress-common-lines)
optstring=${optstring}" --suppress-common-lines"
scl=true
echo "suppress-common-lines"
shift
;;
--)
shift
break
;;
*)
echo "Not implemented: $1" >&2
exit 1
;;
esac
done
echo optstring=${optstring}
Use an array. Arrays can handle multi-word arguments with whitespace. Initialize a blank array with:
options=()
To append an option, do:
options+=(--suppress-common-lines)
Then finally you can get rid of the eval when you call diff and just call it normally. Make sure to quote all of the variable expansions in case they have whitespace:
diff "${options[#]}" "$file" "$TRG_DIR/$filebase2"
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
I have a shell script that I use to launch some ROS launcher file (not that important for my question). My requirement is to use some arguments and therefore I am using getopts.
Basically, I am getting one file or a few, playing them with ROS and simultaneously recording all the files back to a single file. After that, if I have provided an argument -r or -c, I would like to run two additional operations (reindex and compress) on the recorded file. But it is required that the other process are done, before I can run the -r and -c. I am using the wait keyword, but I am not sure I really understand the flow. In other words, the playing and recording should be done and only then -r and -c should be run if provided as arguments.
Second question is related to how do I get or pass the same file that was outputted to these two functions (reindex and compress)?
So my desired format is:
./myscript -i file1 -i file2 -o /home/output -r -c
#!/bin/bash
OUTPUT_FILE_NAME="output_$(date +%Y.%m.%d--%H_%M_%S)"
usage="
$(basename "$0") [-i] [-o] [-r] [-c] [-h] -- to be done"
# Reset is necessary if getopts was used previously in the script.
OPTIND=1
while getopts ":i:orch:" opt; do
case $opt in
i ) echo "INPUT file - argument = $OPTARG"; input_files+=($OPTARG) ;;
o ) echo "OUTPUT dir - argument = $OPTARG"; output_dir=($OPTARG) ;;
r ) echo "REINDEX"; reindex ;;
c ) echo "COMPRESS"; compress ;;
h ) echo "$usage"
graceful_exit ;;
* ) echo "$usage"
exit 1
esac
done
# Shift off the options
shift $((OPTIND-1))
roslaunch myLauncher.launch &
echo "Number of loaded files: ${#input_files[#]}"
echo -n "FILES are:"
rosbag play ${input_files[#]} &
rosbag record -o $output_dir/$OUTPUT_FILE_NAME -a &
wait
function reindex{
rosbag reindex $output_dir/$OUTPUT_FILE_NAME
}
function compress{
rosbag reindex $output_dir/$OUTPUT_FILE_NAME
}
Thank you in advance!
You're very close to where you need to be — and using getopts puts you firmly on the correct track, too. Note whether or not you need to reindex or compress in the option parsing loop. Then, after the music has been played and the output file written, run the code from the functions if you need to:
#!/bin/bash
OUTPUT_FILE_NAME="output_$(date +%Y.%m.%d--%H_%M_%S)"
usage="$(basename "$0") [-i file] [-o file] [-r] [-c] [-h]"
input_files=() # Empty list of files/tracks
output_dir="." # Default output directory
r_flag="no" # No reindex by default
c_flag="no" # No compress by default
while getopts ":i:orch:" opt; do
case $opt in
(i) echo "INPUT file - argument = $OPTARG"; input_files+=("$OPTARG");;
(o) echo "OUTPUT dir - argument = $OPTARG"; output_dir="$OPTARG";;
(r) echo "REINDEX"; r_flag="yes";;
(c) echo "COMPRESS"; c_flag="yes";;
(h) echo "$usage" >&2; exit 0;;
(*) echo "$usage" >&2; exit 1;;
esac
done
# Shift off the options
shift $((OPTIND-1))
if [ $# != 0 ] || [ "${#input_files[#]}" = 0 ] ||
then
echo "$usage" >&2
exit 1
fi
roslaunch myLauncher.launch &
echo "Number of loaded files: ${#input_files[#]}"
echo -n "FILES are:"
rosbag play "${input_files[#]}" &
rosbag record -o "$output_dir/$OUTPUT_FILE_NAME" -a &
wait
if [ "$r_flag" = "yes" ]
then
rosbag reindex "$output_dir/$OUTPUT_FILE_NAME"
fi
if [ "$c_flag" = "yes" ]
then
rosbag compress "$output_dir/$OUTPUT_FILE_NAME"
fi
I didn't keep the functions since they didn't provide any value in the rewritten code.
How do I check if file exists in bash?
When I try to do it like this:
FILE1="${#:$OPTIND:1}"
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
elif
<more code follows>
I always get following output:
requested file doesn't exist
The program is used like this:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
Any ideas please?
I will be glad for any help.
P.S. I wish I could show the entire file without the risk of being fired from school for having a duplicate. If there is a private method of communication I will happily oblige.
My mistake. Fas forcing a binary file into a wrong place. Thanks for everyone's help.
Little trick to debugging problems like this. Add these lines to the top of your script:
export PS4="\$LINENO: "
set -xv
The set -xv will print out each line before it is executed, and then the line once the shell interpolates variables, etc. The $PS4 is the prompt used by set -xv. This will print the line number of the shell script as it executes. You'll be able to follow what is going on and where you may have problems.
Here's an example of a test script:
#! /bin/bash
export PS4="\$LINENO: "
set -xv
FILE1="${#:$OPTIND:1}" # Line 6
if [ ! -e "$FILE1" ] # Line 7
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1" # Line 12
fi
And here's what I get when I run it:
$ ./test.sh .profile
FILE1="${#:$OPTIND:1}"
6: FILE1=.profile
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1"
fi
7: [ ! -e .profile ]
12: echo 'Found File .profile'
Found File .profile
Here, I can see that I set $FILE1 to .profile, and that my script understood that ${#:$OPTIND:1}. The best thing about this is that it works on all shells down to the original Bourne shell. That means if you aren't running Bash as you think you might be, you'll see where your script is failing, and maybe fix the issue.
I suspect you might not be running your script in Bash. Did you put #! /bin/bash on the top?
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
You may want to use getopts to parse your parameters:
#! /bin/bash
USAGE=" Usage:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
"
while getopts gpr:d: option
do
case $option in
g) g_opt=1;;
p) p_opt=1;;
r) rfunction_id="$OPTARG";;
d) dfunction_id="$OPTARG";;
[?])
echo "Invalid Usage" 1>&2
echo "$USAGE" 1>&2
exit 2
;;
esac
done
if [[ -n $rfunction_id && -n $dfunction_id ]]
then
echo "Invalid Usage: You can't specify both -r and -d" 1>&2
echo "$USAGE" >2&
exit 2
fi
shift $(($OPTIND - 1))
[[ -n $g_opt ]] && echo "-g was set"
[[ -n $p_opt ]] && echo "-p was set"
[[ -n $rfunction_id ]] && echo "-r was set to $rfunction_id"
[[ -n $dfunction_id ]] && echo "-d was set to $dfunction_id"
[[ -n $1 ]] && echo "File is $1"
To (recap) and add to #DavidW.'s excellent answer:
Check the shebang line (first line) of your script to ensure that it's executed by bash: is it #!/bin/bash or #!/usr/bin/env bash?
Inspect your script file for hidden control characters (such as \r) that can result in unexpected behavior; run cat -v scriptFile | fgrep ^ - it should produce NO output; if the file does contain \r chars., they would show as ^M.
To remove the \r instances (more accurately, to convert Windows-style \r\n newline sequences to Unix \n-only sequences), you can use dos2unix file to convert in place; if you don't have this utility, you can use sed 's/'$'\r''$//' file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file); to remove all \r instances (even if not followed by \n), use tr -d '\r' < file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file).
In addition to #DavidW.'s great debugging technique, you can add the following to visually inspect all arguments passed to your script:
i=0; for a; do echo "\$$((i+=1))=[$a]"; done
(The purpose of enclosing the value in [...] (for example), is to see the exact boundaries of the values.)
This will yield something like:
$1=[-g]
$2=[input.txt]
...
Note, though, that nothing at all is printed if no arguments were passed.
Try to print FILE1 to see if it has the value you want, if it is not the problem, here is a simple script (site below):
#!/bin/bash
file="${#:$OPTIND:1}"
if [ -f "$file" ]
then
echo "$file found."
else
echo "$file not found."
fi
http://www.cyberciti.biz/faq/unix-linux-test-existence-of-file-in-bash/
Instead of plucking an item out of "$#" in a tricky way, why don't you shift off the args you've processed with getopts:
while getopts ...
done
shift $(( OPTIND - 1 ))
FILE1=$1
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?