13

I have a bash script which goes something like this:

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

So what I want to be able to do is not only run it with ./script --test or ./script -t, but also with no arguments (just ./script), but seemingly if I do that with the current code the output is just:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

So how do I program it so that running it with no arguments at all will just do the else without throwing the error? What am I doing wrong?

  • 1
    You need double brackets around your checks. [[]] – Terrance May 29 '16 at 19:46
  • 3
    See Bash pitfall #4 – steeldriver May 29 '16 at 19:49
  • @Terrance you don't need them, although you should use them if you target bash. – Ruslan May 30 '16 at 15:16
1

The "proper way" of using long options in shell scripting is via getopt GNU utility. There's also getopts that is a bash built-in, but it only allows short options like -t. A few examples of getopt usage can be found here.

Here's a script that demonstrates how I'd approach your question. Explanation of most steps are added as comments within the script itself.

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

With a few simplifications and removed comments, this can be condensed to:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac
16

Several ways; the two most obvious are:

  • Put $1 in double quotes: if [ "$1" = "--test" ]
  • Check for number of arguments using $#

Better yet, use getopts.

  • 2
    +1 for using getopts. That's the best way to go. – Seth May 29 '16 at 19:59
  • 3
    Another common idiom is [ "x$1" = "x--test" ] to defend again command-line arguments that are valid operators for the [ command. (This is another reason why [[ ]] is preferred: it's part of the shell syntax, not just a builtin command.) – Peter Cordes May 30 '16 at 3:18
7

You need to quote your variables inside the if condition. Replace:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

with:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

Then it will work:

➜ ~ ./test.sh
Not testing.

Always always double quote your variables!

  • Are single-quotes essential or can one instead use double-quotes? – user364819 May 29 '16 at 19:50
  • The precise answer depends on which shell you are using, but from your question I assume you are using a Bourne-shell descendant. The main difference between single and double quotes is that variable expansion happens inside double quotes, but not inside single quotes: $ FOO=bar $ echo "$FOO" bar $ echo '$FOO' $FOO (hmm.... can't format code in comments...) – JayEye May 29 '16 at 19:52
  • @ParanoidPanda You don't have to use single quotes, no, but it is good practice to use them on string literals. That way you won't get surprised later on if your string data has a symbol bash wants to expand. – Seth May 29 '16 at 19:58
  • @ParanoidPanda What @JayEye said: With single quotes there is no variable expansion foo=bar echo '$foo' prints $foo to the output, but foo=bar echo "$foo" prints bar instead. – EKons May 30 '16 at 11:13
4

You're using bash. Bash has a great alternative to [: [[. With [[, you don't have to worry whether about quoting, and you get a lot more operators than [, including proper support for ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Or you can use regular expressions:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Of course, proper quoting should always be done, but there's no reason to stick with [ when using bash.

3

To test if arguments are present (in general, and do the actions accordingly) This should work:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi
  • Why not [ -z "$1" ]? – EKons May 30 '16 at 11:14
  • @ΈρικΚωνσταντόπουλος In this case, sure, normally however in scripts, I call the variable a single time, refer to it in the procedures, which is usually more then once. Decided to keep the protocol in the sample. – Jacob Vlijm May 30 '16 at 11:17
  • In this sample you seem to use $check once, before any shifts, so it would be better to use $1 instead. – EKons May 30 '16 at 11:20
  • @ΈρικΚωνσταντόπουλος Of course not, as mentioned, it is a sample, to be used in scripts. In scripts, it is bad practice to repeatedly do the same over and over again. – Jacob Vlijm May 30 '16 at 11:22
  • I understand what you mean, but it's better practice not to use shebangs#! when providing samples (you should describe the shell outside of the code). – EKons May 30 '16 at 11:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy