1

This has me stumped. New to shell scripting and playing around with the ZSH in macOS Catalina (although I think the same thing happens in bash too.)

I have the following script I'm playing around with which I've saved in a file called f that has no extension. I chmod'd it with -x and I put it in my local bin folder which is in my path variable.

clear

echo "Argument Count: $#"

if [ $# == 0 ]; # Run if no arguments
then 
    echo "You ran this with no arguments!"
    exit 0
fi

if [ $1 == "a" ]; # Run if the first argument is 'a'
then
    echo "You ran A!"
    exit 0
fi

# Run if nothing matches the above
echo "No matches!"

The above works as I expect. If I type this...

f

I get this...

Argument Count: 0
You ran this with no arguments!

If I type this...

f a

I get this...

Argument Count: 1
You ran A!

And finally, if I type this...

f b (or f c, f foo, etc.)

I get this...

Argument Count: 1
No matches.

Again, works exactly like I would expect.

However, if I change the script to this where I simply comment out the first if block...

clear

echo "Argument Count: $#"

# if [ $# == 0 ]; # Run if no arguments
# then 
#   echo "You ran this with no arguments!"
#   exit 0
# fi

if [ $1 == "a" ]; # Run if the first argument is 'a'
then
    echo "You ran A!"
    exit 0
fi

# Run if nothing matches the above
echo "No matches!"

...and I type f by itself, I now get this!

Argument Count: 0
/Users/[redacted]/bin/f: line 11: [: ==: unary operator expected
No matches!

Note: Line 11 is the one with the comment # Run if the first argument is 'a'

Even more odd is if I type the following, which directly runs the code on line 11 that it's complaining about...

f a

I get this, which works correctly!

Argument Count: 1
You ran A!

So what the heck am I missing here??

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

1 Answers1

3

When you run your script with no arguments, this expression:

if [ $1 == "a" ]; 

Becomes:

if [ == "a" ];

Which isn't syntactically valid. The code you've commented out normally protects you from this situation because it causes the script to exit if there are no arguments.

This is why you should always quote variables, e.g:

if [ "$1" == "a" ]; 

This will evaluate correctly even when $1 is undefined.

If you're writing scripts for bash, you can take advantage of the [[ ... ]] expression, which is very similar to [ ... ] but doesn't require the same attention to quotes. If you were to write:

if [[ $1 == "a" ]];

It would work as desired. It's possible zsh has something similar, but I don't know for sure.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • And that's why I love StackOverflow! :) It's so simple when you see it. BTW, just out of curiosity, if I wanted that `if` statement to check case-insensitive, could you toss in an example of that? And one other thing... I keep seeing people use `-eq` instead of `==`. Is there a difference? – Mark A. Donohoe Nov 11 '20 at 04:11
  • 1
    For a case insensitive check I would probably just write `if [ "$1" = a ] || [ "$1" = A ]`; but see [this question](https://stackoverflow.com/questions/1728683/case-insensitive-comparison-of-strings-in-shell-script) for a variety of alternatives. `-eq` is a numeric comparator while`=` (aka `==`) is a string compatator (so `00 -eq 0` is true, but `00 = 0` is false). – larsks Nov 11 '20 at 04:15
  • 2
    Since the `==` operator is actually a *pattern matching* operator within `[[...]]` you can do `if [[ $1 == [aA] ]]` – glenn jackman Nov 11 '20 at 14:41
  • Even better! Thanks! I just have to test if that works under ZSH since that's now the default shell. Actually, about that, if you have the comment `#!/bin/bash` in the top of your file, does that mean 'Even if you ran this from zsh, when this script runs, it will run under bash`? – Mark A. Donohoe Nov 11 '20 at 20:27
  • If your script is executable (`chmod +x myscript.sh`), then yes, the "she-bang" line determines which interpreter is used to run the script. The exception is if you specify an interpreter explicitly (`zsh myscript.sh`, `bash myscript.sh`, `python myscript.sh`, etc). – larsks Nov 11 '20 at 22:32
  • @larsks : Testing for case insensitivity could also be done by conveting the parameter to, say, lower case, i.e. `if [[ ${(L)1} == a ]]`. In particular, if you test for words with a length longer than 1, this is more convenient than fiddling around with the pattern matching suggested by glenn jackman. – user1934428 Nov 12 '20 at 08:02