LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Problem with BASH conditionals - passing condition as variable (https://www.linuxquestions.org/questions/programming-9/problem-with-bash-conditionals-passing-condition-as-variable-755804/)

smaddox 09-17-2009 01:11 AM

Problem with BASH conditionals - passing condition as variable
 
I'm trying to implement an assert function similar to:
http://www.codelibary.com/Bash/Assert.html

However, I'm having trouble with file existence testing when the file name has a space in it.

I have distilled the problem down to the following:

This code works as expected, printing 'yes' if '~/test file' exists, and no if not.
Code:

[ -e "~/test file" ] && echo yes || echo no
However, this code gives an error.
Code:

condition=' -e "~/test file" '
[ $condition ] && echo yes || echo no

The error:
Code:

bash: [: "~/test: binary operator expected
Which tells me that it is splitting ["~/test file"] into ["~/test] and [file"]. Why? Is there a way around this?

Note that if you simply use a file path without a space, both cases work perfectly. Is this a BASH bug possibly? I just can't understand why the first would work, but the second wouldn't.

lutusp 09-17-2009 01:36 AM

Quote:

Originally Posted by smaddox (Post 3686446)
I'm trying to implement an assert function similar to:
http://www.codelibary.com/Bash/Assert.html

However, I'm having trouble with file existence testing when the file name has a space in it.

I have distilled the problem down to the following:

This code works as expected, printing 'yes' if '~/test file' exists, and no if not.
Code:

[ -e "~/test file" ] && echo yes || echo no
However, this code gives an error.
Code:

condition=' -e "~/test file" '
[ $condition ] && echo yes || echo no

The error:
Code:

bash: [: "~/test: binary operator expected
Which tells me that it is splitting ["~/test file"] into ["~/test] and [file"]. Why? Is there a way around this?

Note that if you simply use a file path without a space, both cases work perfectly. Is this a BASH bug possibly? I just can't understand why the first would work, but the second wouldn't.

You need to write your test differently -- "test" is getting confused. Just say:

Code:

filepath="~/test file"

[ -e "$filepath" ] && echo Yes || echo No

Notice that within the "test" block, the filename string is enclosed in quotes. Because your filename has a space, you need to enclose the string variable in quotes. This is a good idea in general, but in this case it's essential.

catkin 09-17-2009 02:35 AM

I understand you want to test conditional expressions contained in a variable. Intriguing idea! :)

It will require a detailed understanding of how the shell works. For eaxmple:
Code:

c@CW8:~/d/bin/try$ /bin/ls ~/'test file'
/home/c/test file
c@CW8:~/d/bin/try$ /bin/ls '~/test file'
/bin/ls: cannot access ~/test file: No such file or directory

The necessary information is in the GNU Bash Reference Manual starting at Shell operations. It may help to know "token: a sequence of characters considered a single unit by the shell. It is either a word or an operator"

Knowing the order of shell operations will be essential when designing a solution.

That's all I have time for right now. I'm posting it in case you are in a hurry. Back later.

QUICK EDIT: the solution will almost certainly require the eval built-in command.

catkin 09-17-2009 03:30 AM

Quote:

Originally Posted by smaddox (Post 3686446)
This code works as expected, printing 'yes' if '~/test file' exists, and no if not.
Code:

[ -e "~/test file" ] && echo yes || echo no

It doesn't work for me
Code:

c@CW8:~/d/bin/try$ [ -e "~/test file" ] && echo yes || echo no
no
c@CW8:~/d/bin/try$ [ -e ~"/test file" ] && echo yes || echo no
no
c@CW8:~/d/bin/try$ [ -e ~/"test file" ] && echo yes || echo no
yes

Can you post your tests and results?

smaddox 09-17-2009 07:00 PM

Thanks for the replies everyone. I think I figured it out.

catkin: I made a bad assumption that the ~/ would be expanded. I actually used the following tests:

Code:

smaddox@smaddox-desktop:~$ touch "test file"
smaddox@smaddox-desktop:~$ [ -e "test file" ] && echo yes || echo no
yes
smaddox@smaddox-desktop:~$ condition=' -e "test file" '
smaddox@smaddox-desktop:~$ [ $condition ] && echo yes || echo no
bash: [: "test: binary operator expected
no

However, as catkin alluded to when he mentioned tokens, the problem is that bash would not reparse the string once it was passed. The solution is to call 'bash -c' to parse it. Since 'bash -c' will exit with the same status as the last function called, this has the desired effect.

If anyone wants to see it in action, here is a testing script:
Code:

#!/bin/bash
# test
# used for testing
E_ASSERT_FAILED=99
################################################################################
# If function doesn't exit 0, exit from script with error message.
# Example usage: assert "[ 4 -lt 5 ]" "Math is wrong"
function assert #( function [error_string] )     
{
    #save inputs
    local function="$1"
    local error_string="$2"
    #get caller info from built-in 'caller' function
    set -- `caller 0`; LINE="$1"; FUNC="$2"; FILE="$3"

    #if condition is false, output info to stderr and exit
    if bash -c "$function"
    then
        return
    else
        echo "Assertion failed:  \"${function}\"" >&2
        echo "File \"${FILE}\", Function \"${FUNC}\", Line ${LINE}" >&2

        #if error_string is not empty, echo it to stderr
        if [ ! -z "$error_string" ]; then
            echo "$error_string" >&2
        fi
        exit $E_ASSERT_FAILED
    fi
}

FILE1="test file"
assert "[ -e \"$FILE1\" ]"

echo "\"$FILE1\" Exists"

FILE2="no file"
assert "[ -e \"$FILE2\" ]"

echo "\"$FILE2\" Exists"

Output when "test file" exists, but "no file" does not exist:
Code:

$ ./test
"test file" Exists
Assertion failed:  "[ -e "no file" ]"
File "./test", Function "main", Line 38

It works great. Feel free to use it. It makes it much easier to assert your assumptions. I used to not test really obviously true assumptions, but it always bites me in the butt when I change something.

catkin 09-18-2009 03:10 AM

Thanks for posting your solution. I was quite looking forward to trying to implement it without creating a sub-process (for performance reasons) but there seems little point now you have a working (and tidily programmed :)) solution there doesn't seem much point.

gnashley 09-18-2009 04:23 AM

6 simple examples that worked for me even with sh or dash:

Code:

#/bin/sh
# note that all these work with 'sh', 'dash' or 'bash'

cat /dev/null > "test file"
chmod 644 "test file"

echo Test1
condition="test file"
[ -e "$condition" ] && echo yes || echo no

echo Test2
TEST='-e' ; condition="test file"
[ $TEST "$condition" ] && echo yes || echo no

echo Test3
for TEST in '-e' '-f' '-x' ; do
        echo TEST=$TEST
        [ $TEST "$condition" ] && echo yes || echo no
done

echo Test4
condition="-x test file"
# "$condition" returns zero if true, 1 if not.
[ ! "$condition" ] && echo yes || echo no

echo Test5
# surprise! I didn't know this would work with dash and sh
# this is indirect substitution
[ "${!condition}" ] && echo yes || echo no

# I'm sure eval can be used, but haven't figured it out...


catkin 09-18-2009 05:23 AM

Quote:

Originally Posted by gnashley (Post 3688261)
# I'm sure eval can be used, but haven't figured it out...

Nice explorations :)

If the filename variable's value begins with ~/ then I think eval is required.

smaddox 09-18-2009 11:21 AM

catkin: If you are worried about performance, you could always add a DEBUG var, and check for it at the beginning of the script.

Code:

if [ DEBUG -ne 0 ]; then
    return
fi

This would limit the assert function to acting only when DEBUG is enabled. However, bash scripts aren't really supposed to be performance kings anyways. If you need performance, it is probably better to use another language. I'm using bash because it has great filesystem integration, which makes writing a backup script much easier.


All times are GMT -5. The time now is 02:21 AM.