115

In my script I am trying to error check if the first and only argument is equal to -v, but it is an optional argument. I use an if statement, but I keep getting the unary operator expected error.

This is the code:

if [ $1 != -v ]; then
   echo "usage: $0 [-v]"
   exit
fi

To be more specific:

This part of the script above is checking an optional argument and then after, if the argument is not entered, it should run the rest of the program.

#!/bin/bash

if [ "$#" -gt "1" ]; then
   echo "usage: $0 [-v]"
   exit
fi

if [ "$1" != -v ]; then
   echo "usage: $0 [-v]"
   exit
fi

if [ "$1" = -v ]; then
   echo "`ps -ef | grep -v '\['`"
else
   echo "`ps -ef | grep '\[' | grep root`"
fi
9
  • ...by the way, I think you want echo "usage: $0 [-v]"; $- shows the active shell option flags, not the name of the current script. Commented Mar 4, 2014 at 18:00
  • I have that part right, I want it to show the name of the current script. Commented Mar 4, 2014 at 18:24
  • 4
    Welcome to stackoverflow, and to the bash tag in particular! Check out the tag wiki for useful tools and resources, like shellcheck which will point out (though not always explain) many issues like this. Commented Mar 4, 2014 at 18:25
  • 1
    @user3380240, $- is not the name of the current script. $0 is. Commented Mar 4, 2014 at 18:28
  • Sorry, that was a typo. Commented Mar 4, 2014 at 18:29

2 Answers 2

220

Quotes!

if [ "$1" != -v ]; then

Otherwise, when $1 is completely empty, your test becomes:

[ != -v ]

instead of

[ "" != -v ]

...and != is not a unary operator (that is, one capable of taking only a single argument).

Sign up to request clarification or add additional context in comments.

3 Comments

Or, if you're not concerned about portability, you can use double brackets, inside of which variable expansions need not be quoted: if [[ $1 != -v ]]; then
@MikeHolt, indeed -- I bring that up in a comment on the question, above.
@DanielDinnyes, if IFS=1, then [ $# -eq 1 ] won't behave so well, whereas [ "$#" -eq 1 ] behaves as-intended even then. It's a pathological case, sure, but better to write software that doesn't have 'em when given the choice.
-2

Or for what seems like rampant overkill, but is actually simplistic ... Pretty much covers all of your cases, and no empty string or unary concerns.

In the case the first arg is '-v', then do your conditional ps -ef, else in all other cases throw the usage.

#!/bin/sh
case $1 in
  '-v') if [ "$1" = -v ]; then
         echo "`ps -ef | grep -v '\['`"
        else
         echo "`ps -ef | grep '\[' | grep root`"
        fi;;
     *) echo "usage: $0 [-v]"
        exit 1;; #It is good practice to throw a code, hence allowing $? check
esac

If one cares not where the '-v' arg is, then simply drop the case inside a loop. The would allow walking all the args and finding '-v' anywhere (provided it exists). This means command line argument order is not important. Be forewarned, as presented, the variable arg_match is set, thus it is merely a flag. It allows for multiple occurrences of the '-v' arg. One could ignore all other occurrences of '-v' easy enough.

#!/bin/sh

usage ()
 {
  echo "usage: $0 [-v]"
  exit 1
 }

unset arg_match

for arg in $*
 do
  case $arg in
    '-v') if [ "$arg" = -v ]; then
           echo "`ps -ef | grep -v '\['`"
          else
           echo "`ps -ef | grep '\[' | grep root`"
          fi
          arg_match=1;; # this is set, but could increment.
       *) ;;
  esac
done

if [ ! $arg_match ]
 then
  usage
fi

But, allow multiple occurrences of an argument is convenient to use in situations such as:

$ adduser -u:sam -s -f -u:bob -trace -verbose

We care not about the order of the arguments, and even allow multiple -u arguments. Yes, it is a simple matter to also allow:

$ adduser -u sam -s -f -u bob -trace -verbose

2 Comments

$* should not be used in this context: It concatenates items into a string which is both string-split and glob-expanded; unlike "$@", which leaves items with their precise original values. And you're missing some quotes, which shellcheck.net will catch (with the warnings linked to a wiki page that describes why those quotes were important).
Consider, as a concrete example, -U'Bob Barker'; for arg in $* will see it as -UBob and then Barker as a separate item; whereas for item in "$@" will see -UBob Barker as a single string.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.