Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning the result of 'test' to a variable

Tags:

bash

I'm writing a script where I need to use the output of a file test in several places, including inside a shell function. I would like to assign the file existence to a shell variable, like this: file_exists=[ -f $myfile ].

Just to make sure that I've got my bases covered, I start by touching a file, and testing its existance:

file='a'
touch $file
if [ -f $file ]
then
    echo "1 -- '$file' exists"
fi

Output:

1 -- 'a' exists

The file was created successfully -- no surprises, but at least I know that I'm not dealing with any permissions issues or anything.

Next I test to make sure that I can store a boolean expression in a variable:

mytest=/bin/true

if $mytest
then
    echo "2 -- \$mytest is true"
fi

Output:

2 -- $mytest is true

So I've got the basics covered -- conditional expressions should emit the same output as /bin/true or /bin/false... but that's not what I'm seeing:

mytest=[ -f $file ]
if $mytest
then
    echo "3 -- \$mytest is true [expect true]"
else
    echo "3 -- \$mytest is false [expect true]"
fi

This fails with the following error:

-f: command not found

I get the same error message if i use test -f $file rather than [ -f $file ].

If I put a space in front of the [, the error goes away...

mytest= [ -f $file ]
if $mytest
then
    echo "4 -- \$mytest is true [expect true]"
else
    echo "4 -- \$mytest is false [expect true]"
fi

The output appears to be correct:

4 -- $mytest is true [expect true]

... but if I remove the file, I should get the opposite result:

rm $file
mytest= [ -f $file ]
if $mytest
then
    echo "5 -- \$mytest is true [expect false]"
else
    echo "5 -- \$mytest is false [expect false]"
fi

... and I don't:

5 -- $mytest is true [expect false]

To be fair, I expected the space to mess with the truth value:

mytest= /bin/false
if $mytest
then
    echo "6 -- \$mytest is true [expect false]"
else
    echo "6 -- \$mytest is false [expect false]"
fi

Outputs:

6 -- $mytest is true [expect false]

So, how do I store the output from the test builtin in a shell variable?

like image 953
Barton Chittenden Avatar asked Jul 22 '14 19:07

Barton Chittenden


4 Answers

As others have documented here, using the string "true" is a red herring; this is not an appropriate way to store boolean values in shell scripts, as evaluating it means dynamically invoking a command rather than simply inspecting the stored value using shell builtins hardcoded in your script.

Instead, if you really must store an exit status, do so as a numeric value:

[ -f "$file" ]               # run the test
result=$?                    # store the result

if (( result == 0 )); then   # 0 is success
  echo "success"
else                         # nonzero is failure
  echo "failure"
fi

If compatibility with set -e is desired, replace the first two lines of the above with:

result=0
[ -f "$file" ] || result=$?

...as putting the test on the left-hand side of || marks it as "checked", suppressing errexit behavior. (That said, see BashFAQ #105 describing the extent to which set -e harms predictable, portable behavior; I strongly advise against its use).

like image 86
Charles Duffy Avatar answered Nov 17 '22 21:11

Charles Duffy


You need to quote whitespace:

mytest='[ -f $file ]'
if $mytest; then echo yes; fi

However, this is extremely brittle and potentially insecure. See http://mywiki.wooledge.org/BashFAQ/050 for a detailed discussion and some better ways to accomplish something similar.

If you want to encapsulate a complex piece of code, a function is usually the way to go:

mytest () { [ -f "$file" ]; }
if mytest; then echo yes; fi

If you want to run the code once and store its result so you can examine it later, I would rephrase it like this:

if [ -f "$file" ]; then
    mytest=true
else
    mytest=false
fi
if $mytest; then echo yes; fi
like image 41
tripleee Avatar answered Nov 17 '22 19:11

tripleee


A old one but left this here for reference for people that might need it. Not the most beautiful solution but it works in bash:

mytest=$( [ -f $file ] ; echo $? )

More portable, using the test command, and the backticks:

set mytest=`test -f $file ; echo $?`

In a subprocess (<!> system load), the condition is evaluated, and then the result echoed to the output that is captured by the variable $mytest.

like image 26
Jacques Avatar answered Nov 17 '22 20:11

Jacques


mytest=/bin/true is storing the string /bin/true in the $mytest variable.

mytest=[ -f $file ] is setting the $mytest variable to the value [ for the duration of the command -f $file ] (which as your output indicates fails as there is no -f command available).

mytest= [ -f $file ] (like the above) sets the value of the $mytest variable to blank for the duration of the [ -f $file ] command (and returns whatever [ returns).

mytest= /bin/false this is the same as the above case only the command being run is /bin/false.

If you want to store the return code from a command in a variable you can do

/bin/true
ret=$?

if you want to store the output from a command in a variable you can do

out=$(/bin/true)

(though with /bin/true that variable will be empty as it outputs no text.

For your case you want the former $? model.

Also, using set -x (and/or set -v) in your scripts might have helped you diagnose this.

like image 3
Etan Reisner Avatar answered Nov 17 '22 20:11

Etan Reisner