Reading command line options from bash script

I have the following code and I am calling it using

./raytrac.bash -u

and getting problems. For some reason opt_usage is still 0.

opt_usage=0
iarg=0
narg=$#
while (($iarg < $narg))
do

  (( iarg = $iarg + 1 ))
  arg=$argv[$iarg]
  usrInputFlag=`echo $arg | awk '/=/ {print 1}; ! /=/ {print 0}'`
  opt=`echo $arg | awk 'BEGIN {FS="="} {print $1}' | tr '[:lower:]' '[:upper:]'`
  par=`echo $arg | awk 'BEGIN {FS="="} {print $2}'`

  case $opt in

  # -- Optional Arguments --------------------------------------------------------------------------

  "--RAYTRAC-PATH")
    arg_raytracPath=$par
    opt_raytracPath=1
  ;;

  "--CMODIF"|"--CMOD-INFILE")
    arg_cmdInFile=$par
    opt_cmdInFile=1
  ;;

  "--SRCSIF"|"--SRCS-INFILE"|"--SOURCES-INFILE")
    arg_srcsInFile=$par
    opt_srcsInFile=1
  ;;

  "--RCVSIF"|"--RCVS-INFILE"|"--RECEIVERS_INFILE")
    arg_rcvsInFile=$par
    opt_rcvsInFile=1
  ;;

  "--PHSS"|"--PHASES")
    arg_phases=$par
    opt_phases=1
  ;;

  "--LV"|"--LEVEL")
    arg_level=$par
    opt_level=1
  ;;

  "--FMT"|"--FORMAT")
    arg_format=$par
    opt_format=1
  ;;

  "--DTAU")
    arg_dtau=$par
    opt_dtau=1
  ;;

  "--BRACDIST")
    arg_bracDist=$par
    opt_bracDist=1
  ;;

  "--TWPTDIST")
    arg_twptDist=$par
    opt_twptDist=1
  ;;

  "--MAXITERTP")
    arg_maxitertp=$par
    opt_maxitertp=1
  ;;

  "--RAYS"|"--RAYS-OUTFILE")
    arg_raysOutFile=$par
    opt_raysOutFile=1
  ;;

  "--TRVT"|"--TRVT-OUTFILE")
    arg_trvtOutFile=$par
    opt_trvtOutFile=1
  ;;

  "--V-raytrac"|"--VRB-RAYTRAC-LEVEL")
    arg_vrbRaytracLevel=$par
    opt_vrbRaytrac=1
  ;;

  "--PF"|"--PFILE")
    arg_pfile=$par
    opt_pfile=1
  ;;

  "--BG-RAYTRAC")
    opt_bgRaytrac=1
  ;;

  "-V"|"--VRB-LEVEL")
    arg_vrbLevel=$par
    opt_vrb_usrInputFlag=$usrInputFlag
    opt_verbose=1
  ;;

  "-Q"|"--QUIET")
    opt_verbose=0
  ;;

  "-U"|"--USAGE")
    opt_usage=1
  ;;

  "-E"|"--EXAMPLES")
    opt_examples=1
  ;;

  "-H"|"--HELP")
    opt_help=1
  ;;

  "--BRWSDIR-PATH")
    arg_browseDirPath=$par
    opt_browseDirPath=1
  ;;

  "--BRWSDIR-CHECK"|"--BRWSDIR-CHECK-COLPOS")
    opt_browseDir_chkColPos=1
  ;;

  "--BRWSDIR-SORT"|"--BRWSDIR-SORT-FIELDS")
    arg_browseDir_sortPtn=$par
    opt_browseDir_sortFlds=1
  ;;

  "--BRWSDIR-GROUP"|"--BRWSDIR-GROUP-TABLE")
    arg_browseDir_groupPtn=$par
    opt_browseDir_groupTbl=1
  ;;

  "--BRWSDIR-ALL"|"--BRWSDIR-ALL-FILES")
    opt_browseDir_allFiles=1
  ;;

  "--BRWSDIR-RAYTRAC"|"--BRWSDIR-RAYTRAC-FILES")
    opt_browseDir_raytrac=1
  ;;

  "--BRWSDIR-DRW"|"--BRWSDIR-DRW-FILES")
    opt_browseDir_drw=1
  ;;

  "--BRWSDIR-SMP"|"--BRWSDIR-SMP-FILES")
    opt_browseDir_smp=1
  ;;

  "--BRWSDIR-LOG"|"--BRWSDIR-LOG-FILES")
    opt_browseDir_logFiles=1
  ;;

  "--BRWSDIR-MSF"|"--BRWSDIR-MSF-FILES")
    opt_browseDir_msfFiles=1
  ;;

  "--BRWSDIR-V"|"--BRWSDIR-VRB-LEVEL")
    arg_browseDir_vrbLevel=$par
    opt_browseDir_verbose=1
    opt_browseDir_vrbLevel=$usrInputFlag
  ;;

  "--BRWSDIR-Q"|"--BRWSDIR-QUIET")
    opt_browseDir_quiet=1
    opt_browseDir_verbose=0
  ;;

  *)
    arg_browseDir_LstFiles="$arg_browseDir_FileLst $arg"
    opt_browseDir_LstFiles=1
  ;;

  esac

