How to parse invalid options? - bash

I am writing the following script to parse some options:
#!/bin/bash
while [[ $# > 1 ]]
do
key="$1"
case $key in
-i|--inbound)
inbound="true"
shift # past argument
;;
-o|--outbound)
outbound="true"
shift # past argument
;;
*)
echo "hola"
exit 1
;;
esac
shift # past argument or value
done
echo $inbound
echo $outbound
The problem is that i would like to terminate the program, if I receive an invalid option, I tried the following:
*)
exit 1
echo "invalid option"
;;
but when i run the program like this: bash script.sh -invalid, anything happens, i would like to appreciate any suggestion to fix this, my desired output would like to:
invalid option

while [[ $# > 1 ]]
should be
while (($# >= 1))
You want a numeric comparison, not a string comparison. Although in this case, it doesn't make any difference, it would make a big difference if you had compared with 2, since the string 10 is less than the string 2.
Anyway, if you invoke your script with one argument, $# will be one. So the greater-than comparison is not correct.
Finally, if you really had:
exit 1
echo "hola"
the echo would never be executed, because the exit would have happened first.

Related

Bash switch case statement keeps failing to parse arguments, but pattern matching looks fine?

I am studying a simple bash script, or what I thought would be simple. It looks as though it is simply trying to store the first argument as a variable before proceeding.
However, it fails every time.
Script:
while [ $# -gt 0 ]; do
case "$1" in
variableName=*)
variableName="${1#*=}"
;;
*)
echo "***************************"
echo "* Invalid argument:"
echo "* ($1)"
echo "***************************"
exit 1
esac
shift
done
Now, if I try to execute it, this happens:
$ sh script.sh "test"
***************************
* Invalid argument:
* (test)
***************************
The pattern match is *, so that looks like it should match anything. At that point, it should simply be storing the variable, and that's it.
In the non-dissected script, there are other variables being stored which proceed this one, hence the loop.
If you run your code with the argument variableName=xx it will not go into the Invalid argument condition.
Add a echo foo in the line above the ;; and you will see this.
In the end of the script, the variable variableName will be set to the value you passed in, i.e. xx in this case.
The following snipped is your code plus some echo statements which hopefully clearly show, what's going on.
while [ $# -gt 0 ]; do
case "$1" in
variableName=*)
variableName="${1#*=}"
echo "setting variableName to $variableName"
;;
*)
echo "***************************"
echo "* Invalid argument:"
echo "* ($1)"
echo "***************************"
exit 1
esac
shift
done
echo "variableName is set to $variableName"
Your pattern is variablename=*, not *. Your argument doesn't start with variablename=, so that case fails.

How to pattern match a script argument in Linux bash

Basically, I have to create a bash script that analyzez the arguments of the script and if it's only one, proceeds to pattern match it. If it starts with a number, it says so, if it starts with a letter, it says so and if neither, it says the argument is a string. If you didn't provide an argument, it says you need 1, or if you provided 2 or more. Everything works, apart from the pattern matching itself. I always get the default case. What's wrong with it?
I gotta use case :( .
case $# in
1) case $1 in
/^?[0-9][a-zA-z]*$/) echo "Argument starts with number";;
/^?[a-zA-z][a-zA-z]*$/) echo "Argument starts with letter";;
*) echo "Argument is a string";;
esac;;
0) echo -n "You can't use 0 arguments"
exit 1;;
*) echo -n "You can't use 2 or more arguments"
exit 1;;
esac
exit 0
EDIT: typing this in terminal gives me the default case: l#ubuntu:~/Documents$ ./e4_G_L.sh contor - maybe I wrote the argument wrong and the pattern matching works just fine?
As pointed out in the comments, case only supports globs (also known as wildcards). To check regular expression as in your script you could use [[ … =~ … ]] but that would be overkill. Since you only want to check the first letter, globs are sufficient.
Also, I would rephrase the warning message. Tell the user what to do, not just “You messed up. Good luck guessing on your next try.”.
if [ $# != 1 ]; then
echo "Expected 1 argument but found $#."
exit 1
fi
case "$1" in
[0-9]*) echo "Argument starts with number" ;;
[a-zA-Z]*) echo "Argument starts with letter" ;;
*) echo "Argument is a string";;
esac
Using bash extended patterns:
shopt -s extglob
case $# in
0) echo "You can't use 0 arguments"
exit 1
;;
1) case $1 in
[0-9]+([[:alpha:]]) ) echo "A number followed by letters" ;;
+([[:alpha:]]) ) echo "Only letters" ;;
*) echo "Something else" ;;
esac
;;
*) echo "You can't use 2 or more arguments"
exit 1
;;
esac
Thanks to everybody who bothered to help this lost soul :). It's a task for my Uni so no need for complex hints. The teacher knows what this is about so keeping it short. Ended up using the first answer.

Storing bash script argument with multiple values

I would like to be able to parse an input to a bash shell script that looks like the following.
myscript.sh --casename obstacle1 --output en --variables v P pResidualTT
The best I have so far fails because the last argument has multiple values. The first arguments should only ever have 1 value, but the third could have anything greater than 1. Is there a way to specify that everything after the third argument up to the next set of "--" should be grabbed? I'm going to assume that a user is not constrained to give the arguments in the order that I have shown.
casename=notset
variables=notset
output_format=notset
while [[ $# -gt 1 ]]
do
key="$1"
case $key in
--casename)
casename=$2
shift
;;
--output)
output_format=$2
shift
;;
--variables)
variables="$2"
shift
;;
*)
echo configure option \'$1\' not understood!
echo use ./configure --help to see correct usage!
exit -1
break
;;
esac
shift
done
echo $casename
echo $output_format
echo $variables
One conventional practice (if you're going to do this) is to shift multiple arguments off. That is:
variables=( )
case $key in
--variables)
while (( "$#" >= 2 )) && ! [[ $2 = --* ]]; do
variables+=( "$2" )
shift
done
;;
esac
That said, it's more common to build your calling convention so a caller would pass one -V or --variable argument per following variable -- that is, something like:
myscript --casename obstacle1 --output en -V=v -V=p -V=pResidualTT
...in which case you only need:
case $key in
-V=*|--variable=*) variables+=( "${1#*=}" );;
-V|--variable) variables+=( "$2" ); shift;;
esac

Having getopts to show help if no options provided

I parsed some similar questions posted here but they aren't suitable for me.
I've got this wonderful bash script which does some cool functions, here is the relevant section of the code:
while getopts ":hhelpf:d:c:" ARGS;
do
case $ARGS in
h|help )
help_message >&2
exit 1
;;
f )
F_FLAG=1
LISTEXPORT=$OPTARG
;;
d )
D_FLAG=1
OUTPUT=$OPTARG
;;
c )
CLUSTER=$OPTARG
;;
\? )
echo ""
echo "Unimplemented option: -$OPTARG" >&2
echo ""
exit 1
;;
: )
echo ""
echo "Option -$OPTARG needs an argument." >&2
echo ""
exit 1
;;
* )
help_message >&2
exit 1
;;
esac
done
Now, all my options works well, if triggered. What I want is getopts to spit out the help_message function when no option is triggered, say the script is launched just ./scriptname.sh without arguments.
I saw some ways posted here, implementing IF cycle and functions but, since I'm just starting with bash and I already have some IF cycles on this script, I would like to know if there is an easier (and pretty) way to to this.
If you just want to detect the script being called with no options then just check the value of $# in your script and exit with a message when it is zero.
If you want to catch the case where no option arguments are passed (but non-option arguments) are still passed then you should be able to check the value of OPTIND after the getopts loop and exit when it is 1 (indicating that the first argument is a non-option argument).
Many thanks to Etan Reisner, I ended up using your suggestion:
if [ $# -eq 0 ];
then
help_message
exit 0
else
...... remainder of script
This works exactly the way I supposed.
Thanks.
Dropping a sample code from #Etan Reisner's statement:
you want to catch the case where no option arguments are passed (but non-option arguments) are still passed then you should be able to check the value of OPTIND after the getopts loop and exit when it is 1 (indicating that the first argument is a non-option argument).
# Check if any arguments were passed
if [ $OPTIND -eq 1 ]; then
help_message
exit 1
fi
# ........ the rest of the script
Just in case someone wanted a sample visual 😅

File globbing and matching numbers only

In a bash script I need to verify that the user inputs actual numbers so I have thought the easiest way to make myself sure about that is implementing a case:
case $1 in
[0-9]*)
echo "It's ok"
;;
*)
echo "Ain't good!"
exit 1
;;
esac
But I'm having hard time with file globbing because I can't find a way to demand the $1 value has to be numeric only. Or another way could be excluding all the alternatives:
case $1 in
-*)
echo "Can't be negative"
exit 1
;;
+*)
echo "Must be unsigned"
exit 1
;;
*[a-zA-z]*)
echo "Can't contain letters"
exit 1
;;
esac
The thing is in this case I should be able to block "special" chars like ! ? ^ = ( ) and so forth... I don't know how to acheive it. Please anyone give me a hint?
Actually it would be better to use
*[!0-9]*
instead of
*[^0-9]*
as the first one is POSIX and the second one is a bashism[1].
[1] http://rgeissert.blogspot.com/2013/02/a-bashism-week-negative-matches.html
If you find a non-numeric character anywhere in the string, the input is bad, otherwise it's good:
case "$1" in
*[^0-9]*) echo "first parameter must contain numbers only"; exit 1;;
esac

Resources