views:

272

answers:

4

I need to understand bash if expressions. Sure, I've used Google. I know that it goes something like this:

if [ expr operator expr ]
then
doSomeThing
fi

However, I understand that Bash doesn't have a boolean data type. I want to check if the file passed as an argument ($1) exists. The way I would do this straight of my mind:

if [ -e $1 = true ] 
then 
echo "File exists."
fḯ

Or maybe like:

if [ -e $1 ] #Assuming that this is true only if file in $1 exists.

None of these work, and I'm not sure what [ ] means. -e $1 seems like a smart choice, but it is always true? There are different operators for strings and integers. And I can't use parenthesis to group together expressions. This is so confusing.

Anyone got a few hints? The IF in bash does not work like if I've tried in any other language.

A: 

the [ ] is shorthand for bash test command http://ss64.com/bash/test.html

if operation in bash checks if return code of the command use for testing is 0.

You can check the return status of any command with: echo $?

For example try this:

test -e myfile.txt
echo $?
[ -e myfile.txt ] 
echo ?$
Michał Piaskowski
A: 
  1. see man test
  2. this should work:

    function foo() {
      if [ -e "$1" ] ; then
        echo "$1 exists"
      fi
    }
    
  3. There several ways to write a compound expressions: and:

    [ expr1 -a expr2 ]
    

    or:

    [ expr1 -o expr2 ]
    

    You can also use the [[ expr && expr || expr ]] syntax

Chen Levy
+3  A: 

Hi,

[ ... ]

means that the program /usr/bin/test gets executed with ... as the arguments and its return value is checked (0 means true and x != 0 means false (yes, 0 really means true here because 0 is the OK exit code in UNIX)). So

if [ -e $1 ]; then
    echo "ok"
fi

is correct. It's the same as

if test -e $1; then
    echo "ok"
fi

The sole problem is that $1 could contain spaces. If so /usr/bin/test gets confused, since it gets 3 or more arguments. The first one (-e) is a unary operator (wants one argument). Since you gave it 2 or more (one /usr/bin/test argument is the -e itself), it complains like that:

...: binary operator expected

So, simply use

if [ -e "$1" ];then
    echo "ok"
fi

That will even work if $1 contains spaces. Another possibility is to use

if [[ -e $1 ]]; then
    echo "ok"
fi

That will do the same but it gets evaluated by bash itself and no /usr/bin/test program gets forked.

Compounds are as usual but -a means and and -o means or. So

if [ -e /etc/fstab -a -e "$1" ]; then
   echo "ok"
fi

will echo ok if /etc/fstab and the file given as first command line argument exist.

Johannes Weiß
What does it do when add "" around $1 => "$1"?
Algific
It will make exactly ONE argument for /usr/bin/test, no matter how many spaces.
Johannes Weiß
Except that `[` and `test` are also Bash builtins and as such are executed with higher precedence than `/usr/bin/{[,test}`
Dennis Williamson
+1  A: 

As other have mentioned, [ is actually the test command, so its arguments get parsed according to standard argument-parsing rule, and this forces some rather inconvenient and confusing syntax. For example, you can use parentheses, <, and > in test commands, but you'd better escape them or else the shell will take them to mean something unfortunate.

There's a much better solution: the conditional expression, which looks a lot like the old-school test command, but with [[ ]] instead of [ ] around the expression. Because this isn't parsed as a command, it has a much more natural syntax, and also has some more powerful features:

if [[ -e $1 && ! -d $1 ]]; then  # if $1 exists but isn't a directory...
    # note that quotes are not needed, since $1 will not undergo word splitting
[[ $string == done ]] # string comparison
[[ $string == a* ]] # glob-style pattern matching: does $string start with "a"?
[[ $string == "*"* ]] # partial glob: does $string start with "*"?
[[ $string =~ ^[[:upper:]] ]] # regex matching: does $string start with an upper-case letter?
[[ $string < g ]] # string comparison: is $string alphabetically before "g"
[[ $num -lt 5 ]] # numeric comparison: is $num numerically less than 5
Gordon Davisson