done   # which

---------- Post updated at 10:27 AM ---------- Previous update was at 09:33 AM ----------

Problem seems to be here, when I am trying to get the argument using argv.

arg=$argv[$iarg]

FYI - Consider getopts for processing arguments.

You missed below to give at start of your script to initialized array ... :slight_smile:

argv=("$@")

Also your array syntax is wrong it should be as below instead and note you have incremented the "iarg"at wrong position that you code will ignore first argument ..

arg=$argv[$iarg]       ---->>>>     arg=${argv[$iarg]} 

Try This code you will get your Mistakes ...

argv=("$@")
opt_usage=0
iarg=0
narg=$#
while (($iarg < $narg))
do
  arg=${argv[$iarg]}
  echo $arg
  ((iarg++))
done

--Shirish Shukla

I have found BASH_ARGV. What do you thing of using it rather than
argv=("$@")

for a in ${BASH_ARGV
[*]} ; do
  echo "BASH_ARGV = $a"
done

That's a bit redundant if all you want is a loop:

for X in "$@"
do
        echo "argument $X"
done

I would not reccomend using BASH_ARGV. That's only going to exist under bash, for starters, and just doesn't seem necessary.

There are multiple way ... it's depends on your use and logic that your mind can build at that time .. have provided base on your posted script mis....

--Shirish Shukla

What is the difference between $* and $@

"$*" splits on spaces.

"$@" preserves the arguments as they were given, spaces or no.

I am using them in this way. $@ for creating an array, whereas I use $* to create a string.

args=("$@")                                      # Set array containing all arguments.
argsUC=`echo "$*" | tr '[:lower:]' '[:upper:]'`  # Set list containing all arguments.

What do you mean exactly by

"$@" preserves the arguments as they were given, spaces or no.

---------- Post updated at 05:18 PM ---------- Previous update was at 05:12 PM ----------

I also want to get the directory name from the script that I run. I am using this line.
Any suggestions?

bashPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Basically, I do as follows

cd /home/chrisd/tatsh/branches/test
/home/chrisd/tatsh/trunk/hstmy/bin/bash/raytrac.bash --brwsdir-log

Inside the script raytrac.bash, I want to capture the directory path from where the script resides (i.e. I want to get /home/chrisd/tatsh/trunk/hstmy/bin/bash)

"$*" becomes a string. "$@" becomes a list.

as in, arguments "A" "B" "C" would evaluate to "A B C" for $*, and "A" "B" "C" for "$@"

Using "$@" to create an array is redundant, as I've shown you. The arguments are all there already and can be used directly. In BASH you can even access them numerically:

N=1
echo "${!N}" # First argument

I don't know why you'd need to cd into a directory to get its name. If you can cd into it, you obviously know its name already.

I would need to run some other scripts inside raytrac.bash.

For example, from raytrac.bash I want to run the following:

/home/chrisd/tatsh/trunk/hstmy/bin/bash/browseDir.bash
/home/chrisd/tatsh/trunk/hstmy/bin/bash/printTable.bash
/home/chrisd/tatsh/trunk/hstmy/bin/prog/raytrac

I do not want the path hardwired, as I would need to change it whenever I am on another system.

By running my script /home/chrisd/tatsh/trunk/hstmy/bin/bash/raytrac.bash, I want to get the directory path before raytrac.bash. This will get me the bash directory path.

---------- Post updated at 05:58 PM ---------- Previous update was at 05:52 PM ----------

Ok, so how would you code the section below in your opinion?

iarg=0
narg=$#                                          # Number of arguments passed.
args=("$@")                                      # Set array containing all arguments.
argsUC=`echo "$*" | tr '[:lower:]' '[:upper:]'`  # Set list containing all arguments.
opt_browseDir_flag=`echo $argsUC | awk '/BRWS/ {print 1}; ! /BRWS/ {print 0}'`

while (($iarg < $narg))
do

  arg=${args[$iarg]}    # Bash arrays are zero-based: first element is indexed with number 0.
  usrInputFlag=`echo $arg | awk '/=/ {print 1}; ! /=/ {print 0}'`
  opt=`echo $arg | awk 'BEGIN {FS="="} {print $1}' | tr '[:lower:]' '[:upper:]'`
  par=`echo $arg | awk 'BEGIN {FS="="} {print $2}'`

  case $opt in

  "--CMODIF"|"--CMOD-INFILE")
    arg_cmdInFile=$par
    opt_cmdInFile=1
  ;;

  "--DTAU")
    arg_dtau=$par
    opt_dtau=1
  ;;

  "--BRACDIST")
    arg_bracDist=$par
    opt_bracDist=1
  ;;

  "--RAYS"|"--RAYS-OUTFILE")
    arg_raysOutFile=$par
    opt_raysOutFile=1
  ;;

  *)
    arg_browseDir_fileLst="$arg_browseDir_fileLst $arg"
    opt_browseDir_fileLst=1
  ;;

  esac

  ((iarg++))

done   # which

---------- Post updated at 06:30 PM ---------- Previous update was at 05:58 PM ----------

I came up with the piece below

iarg=1
narg=$#                                          # Number of arguments passed.
while (($iarg <= $narg))
do
  echo "Argument ${!iarg}"
  ((iarg++))
done

Is this what you were suggesting? I am getting bit confused about this as I had to do start from iarg=1 and then continue till iarg is narg. I thought it goes to one less.

---------- Post updated at 06:57 PM ---------- Previous update was at 06:30 PM ----------

I can get the list of all arguments using either "$@" or "$". However, the best one to use is "$@" I think, as "$" might fail in some instances.

argsUC=`echo "$@" | tr '[:lower:]' '[:upper:]'`  # Set list containing all arguments.
argsUC=`echo "$*" | tr '[:lower:]' '[:upper:]'`  # Set list containing all arguments.

[quote=kristinu;302609369]
Ok, so how would you code the section below in your opinion?

iarg=0
narg=$#                                          # Number of arguments passed.
args=("$@")                                      # Set array containing all arguments.
argsUC=`echo "$*" | tr '[:lower:]' '[:upper:]'`  # Set list containing all arguments.
opt_browseDir_flag=`echo $argsUC | awk '/BRWS/ {print 1}; ! /BRWS/ {print 0}'`

No matter which $* of $@ you use, feeding it into echo is going to flatten it. tr can't tell the difference between spaces caused by spaces, and spaces caused by an argument being split, because they're both just spaces... You don't put arguments through a pipe, only bytes. If you want to feed it through pipes, you'll have to preserve the splitting yourself, probably by changing the separator into something strange, so that echo "$*" output a|b|c|d instead of a b c d . This will let us split it ourselves later without losing anything.

I'd redo the arguments splitting on | and =, so that I don't have to worry about the difference between --argument=asdf and --argument asdf . The IFS controls all splitting, and set -- sets arguments to any values you want, so we can shuffle the arguments creatively in one go.

I wouldn't transform everything into uppercase. That mangles filenames too, and UNIX filenames are case-sensitive -- they'd be ruined.

I also wouldn't use awk 9 times to process one argument when case can do everything the first try...

OLDIFS="$IFS"
# IFS is a special variable that controls splitting.  We split on | and = now, not whitespace.  "$*" will now become a string like a|b|c
IFS="|="

# Split on | and =.  $* will insert the | itself when IFS="|", so we
# get back what we put in, except everything's now split on = too, i.e.
# "a b" "c" --option=file
# becomes
# "a b" "c" --option file
set -- "$*"

# Return IFS to its normal whitespace.
IFS="$OLDIFS"

# Loop until all arguments are used up.
# Shift tosses the value of $1 and sets $1=$2, $2=$3, ...
# so doing this repeatedly will decrease the value of $# until
# we run out of arguments.
while [ "$#" -gt 0 ]
do
        case "$1" in
        "--"[cC][mM][oO][dD][iI][fF]|"--"[cC][mM][oO][dD][iI][nN][fF][iI][lL][eE])
                shift  # Skip ahead one to the next argument.
                arg_cmdInFile="${1}"
                ;;
        esac

        shift # Skip ahead to the next argument
done

Pure builtins with no backticks necessary. And this doesn't even need BASH -- this should work equally well in BASH, KSH, ZSH, or even a plain Bourne shell.

1 Like

I was doing like the code below, I was only taking care to putting to upcase on the left hand side of the "=" sign. As you say, ideally it would be good to have the script accept --argument=asdf and --argument asdf.

Having to code as your scheme seems quite long rather than just taking the upper case, but then as you say, it will handle both --argument=asdf and --argument asdf as expected.
Considering all this, you think your new code is fine?

I am unsure what set -- "$*" does. Would it be possible to explain better your comment

set --  sets arguments to any values you want, so we can shuffle the arguments creatively in one go.

"--"[cC][mM][oO][dD][iI][fF]|"--"[cC][mM][oO][dD][iI][nN][fF][iI][lL][eE])

What should for checking the other options "--CMODIF"|"--CMOD-INFILE", "--SRCSIF"|"--SRCS-INFILE"|"--SOURCES-INFILE" etc...

iarg=0
narg=$#                                          # Number of arguments passed.
args=("$@")                                      # Set array containing all arguments.

while (($iarg < $narg))
do

  arg=${args[$iarg]}
  usrInputFlag=`echo $arg | awk '/=/ {print 1}; ! /=/ {print 0}'`
  opt=`echo $arg | awk 'BEGIN {FS="="} {print $1}' | tr '[:lower:]' '[:upper:]'`
  par=`echo $arg | awk 'BEGIN {FS="="} {print $2}'`

  case $opt in

  "--RAYTRAC-PATH")
    arg_raytracPath=$par
    opt_raytracPath=1
  ;;

  "--CMODIF"|"--CMOD-INFILE")
    arg_cmdInFile=$par
    opt_cmdInFile=1
  ;;

  "--SRCSIF"|"--SRCS-INFILE"|"--SOURCES-INFILE")
    arg_srcsInFile=$par
    opt_srcsInFile=1
  ;;

  *)
    arg_browseDir_fileLst="$arg_browseDir_fileLst $arg"
    opt_browseDir_fileLst=1
  ;;

  esac

  ((iarg++))

done   # which

---------- Post updated at 03:03 PM ---------- Previous update was at 01:06 PM ----------

I have tried the following code but I am getting

$* = --cmodif=myfile.cmod
arg_cmdInFile = 

Seems that arg_cmdInFile has not been set.

IFS="|="
set -- "$*"
IFS="$OLDIFS"

echo "\$* = $*"

while [ "$#" -gt 0 ]
do
  case "$1" in

  "--"[cC][mM][oO][dD][iI][fF]|"--"[cC][mM][oO][dD][iI][nN][fF][iI][lL][eE])
    shift  # Skip ahead one to the next argument.
    arg_cmodInFile="${1}"
  ;;

  esac

  shift # Skip ahead to the next argument
done

echo "arg_cmdInFile = $arg_cmodInFile"

---------- Post updated at 03:16 PM ---------- Previous update was at 03:03 PM ----------

I have put some comments. Looks like $1 contains all arguments.

OLDIFS="$IFS"      
IFS="|="
set -- "$*"
IFS="$OLDIFS"

echo "\$* = $*"

while [ "$#" -gt 0 ]
do

  echo "Current Argument is ${1}"
  case "$1" in

  "--"[cC][mM][oO][dD][iI][fF]|"--"[cC][mM][oO][dD][iI][nN][fF][iI][lL][eE])
    echo "Argument is ${1}"
    shift  # Skip ahead one to the next argument.
    arg_cmodInFile="${1}"
  ;;

  esac

  shift # Skip ahead to the next argument

done

echo "arg_cmdInFile = $arg_cmodInFile"

running ./raytrac.bash --cmodif=myfile.cmod --srcsif sources.srcs
the output is

$* = --cmodif=myfile.cmod|--srcsif|sources.srcs
Current Argument is --cmodif=myfile.cmod|--srcsif|sources.srcs
arg_cmdInFile = 

There was a minor error in my code. You want

set -- $* not set -- "$*" because the quotes will prevent it from splitting.

When I replace that, your code does this:

$ ./arg.sh --cmodif=myfile.cmod --srcsif sources.srcs

$* = --cmodif myfile.cmod --srcsif sources.srcs
Current Argument is --cmodif
Argument is --cmodif
Current Argument is --srcsif
Current Argument is sources.srcs
arg_cmdInFile = myfile.cmod

$
1 Like

Things are working now.

---------- Post updated at 04:23 PM ---------- Previous update was at 04:20 PM ----------

I have created a new thread concerning use of bash case statement.

Having problems when I code the following:

  "--"[sS][rR][cC][sS][iI][fF]|"--"[sS][rR][cC][sS]"-"[iI][nN][fF][iI][lL][eE])
  "--"[sS][oO][uU][rR][cC][eE][sS]"-"[iI][nN][fF][iI][lL][eE])
    shift     # Skip ahead one to the next argument.
    arg_srcsInFile="${1}"
    opt_srcsInFile=1
  ;;
 

I want to check for

  "--"[sS][rR][cC][sS][iI][fF]
  "--"[sS][rR][cC][sS]"-"[iI][nN][fF][iI][lL][eE])
  "--"[sS][oO][uU][rR][cC][eE][sS]"-"[iI][nN][fF][iI][lL][eE])
  

but are too long to fit in one line.

---------- Post updated at 04:42 PM ---------- Previous update was at 04:23 PM ----------

I got an infringement when I posted this on another tread. Thought this post is getting too long and too many requirements. Apologies.

You can't put more than one case that way. Put them in one line separated by |, or just make them separate cases.

case "$STUFF" in
case1|case2)
        ;;

case3)
        ;;
esac
1 Like

Is there a neater way to allow upper or lower case checks rather than having to do the following:

"--"[sS][rR][cC][sS][iI][fF]  \
|"--"[sS][rR][cC][sS]"-"[iI][nN][fF][iI][lL][eE]  \
|"--"[sS][oO][uU][rR][cC][eE][sS]"-"[iI][nN][fF][iI][lL][eE])

That's actually a pretty good idea. Seems to work.

case "$1" in
        --qwerty|\
        --uiop)
                echo "asdf"
                ;;
esac

Which good idea? Using [bB][rR] ... ?

The backslash to cross lines.