Execution sequence in shell script and passing the right arguments to functions - bash

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.

Related

Update parameter with default value in a bash script [duplicate]

I'm trying to make a getopt command such that when I pass the "-ab" parameter to a script,
that script will treat -ab as a single parameter.
#!/bin/sh
args=`getopt "ab":fc:d $*`
set -- $args
for i in $args
do
case "$i" in
-ab) shift;echo "You typed ab $1.";shift;;
-c) shift;echo "You typed a c $1";shift;;
esac
done
However, this does not seem to work. Can anyone offer any assistance?
getopt doesn't support what you are looking for. You can either use single-letter (-a) or long options (--long). Something like -ab is treated the same way as -a b: as option a with argument b. Note that long options are prefixed by two dashes.
i was struggling with this for long - then i got into reading about getopt and getopts
single char options and long options .
I had similar requirement where i needed to have number of multichar input arguments.
so , i came up with this - it worked in my case - hope this helps you
function show_help {
echo "usage: $BASH_SOURCE --input1 <input1> --input2 <input2> --input3 <input3>"
echo " --input1 - is input 1 ."
echo " --input2 - is input 2 ."
echo " --input3 - is input 3 ."
}
# Read command line options
ARGUMENT_LIST=(
"input1"
"input2"
"input3"
)
# read arguments
opts=$(getopt \
--longoptions "$(printf "%s:," "${ARGUMENT_LIST[#]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$#"
)
echo $opts
eval set --$opts
while true; do
case "$1" in
h)
show_help
exit 0
;;
--input1)
shift
empId=$1
;;
--input2)
shift
fromDate=$1
;;
--input3)
shift
toDate=$1
;;
--)
shift
break
;;
esac
shift
done
Note - I have added help function as per my requirement, you can remove it if not needed
That's not the unix way, though some do it e.g. java -cp classpath.
Hack: instead of -ab arg, have -b arg and a dummy option -a.
That way, -ab arg does what you want. (-b arg will too; hopefully that's not a bug, but a shortcut feature...).
The only change is your line:
-ab) shift;echo "You typed ab $1.";shift;;
becomes
-b) shift;echo "You typed ab $1.";shift;;
GNU getopt have --alternative option
-a, --alternative
Allow long options to start with a single '-'.
Example:
#!/usr/bin/env bash
SOPT='a:b'
LOPT='ab:'
OPTS=$(getopt -q -a \
--options ${SOPT} \
--longoptions ${LOPT} \
--name "$(basename "$0")" \
-- "$#"
)
if [[ $? > 0 ]]; then
exit 2
fi
A=
B=false
AB=
eval set -- $OPTS
while [[ $# > 0 ]]; do
case ${1} in
-a) A=$2 && shift ;;
-b) B=true ;;
--ab) AB=$2 && shift ;;
--) ;;
*) ;;
esac
shift
done
printf "Params:\n A=%s\n B=%s\n AB=%s\n" "${A}" "${B}" "${AB}"
$ ./test.sh -a aaa -b -ab=test
Params:
A=aaa
B=true
AB=test
getopt supports long format. You can search SO for such examples.
See here, for example

Bash - case statement always enters default

I am writing a Bash script to parse a CSV file (values separated by ; character) and extract some arguments. Depending on the current argument read from the file a specific string is appended to a variable. However, the case statement always enters the default state *) and I can't figure out why.
I have no problems reading the .csv file. Already did that and displayed the output of the arguments read from the file. Everything works perfectly. The problem is that the case statement is not processed as expected.
So, the problem is not reading the .csv file but the processing of the arguments in the case statement.
This is my code:
while IFS=";" read -r arg
do
case ${arg} in
"valueX")
var+="blabla"
;;
"valueY")
var+="blublu"
;;
*)
echo -e "Argument ${arg} not supported"
exit 1
;;
esac
done < filename
Assuming "valueX" is the current argument read from the file. Somehow the script always outputs:
Argument "valueX" not supported
Apparently, the argument (here: "valueX") read from the file is correct, but the script won't enter the corresponding state. Instead, it always enters the default state, no matter what value ${arg} holds.
[EDIT]
I thought it would be a good idea to ask the question more generally, but it turns out to be confusing. So here is the full bash script and .csv file:
Script:
#!/bin/bash
# style reset
STYLE_RESET='\e[0m'
# Red foreground color
FOREGROUND_RED='\e[31m'
# Green foreground color
FOREGROUND_GREEN='\e[32m'
# Blue foreground color
FOREGROUND_BLUE='\e[34m'
# Red background color
BACKGROUND_RED='\e[41m'
# Green background color
BACKGROUND_GREEN='\e[42m'
# Blue background color
BACKGROUND_BLUE='\e[44m'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
usage()
{
echo "ToDo"
exit 1
}
# --------------------------------------- #
# --- Checking Command Line Arguments --- #
# --------------------------------------- #
# Supported command line arguments:
# -h|--help
# -a|--address IP of SSH server for remote VMAF
# -u|--user User for SSH server login
# -d|--doe DoE worksheet exported as CSV UTF-8 file
PARAMS=""
while (( "$#" )); do
case "$1" in
-h|--help)
usage
shift
;;
-u|--user)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then # Check length of argument and first character of argument != '-'
SSH_USER=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-a|--address)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then # Check length of argument and first character of argument != '-'
SSH_IP=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-d|--doe)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then # Check length of argument and first character of argument != '-'
DOE_FILE=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-*|--*=) # unsupported flags
echo "Error: Unsupported flag $1" >&2
exit 1
;;
*) # DEFAULT
PARAMS="${PARAMS} $1" # preserve positional arguments
shift
;;
esac
done
# set positional arguments in their proper place
eval set -- "${PARAMS}"
# ---------------------- #
# --- Processing DoE --- #
# ---------------------- #
echo -e "${BACKGROUND_BLUE}Processing DoE specified in file ${DOE_FILE}:${STYLE_RESET}"
echo -e "${BACKGROUND_BLUE}Configuring Video source for GStreamer pipeline...${STYLE_RESET}"
GSTPIPE_SRC="gst-launch-1.0 -e "
run=1
while IFS=";" read -r motion bitrate_in bitrate_out twopass iframe quantI quantP quantB mvbuffer cabac vbv
do
echo -e "\n\n${BACKGROUND_BLUE}Setting #${run}:${STYLE_RESET}"
echo -e "${BACKGROUND_BLUE}${motion} ${bitrate_in} ${bitrate_out} ${twopass} ${iframe} ${quantI} ${quantP} ${quantB} ${mvbuffer} ${cabac} ${vbv}${STYLE_RESET}"
echo -e "\n${BACKGROUND_BLUE}Generating GStreamer pipelines...${STYLE_RESET}"
case ${motion} in
"low")
GSTPIPE_SRC+="videotestsrc pattern=colors num-buffers=300 ! " # -> no motion content
case ${bitrate_in} in # -> bitrate of video source (width*height*framerate)
"low") # -> 640x480
GSTPIPE_SRC+="'video/x-raw, width=(int)640, height=(int)480, framerate=(fraction)30/1, format=(string)I420' "
width=640
height=480
fps=30
;;
"high") # -> 3840x2160
GSTPIPE_SRC+="'video/x-raw, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)I420' "
width=3840
height=2160
fps=30
;;
*)
echo -e "\n\n${BACKGROUND_RED}Input bitrate ${bitrate_in} not supported${STYLE_RESET}"
echo -e "Use low, or high instead"
exit 1
;;
esac
;;
"high")
GSTPIPE_SRC+="filesrc location=${SCRIPT_DIR}/extensive.mp4 " # -> high motion content
case ${bitrate_in} in # -> bitrate of video source (width*height*framerate)
"low") # -> 640x480
GSTPIPE_SRC+="blocksize=460800 ! " # blocksize=width*height*bytesPerPixel (I420->12bit->bytesPerPixel=1.5)
GSTPIPE_SRC+="'video/x-raw, width=(int)640, height=(int)480, framerate=(fraction)30/1, format=(string)I420' "
width=640
height=480
fps=30
;;
"high") # -> 3840x2160
GSTPIPE_SRC+="blocksize=12441600 ! " # blocksize=width*height*bytesPerPixel (I420->12bit->bytesPerPixel=1.5)
GSTPIPE_SRC+="'video/x-raw, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)I420' "
width=3840
height=2160
fps=30
;;
*)
echo -e "\n\n${BACKGROUND_RED}Input bitrate ${bitrate_in} not supported${STYLE_RESET}"
echo -e "Use low, or high instead"
exit 1
;;
esac
;;
*)
echo -e "${BACKGROUND_RED}Argument ${motion} for DoE factor 'motion' not supported${STYLE_RESET}"
echo -e "Use low, or high instead"
exit 1
;;
esac
GSTPIPE_ENC=$GSTPIPE_SRC
GSTPIPE_REF=$GSTPIPE_SRC
GSTPIPE_REF+="! y4menc ! filesink location=${SCRIPT_DIR}/reference${fps}fps_run${run}.y4m"
GSTPIPE_ENC+="! nvvidconv ! 'video/x-raw(memory:NVMM)' ! nvv4l2h264enc "
GSTPIPE_ENC+="bitrate=${bitrate_out} EnableTwopassCBR=${twopass} "
case ${iframe} in
"low")
GSTPIPE_ENC+="iframeinterval=20 SliceIntraRefreshInterval=10 "
;;
"high")
GSTPIPE_ENC+="iframeinterval=120 SliceIntraRefreshInterval=60 "
;;
*)
echo -e "${BACKGROUND_RED}Argument ${motion} for DoE factor iframe is not supported${STYLE_RESET}"
echo -e "Use low, or high instead"
exit 1
;;
esac
# The range of B frames does not take effect if the number of B frames is 0. (https://docs.nvidia.com/jetson/l4t/index.html#page/Tegra%20Linux%20Driver%20Package%20Development%20Guide/accelerated_gstreamer.html#wwpID0E0YX0HA)
GSTPIPE_ENC+="quant-i-frames=${quantI} quant-p-frames=${quantP} quant-b-frames=${quantB} num-b-frames=1 EnableMVBufferMeta=${mvbuffer} cabac-entropy-coding=${cabac} "
GSTPIPE_ENC+="! nvv4l2decoder ! nvvidconv ! 'video/x-raw' ! y4menc ! filesink location=${SCRIPT_DIR}/distorted${fps}fps_run${run}.y4m"
echo -e "${BACKGROUND_BLUE}Distorted Video:${STYLE_RESET}"
echo -e "${FOREGROUND_BLUE}${GSTPIPE_ENC}${STYLE_RESET}"
echo -e "${BACKGROUND_BLUE}Reference Video:${STYLE_RESET}"
echo -e "${FOREGROUND_BLUE}${GSTPIPE_REF}${STYLE_RESET}"
# --- Launching GStreamer pipelines (surpress detailed output) --- #
echo -e "${BACKGROUND_BLUE}Launching GStreamer pipeline for encoded video (distorted):${STYLE_RESET}"
eval "${GSTPIPE_ENC[#]}" #> /dev/null
echo -e "${BACKGROUND_BLUE}Launching GStreamer pipeline for uncompressed video (reference):${STYLE_RESET}"
eval "${GSTPIPE_REF[#]}" #> /dev/null
# --- Create and Check Remote Directories --- #
echo -e "\n${BACKGROUND_BLUE}Video transfer to remote machine:${STYLE_RESET}"
SSH_DIR_ADD="v4l2h264/motion_${motion}/bitrate_${bitrate_in}/"
SSH_DIR="/home/${SSH_USER}/metrics/${SSH_DIR_ADD}" # Create variable holding path of directory for both:
# 1.) reference.y4m and distorted.y4m videos
# 2.) remote VMAF
ssh ${SSH_USER}#${SSH_IP} "test -d ${SSH_DIR}" < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
if [ $? -ne 0 ]; then # Directory does not exist
echo -e "${BACKGROUND_BLUE}Creating remote directory for run #${run}: ${SSH_DIR}...${STYLE_RESET}"
ssh ${SSH_USER}#${SSH_IP} "mkdir -p ${SSH_DIR}" < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
else # Directory already exists
echo -e "${BACKGROUND_BLUE}Remote directory ${SSH_DIR} already exists${STYLE_RESET}"
fi
# --- Transfer Video Files --- #
echo -e "${BACKGROUND_BLUE}Transfering reference and distorted videos of run #${run}:${STYLE_RESET}"
scp ${SCRIPT_DIR}/distorted${fps}fps_run${run}.y4m ${SSH_USER}#${SSH_IP}:${SSH_DIR} < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
scp ${SCRIPT_DIR}/reference${fps}fps_run${run}.y4m ${SSH_USER}#${SSH_IP}:${SSH_DIR} < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
# --- Run VMAF on Remote Machine --- #
echo -e "\n${BACKGROUND_BLUE}Running VMAF metric for DoE run #${run} on remote machine...${STYLE_RESET}"
ssh ${SSH_USER}#${SSH_IP} "vmaf -r ${SSH_DIR}/reference${fps}fps_run${run}.y4m -d ${SSH_DIR}/distorted${fps}fps_run${run}.y4m -w ${width} -h ${height} -p 420 -b 12 -o ${SSH_DIR}/log${fps}fps_run${run}.xml --threads 8" < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
echo -e "${BACKGROUND_BLUE}VMAF metric for DoE run #${run} finished.${STYLE_RESET}"
# --- Remove Videos on Remote Machine (Laptop) --- #
echo -e "\n${BACKGROUND_BLUE}Removing videos from remote machine${STYLE_RESET}"
ssh ${SSH_USER}#${SSH_IP} "rm ${SSH_DIR}/distorted${fps}fps_run${run}.y4m ${SSH_DIR}/reference${fps}fps_run${run}.y4m" < /dev/null # see https://stackoverflow.com/questions/9393038/ssh-breaks-out-of-while-loop-in-bash
# --- Remove videos on local machine (SPU) --- #
echo -e "\n${BACKGROUND_BLUE}Removing videos from local machine${STYLE_RESET}"
rm distorted${fps}fps_run${run}.y4m reference${fps}fps_run${run}.y4m
((run++))
done < <(cut -d ";" -f5,6,7,8,9,10,11,12,13,14,15,16 ${DOE_FILE} | tail -n +2) # read from the second line of the file (no header) and only read the columns specified with -f
((run--))
exit 0
.csv file:
"StdOrder";"RunOrder";"CenterPt";"Blocks";"motion";"bitrate_in";"bitrate_out";"twopass";"iframe";"quantI";"quantP";"quantB";"mvbuffer";"cabac"
1;1;1;1;"low";"low";200000;"false";"low";0;0;0;"true";"true"
6;2;1;1;"high";"low";80000000;"false";"low";51;0;51;"true";"false"
8;3;1;1;"high";"high";80000000;"false";"high";0;0;0;"false";"true"
2;4;1;1;"high";"low";200000;"false";"high";0;51;51;"false";"false"
3;5;1;1;"low";"high";200000;"false";"high";51;0;51;"false";"false"
7;6;1;1;"low";"high";80000000;"false";"low";0;51;51;"true";"false"
10;7;1;1;"high";"low";200000;"true";"high";51;0;0;"true";"false"
9;8;1;1;"low";"low";200000;"true";"low";51;51;51;"false";"true"
4;9;1;1;"high";"high";200000;"false";"low";51;51;0;"true";"true"
13;10;1;1;"low";"low";80000000;"true";"high";0;0;51;"true";"true"
5;11;1;1;"low";"low";80000000;"false";"high";51;51;0;"false";"true"
12;12;1;1;"high";"high";200000;"true";"low";0;0;51;"false";"true"
15;13;1;1;"low";"high";80000000;"true";"low";51;0;0;"false";"false"
14;14;1;1;"high";"low";80000000;"true";"low";0;51;0;"false";"false"
16;15;1;1;"high";"high";80000000;"true";"high";51;51;51;"true";"true"
11;16;1;1;"low";"high";200000;"true";"high";0;51;0;"true";"false"
Apparently, the argument (here: "valueX") read from the file is correct
Since you consider the quotes being part of the argument to be correct, in order to match them with a case pattern, you have to escape the pattern quotes:
case ${arg} in
\"valueX\")
Interestingly, this appears to be a Bash (documentation) error, as that says only:
Each pattern undergoes tilde expansion, parameter expansion, command substitution, and arithmetic expansion.
It doesn't tell that the pattern undergoes quote removal.
None of your sample data contains valueX, but assuming you were looking for e.g. "high" in the fifth field, you need to ask the shell to split into at least six fields;
while IFS=";" read -r _first _second _third _fourth arg _rest
do
case ${arg} in
'"high"')
var+="blabla"
;;
'"low"')
var+="blublu"
;;
*)
echo -e "Argument ${arg} not supported"
exit 1
;;
esac
done < filename
Probably a better solution here is to use Awk, though.
awk -F ';' '$5 !~ /"(high|low)"/ { print "Argument " $5 " is not supported" }' filename
Without the rest of your script, it's hard to tell in which direction to continue this; very often, if you are using Awk anyway, it makes sense to implement the rest of the logic in Awk, too.

Handling unused getopts argument

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"

Pass options received in bash script to a called script, command or builtin

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"

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 "**********************************************************************************"

Resources