LinuxQuestions.org
Did you know LQ has a Linux Hardware Compatibility List?
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices



Reply
 
Search this Thread
Old 09-17-2009, 02:11 AM   #1
smaddox
LQ Newbie
 
Registered: Sep 2009
Posts: 11

Rep: Reputation: 0
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.
 
Old 09-17-2009, 02:36 AM   #2
lutusp
Member
 
Registered: Sep 2009
Distribution: Fedora
Posts: 835

Rep: Reputation: 101Reputation: 101
Quote:
Originally Posted by smaddox View Post
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.
 
Old 09-17-2009, 03:35 AM   #3
catkin
LQ 5k Club
 
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Servers: Debian Squeeze and Wheezy. Desktop: Slackware64 14.0. Netbook: Slackware 13.37
Posts: 8,563
Blog Entries: 29

Rep: Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179
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.

Last edited by catkin; 09-17-2009 at 03:36 AM.
 
Old 09-17-2009, 04:30 AM   #4
catkin
LQ 5k Club
 
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Servers: Debian Squeeze and Wheezy. Desktop: Slackware64 14.0. Netbook: Slackware 13.37
Posts: 8,563
Blog Entries: 29

Rep: Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179
Quote:
Originally Posted by smaddox View Post
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?
 
Old 09-17-2009, 08:00 PM   #5
smaddox
LQ Newbie
 
Registered: Sep 2009
Posts: 11

Original Poster
Rep: Reputation: 0
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.
 
Old 09-18-2009, 04:10 AM   #6
catkin
LQ 5k Club
 
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Servers: Debian Squeeze and Wheezy. Desktop: Slackware64 14.0. Netbook: Slackware 13.37
Posts: 8,563
Blog Entries: 29

Rep: Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179
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.
 
Old 09-18-2009, 05:23 AM   #7
gnashley
Amigo developer
 
Registered: Dec 2003
Location: Germany
Distribution: Slackware
Posts: 4,775

Rep: Reputation: 481Reputation: 481Reputation: 481Reputation: 481Reputation: 481
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...
 
Old 09-18-2009, 06:23 AM   #8
catkin
LQ 5k Club
 
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Servers: Debian Squeeze and Wheezy. Desktop: Slackware64 14.0. Netbook: Slackware 13.37
Posts: 8,563
Blog Entries: 29

Rep: Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179Reputation: 1179
Quote:
Originally Posted by gnashley View Post
# 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.
 
Old 09-18-2009, 12:21 PM   #9
smaddox
LQ Newbie
 
Registered: Sep 2009
Posts: 11

Original Poster
Rep: Reputation: 0
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.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
passing variable from bash to perl in a bash script quadmore Programming 6 02-21-2011 05:11 AM
passing array and variable to function in bash script ajaypitroda Programming 2 07-08-2009 12:10 AM
Passing a variable to bc in bash - HELP! rodhull Programming 4 01-14-2009 07:59 PM
bash: passing asterisk to variable command Dr_Death_UAE Programming 14 11-26-2008 11:04 AM
Bash Script Passing variable to Function nutthick Programming 2 02-02-2005 06:15 AM


All times are GMT -5. The time now is 09:31 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration