I have created a Bash script and I have some rather fugly arguments validation. I know about getopt and getopts but thought they where overkill. This is how I want the usage:

Usage:  flipfile [OPTION] inputfile outputfile

-f  Force. Accept ANY inputfile, not just regular files.

And my current validation code is:

if [ $# -eq 2 ]; then
elif [ $# -eq 3 -a $1 = "-f" ]; then
        echo -e "Error. Usage:  flipfile [OPTION] inputfile outputfile\n\n"
        echo -e "Options:\n-f\tForce. Accept ANY inputfile, not just regular files."
        exit 1

The validation works. But as I'm doing this for fun and for the learning experience I appreciate any tips which may help me write cleaner Bash scripts.

How would you improve this validation code? Use getopt or getopts if you think it's the right decision. I want the optional -f to come as the first argument. If you think [[ conditional-expression ]] is cleaner than what I have, feel free to change that too. I'll accept the answer I think is cleanest.

+2  A: 

This is a nice starting template - easy to extend to other options, and easy to have different possibilities for different numbers of arguments.


exit_usage() {
    echo -e "Usage: flipfile [option] inputfile outputfile"
    exit 1

while getopts "f" opt; do
    case $opt in 
        f ) force=1;;
        * ) exit_usage;;
shift $((OPTIND-1))

case $# in
    2 ) infile=$1
    * ) exit_usage;;

<actual script>
Why don't you replace `echo -e` with `cat <<USAGE ENDS ...`? It's clearer that way.
Pavel Shved
I just copied the echo from his. The point is the structure - while/case loop for getopt, case for number of arguments, and a usage function.
well, when a guy's asking for improvement and posts his working (!) code, he deserves a lot of improvements, I think. :) +1, btw.
Pavel Shved
@Pavel Shved: Are you volunteering?
Dennis Williamson
Personally I tend to define a few variables so that I can have at least two usage statements depending on how much help the user appears to need (perhaps provide a '-h' option), so the printing tends to look like `echo [-n] "$USAGE_<type>"`.
+1  A: 

I think Jefromi's answer is very good. Even though it eliminates all your if statements, I will comment on the part of your question that refers to them.

In Bash, the double bracket allows much cleaner syntax that looks more like some other languages. You can avoid escaping parentheses and most quoting. The comparison operators are simpler. There are some operators available that [ doesn't offer such as wildcard-style pattern matching and regex matching. It uses && and || for "and" and "or" and ! for "not". Use it for strings and files.

Here's a crazy example that illustrates a bunch of the versatility available:

if [[ ( $v1 > 42 && $v2 == $v3 ) || ( $v4 != $(cmnd $(mk-arg-cmnd)) && v5 =~ ^-?[[:digit:]]+$ ]]

If you're comparing numbers, use double parentheses:

if (( $i >= $number + $other ))

See this page for more information.

Dennis Williamson