14

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?

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

5 Answers 5

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
  • 2
    +1 for using getopts. That's the best way to go.
    – Seth
    May 29, 2016 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.) May 30, 2016 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!

4
  • Are single-quotes essential or can one instead use double-quotes?
    – user364819
    May 29, 2016 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, 2016 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, 2016 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, 2016 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
6
  • Why not [ -z "$1" ]?
    – EKons
    May 30, 2016 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. May 30, 2016 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, 2016 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. May 30, 2016 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, 2016 at 11:25
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

You must log in to answer this question.