7

So, Being new to GIT and thus extremely rusty in my bash commands and scripting I've been looking around for different syntax and scripting help. Now, I've found a lot of help and have been able to create the scripts and alias that will make my Git experience more pleasant.

However, I came across some nuances that seem to confuse me, specifically related to the "if" command.

if [ -z $1 ] ; #<- Zero length string
if [[ -z $1 ]] ; #<- Also Zero Length String
if [[ "$1" == -* ]] ; #<- Starts with - (hyphen)


if [ -z $1 ] && [ -z $2 ] ; #<- both param 1 & 2 are zero length
if [[ -z $1 ]] && [[ -z $2 ]] ; #<- Also both param 1 & 2 are zero length
if [[ "$1" == -* ]] || [[ "$2" == -* ]] ; #<- Either param 1 or 2 starts with -

if [ "$1" == -* ] || [ "$2" == -* ] ; #<- Syntax Failure, "bash: ]: too many arguments"

Why the discrepancy? How to know when the [[ (double) is required and when a [ (single) will do?

Thanks Jaeden "Sifo Dyas" al'Raec Ruiner

3 Answers3

16

First off, note that neither type of bracket is part of the syntax for if. Instead:

  • [ is another name for the shell built-in test;
  • [[ ... ]] is a separate built-in, with different syntax and semantics.

Here are excerpts from the bash documentation:

[ / test

test: test [expr]
    Evaluate conditional expression.

    Exits with a status of 0 (true) or 1 (false) depending on
    the evaluation of EXPR.  Expressions may be unary or binary.  Unary
    expressions are often used to examine the status of a file.  There
    are string operators and numeric comparison operators as well.

    The behavior of test depends on the number of arguments.  Read the
    bash manual page for the complete specification.

    File operators:

      -a FILE        True if file exists.
      (...)

[[ ... ]]

[[ ... ]]: [[ expression ]]
    Execute conditional command.

    Returns a status of 0 or 1 depending on the evaluation of the conditional
    expression EXPRESSION.  Expressions are composed of the same primaries used
    by the `test' builtin, and may be combined using the following operators:

      ( EXPRESSION )    Returns the value of EXPRESSION
      ! EXPRESSION              True if EXPRESSION is false; else false
      EXPR1 && EXPR2    True if both EXPR1 and EXPR2 are true; else false
      EXPR1 || EXPR2    True if either EXPR1 or EXPR2 is true; else false

    When the `==' and `!=' operators are used, the string to the right of
    the operator is used as a pattern and pattern matching is performed.
    When the `=~' operator is used, the string to the right of the operator
    is matched as a regular expression.

    The && and || operators do not evaluate EXPR2 if EXPR1 is sufficient to
    determine the expression's value.

    Exit Status:
    0 or 1 depending on value of EXPRESSION.

More simply said, [ requires one to take the normal care needed for bash expressions, quote to avoid interpolation, etc. So a proper way of testing for $foo being the empty string, or being unset, would be:

[ -z "$foo" ]

or

[[ -z $foo ]]

It's important to quote in the first case, because setting foo="a b" and then testing [ -z $foo ] would result in test -z receiving two arguments, which is incorrect.

The language for [[ .. ]] is different, and properly knows about variables, much in the way one would expect from a higher-level language than bash. For this reason, it is much less error-prone than classic [ / test.

dhag
  • 15,736
  • 4
  • 55
  • 65
  • it is as equally important omit quotes in the first case as it is to include same in the second. with [ you can variably expand its operators, but have no such freedom with [[. – mikeserv Dec 08 '15 at 19:38
  • @mikeserv: I think I understand the spirit of your comment; that the freedom to let expansion happen is sometimes valuable, but I'm not sure it's very relevant when using [, since all its operators are either single-argument prefix, are two-argument infix, one on each side, meaning it would not be obviously desirable to let splitting happen. But I'm probably missing something, aren't I? – dhag Dec 08 '15 at 19:57
  • expansion != splitting. set 0 1 2 3 4 5 6 7 8 9; while "${1+shift}"; do echo $#; done anyway, its still a good answer. just mentioned it because i think people tend to overlook it. include it or dont - its your answer. – mikeserv Dec 08 '15 at 20:06
  • @dhag So to rephrase for clarity:
    1. "[ x ]" is the same as "test x"?
    2. $foo="a b"; [ -z $foo ] is the same as "test -z a b"?
    3. $foo="a b"; [[ -z $foo ]] is similar to 'test -z "a b"'?

    finally: why then does the pattern matching == require

    • Quotes around the variable
    • double brackets [[]] around the expression?
    – JaedenRuiner Dec 08 '15 at 21:03
  • To your first three points: yes, equivalent. The pattern-matching with [[ $var = pattern ]] never requires $var to be quoted, and only requires pattern to escape whitespace (e.g. as \) if it contains any. – dhag Dec 08 '15 at 21:28
2

Please type man bash and read the documentation.

Things you will find there:

   if list; then list; [ elif list; then list; ] ... [ else list; ] fi
          The  if  list  is  executed.  If its exit status is zero, the
          then list is executed.  Otherwise, each elif list is executed
          in  turn,  and  if its exit status is zero, the corresponding
          then list is executed and the command completes.   Otherwise,
          the  else  list  is executed, if present.  The exit status is
          the exit status of the last command executed, or zero  if  no
          condition tested true.

This means after if there can be any command and bash only cares about the return value of that command. You used two different commands above: [ and [[.

   test expr
   [ expr ]
          Return a status of 0 (true) or 1  (false)  depending  on  the
          evaluation of the conditional expression expr.  Each operator
          and operand must be a  separate  argument.   Expressions  are
          composed  of  the primaries described above under CONDITIONAL
          EXPRESSIONS.  test does not accept any options, nor  does  it
          accept  and ignore an argument of -- as signifying the end of
          options. [...]

This is the classic test available on many shells.

   [[ expression ]]
          Return  a status of 0 or 1 depending on the evaluation of the
          conditional expression expression.  Expressions are  composed
          of  the  primaries  described below under CONDITIONAL EXPRES‐
          SIONS.  Word splitting and pathname expansion  are  not  per‐
          formed  on  the words between the [[ and ]]; tilde expansion,
          parameter and variable expansion, arithmetic expansion,  com‐
          mand  substitution,  process  substitution, and quote removal
          are performed.  Conditional operators  such  as  -f  must  be
          unquoted to be recognized as primaries.

          When  used  with [[, the < and > operators sort lexicographi‐
          cally using the current locale.

          When the == and != operators are  used,  the  string  to  the
          right  of  the  operator  is considered a pattern and matched
          according to the rules described below under  Pattern  Match‐
          ing,  as  if  the  extglob  shell option were enabled.  The =
          operator is equivalent to ==.  If the  shell  option  nocase‐
          match  is  enabled,  the match is performed without regard to
          the case of alphabetic characters.  The return value is 0  if
          the  string  matches (==) or does not match (!=) the pattern,
          and 1 otherwise.  Any part of the pattern may  be  quoted  to
          force the quoted portion to be matched as a string.

This is an extension in bash with slightly different meaning. Especially == is defined differently. The first does literal comparison, while the second dose wildcard matching.

michas
  • 21,510
-1

When in doubt (and in bash), always use double-braces ([[ ]]), as they are a superset of the single-braces which are more prone to user error in their invocation.

[ is an internal command in bash, but in other environments can be a symlink to /bin/test; the corresponding ] is actually ignored and is there for aesthetics. if /bin/test -z "$VAR" and if [ -z "$VAR" ] are identical statements, at the end of the day (notwithstanding that [ may or may not invoke a subprocess in the name of /bin/test, depending on your environment).

[[ ]] is also an internalism for bash, so there's no subshell, is a superset of [ ], and also allows for more friendly writing which just Does The Right Thing where [ ] would explode.

http://mywiki.wooledge.org/BashFAQ/031 goes into a lot more detail.

DopeGhoti
  • 76,081