When I run the command like this :
$: ./script -r f1 f2 :
it detects the "-r" flag and sets the recursive flag to 1.
$: ./script directory/ -r :
getopts doesn't detect the -r flag at all. So inside the case statement it never detects -r flag and so the while loop doens't even run at all. how to fix this ?
RECURSIVE_FLAG=0
while getopts ":rR" opt ; do
echo " opt = $opt"
set -x
case "$opt" in
r) RECURSIVE_FLAG=1 ;;
R) RECURSIVE_FLAG=1 ;;
:)echo "not working" ;;
*)echo "Testing *" ;;
esac
done
It has nothing to do with slash. getopts stops processing options when it gets to the first argument that doesn't begin with -. This is the documented behavior:
When the end of options is encountered, getopts exits with a return value greater than zero. OPTIND is set to the index of the first non-option argument, and name is set to ?.
Your claim that it works when you use
./script f1 f2 -r
is simply wrong. I added echo $RECURSIVE_FLAG to the end of your script, and when I ran it that way it echoed 0.
If you want to allow a more liberal syntax, with options after filenames (like GNU rm) you'll need to do some argument parsing of your own. Put your getopts loop inside another loop. When the getopts loop finishes, you can do:
# Find next option argument
while [[ $OPTIND <= $# && ${!OPTIND} != -* ]]; do
((OPTIND++))
done
# Stop when we've run out of arguments
if [[ $OPTIND > $# ]]; then
break
fi
Related
I am trying to parse some arguments in a bash script with the following code:
for arg in "$#"
do
case $arg in
-i|--initialize)
SHOULD_INITIALIZE=1
shift # Remove --initialize from processing
;;
-r|--root)
ROOT_DIRECTORY="$2"
shift # Remove argument name from processing
shift # Remove argument value from processing
;;
*)
echo "Error, Invalid argument $arg"
exit 1
;;
esac
done
However, when calling the function as expected
bash script.sh -i -r /some/directory
The result is always
Error /some/directory
It seems like the "/some/directory" string is reentering the switch statement. Isn't that the whole purpose of the shifts? I am not that well versed in Bash, so any help would be appreciated.
Thanks!
shift doesn't affect what value will be assigned to arg next, because the for loop effectively "freezes" the set of values to iterate over.
Use a while loop instead:
while [ $# -gt 0 ]; do
case $1 in
-i|--initialize)
SHOULD_INITIALIZE=1
shift
;;
-r|--root)
ROOT_DIRECTORY="$2"
shift # Remove argument name from processing
shift || { echo "Error, no root directory given"; exit 1; }
;;
*)
echo "Error, Invalid argument $1"
exit 1
;;
esac
done
Unlike the for loop, the while loop re-evaluates its condition at the start of each iteration, which includes re-evaluating the value of $# after any previous shifts.
Note that shift will fail if there is no argument to shift, so you can use its exit status to check if $2 actually existed.
I am trying to write a ksh script that takes an optional flag and two mandatory strings as argument. The flag is denoted as -a. Thus the command look like one of the following when correct:
command.sh -a -b abc -c 123
command.sh -b xyz -c 789
I am using the following code in my script:
while getopts "a:b:c:" args
do
case $args in
a) # Flag
flag=1
;;
b) # str1
str1=$OPTARG
;;
c) # str2
str2=$OPTARG
;;
*) # usage
echo "- - - - "
exit 0
;;
esac
done
if [[ -z $str1 || -z $str2 ]]
then
echo "Incomplete arguments supplied\n"
exit 1
fi
...
Doing so when I execute 1 (see above) it throws me the message Incomplete arguments supplied where as 2 (see above) is working fine.
Can anyone point out what is going wrong and recommend a rectification?
Thanks...
A colon (:) after the option letter specifies that the option (aka flag) requires an argument; since you have a colon (:) after the 'a', getopts is expecting an argument to go along with -a; try this instead:
while getopts "ab:c:" args
-z option to check whether variable is set is not used correctly. Refer to the below link for correct usage:
How to check if a variable is set in Bash?
I have a bash file where I am passing arguments like
bash foo.sh update -f /var/ -v true
so according to this answer my script should look like
if [[ "$1" == "update" ]]; then
updater
fi
function updater(){
verbose='false'
fflag=''
error=''
while getopts 'f:v' flag; do
case "${flag}" in
f) fflag="${OPTARG}";;
v) verbose='false';;
*) error="bflag";;
esac
done
echo $fflag
}
I am using the first script as an entry point, because I have other function that do other things, but for some reason the script above does not even show the value of the $fflag I tried moving out the getopts loop out of the function to no avail
There are 3 issues:
Define a function first at the start and then call it in your script
You need to pass command line to your function using "$#"
Before passing command line arguments call shift to remove first argument
You can use this script:
updater() {
verbose='false'
fflag=''
error=''
while getopts 'f:v' flag; do
case "$flag" in
f) fflag="${OPTARG}";;
v) verbose='false';;
*) error="bflag";;
esac
done
declare -p fflag
}
if [[ $1 == "update" ]]; then
shift
updater "$#"
fi
I am writing a script that will allow me to change a char in a string from "#" to something else, if I call an argument in terminal.
eg if I write
./myprogram testText.txt -r a
the -r argument will remove all "#" from testTxt.txt and replace them with "a"
My problem is I do not know how to write "If -r is $x, $x+1 is the char I want for replacement"
This is purely a syntax problem, I'm a bash noob :P. Here is the part of code I'm trying to work with.
for i in $*
do
if [[ $i = "-r" ]]
then
$customHashChoice=$((i+1))
# ^^^^^ Problematic Line ^^^^
fi
done
Try this:
customHashChoice=($(getopt "r:" "$#" 2>/dev/null))
if [ "${customHashChoice[0]}" == "-r" ]; then
customHashChoice="${customHashChoice[1]}"
else
echo "-r option is missing. Aborting..."
exit 1
fi
Syntax: getopt optstring parameters
From manual: getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.
Here, optstring is r:. It means, that the script accepts an option -r & the option takes an argument (implied by :).
The output of getopt "r:" "$#" is as below:
-r <argument to -r option> -- <unmatched parameters>
e.g. for command-line arguments,
./myprogram testText.txt -r a
getopt "r:" "$#" returns
-r a -- testText.txt
This output is stored in array & the second element of array is used, if the first element is equal to -r.
i=1
while [ "$i" -le $# ]
do
if [[ ${!i} = "-r" ]]
then
i=$(($i + 1))
customHashChoice=${!i}
i=$(($i + 1))
continue
fi
# do something useful
i=$(($i + 1))
done
The command line arguments are numbered 1 through $#. The above loops through each of them. If first checks if the current argument is -r and, if so, sets customHashChoice.
In the above, i contains the argument number. So, $i gives the value of i. To access the i'th command line argument, one uses ${!i}.
A more standard approach
The standard way to process command line arguments in shell scripts is getopts. It can handle many options. Here is sample code that that takes an option -r and requires it to have an argument, which is assigned to the shell variable char:
while getopts r: arg ; do case $arg in
r) char="$OPTARG" ;;
:) echo "${0##*/}: Must supply an argument to $OPTARG." ; exit 1 ;;
\?) echo "Invalid option" ; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
echo "I will replace # with $char in file $1"
For getopts to work, the options have to come first. So, your command line would becomes:
./myprogram -r a testText.txt
If this is not acceptable, you can roll your own custom option processor. In the long run, there is some advantage, however, to standardizing on the usual approach.
You could do something like the following:
#!/bin/bash
val=
xval=
fname=$1
while [ "$*" != "" ]; do
case $1 in
"-r") val="${2}"; shift ;;
"-x") xval="${2}"; shift ;;
esac
shift
done
echo ${fname} ${val} ${xval}
Then when you pass the command like so
./myprogram testText.txt -r a
fname will be testText.txt, and the arguments will be parsed (where the -r will pick up a); for any other values you might want to parse, you'll need variable names to assign and test against. The output would be:
testText.txt a
Hope that helps
I am trying to make a shell script which is designed to be run like this:
script.sh -t application
Firstly, in my script I want to check to see if the script has been run with the -t flag. For example if it has been run without the flag like this I want it to error:
script.sh
Secondly, assuming there is a -t flag, I want to grab the value and store it in a variable that I can use in my script for example like this:
FLAG="application"
So far the only progress I've been able to make on any of this is that $# grabs all the command line arguments but I don't know how this relates to flags, or if this is even possible.
You should read this getopts tutorial.
Example with -a switch that requires an argument :
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
Like greybot said(getopt != getopts) :
The external command getopt(1) is never safe to use, unless you know
it is GNU getopt, you call it in a GNU-specific way, and you ensure
that GETOPT_COMPATIBLE is not in the environment. Use getopts (shell
builtin) instead, or simply loop over the positional parameters.
Use $# to grab the number of arguments, if it is unequal to 2 there are not enough arguments provided:
if [ $# -ne 2 ]; then
usage;
fi
Next, check if $1 equals -t, otherwise an unknown flag was used:
if [ "$1" != "-t" ]; then
usage;
fi
Finally store $2 in FLAG:
FLAG=$2
Note: usage() is some function showing the syntax. For example:
function usage {
cat << EOF
Usage: script.sh -t <application>
Performs some activity
EOF
exit 1
}
Here is a generalized simple command argument interface you can paste to the top of all your scripts.
#!/bin/bash
declare -A flags
declare -A booleans
args=()
while [ "$1" ];
do
arg=$1
if [ "${1:0:1}" == "-" ]
then
shift
rev=$(echo "$arg" | rev)
if [ -z "$1" ] || [ "${1:0:1}" == "-" ] || [ "${rev:0:1}" == ":" ]
then
bool=$(echo ${arg:1} | sed s/://g)
booleans[$bool]=true
echo \"$bool\" is boolean
else
value=$1
flags[${arg:1}]=$value
shift
echo \"$arg\" is flag with value \"$value\"
fi
else
args+=("$arg")
shift
echo \"$arg\" is an arg
fi
done
echo -e "\n"
echo booleans: ${booleans[#]}
echo flags: ${flags[#]}
echo args: ${args[#]}
echo -e "\nBoolean types:\n\tPrecedes Flag(pf): ${booleans[pf]}\n\tFinal Arg(f): ${booleans[f]}\n\tColon Terminated(Ct): ${booleans[Ct]}\n\tNot Mentioned(nm): ${boolean[nm]}"
echo -e "\nFlag: myFlag => ${flags["myFlag"]}"
echo -e "\nArgs: one: ${args[0]}, two: ${args[1]}, three: ${args[2]}"
By running the command:
bashScript.sh firstArg -pf -myFlag "my flag value" secondArg -Ct: thirdArg -f
The output will be this:
"firstArg" is an arg
"pf" is boolean
"-myFlag" is flag with value "my flag value"
"secondArg" is an arg
"Ct" is boolean
"thirdArg" is an arg
"f" is boolean
booleans: true true true
flags: my flag value
args: firstArg secondArg thirdArg
Boolean types:
Precedes Flag(pf): true
Final Arg(f): true
Colon Terminated(Ct): true
Not Mentioned(nm):
Flag: myFlag => my flag value
Args: one => firstArg, two => secondArg, three => thirdArg
Basically, the arguments are divided up into flags booleans and generic arguments.
By doing it this way a user can put the flags and booleans anywhere as long as he/she keeps the generic arguments (if there are any) in the specified order.
Allowing me and now you to never deal with bash argument parsing again!
You can view an updated script here
This has been enormously useful over the last year. It can now simulate scope by prefixing the variables with a scope parameter.
Just call the script like
replace() (
source $FUTIL_REL_DIR/commandParser.sh -scope ${FUNCNAME[0]} "$#"
echo ${replaceFlags[f]}
echo ${replaceBooleans[b]}
)
Doesn't look like I implemented argument scope, not sure why I guess I haven't needed it yet.
Try shFlags -- Advanced command-line flag library for Unix shell scripts.
https://github.com/kward/shflags
It is very good and very flexible.
FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take
a name, default value, help-string, and optional 'short' name (one-letter
name). Some flags have other arguments, which are described with the flag.
DEFINE_string: takes any input, and intreprets it as a string.
DEFINE_boolean: typically does not take any argument: say --myflag to set
FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false.
Alternately, you can say
--myflag=true or --myflag=t or --myflag=0 or
--myflag=false or --myflag=f or --myflag=1
Passing an option has the same affect as passing the option once.
DEFINE_float: takes an input and intreprets it as a floating point number. As
shell does not support floats per-se, the input is merely validated as
being a valid floating point value.
DEFINE_integer: takes an input and intreprets it as an integer.
SPECIAL FLAGS: There are a few flags that have special meaning:
--help (or -?) prints a list of all the flags in a human-readable fashion
--flagfile=foo read flags from foo. (not implemented yet)
-- as in getopt(), terminates flag-processing
EXAMPLE USAGE:
-- begin hello.sh --
! /bin/sh
. ./shflags
DEFINE_string name 'world' "somebody's name" n
FLAGS "$#" || exit $?
eval set -- "${FLAGS_ARGV}"
echo "Hello, ${FLAGS_name}."
-- end hello.sh --
$ ./hello.sh -n Kate
Hello, Kate.
Note: I took this text from shflags documentation