0

First off, I am aware that when comparing strings in Bash with single brackets using > or <, it has to be escaped. However, I am wondering what it does if it is not escaped. What does it mean? It does not throw an error. Here is the code:

((a = 1))
if [ a > 2 ]; then echo "Foo"; else echo "bar"; fi

What is a > 2 doing? What would it do to redirect 1 into 2 and why, if useless, does it not give an error.

JustAFellowCoder
  • 300
  • 2
  • 11
  • 1
    This is creating a _file_ named `2`. It's a bug. – Charles Duffy Mar 22 '21 at 17:22
  • 3
    "Why, if useless, does it not give an error?" -- putting the responsibility on the shell to detect every possible useless thing and make them all errors in not reasonable. Sometimes someone _wants_ to create an empty file named `2`. Why is it the shell's job to second-guess the developer? – Charles Duffy Mar 22 '21 at 17:25
  • 1
    ...moreover, the behavior that `> 2` actually has is mandated by the POSIX sh specification, so to make it cause an error instead of performing the useless action it's specified to perform would make the shell no longer be standards-compliant. – Charles Duffy Mar 22 '21 at 17:27

4 Answers4

2

I suspect, without further context, that what was intended was an evaluation.

((a = 1))
if [ a > 2 ]; then echo "Foo"; else echo "bar"; fi

is a bug, as mentioned. [ is a synonym ("syntactic sugar", as Larry Wall likes to call it) for test, so what is actually being done here is:

let a=1
if test a 1>2 # evaluate a, redirect stdout (there is none) to file "2"
then echo "Foo"
else echo "bar"
fi

Look over the surrounding logic and see if perhaps it should not have been something like this:

((a = 1))
if (( a > 2 )); then echo "Foo"; else echo "bar"; fi

That would be a proper arithmetic evaluation.

$: (( 1 > 2 )) && echo ok || echo no
no
$: (( 20 > 100 )) && echo ok || echo no
no
$: (( 100 > 20 )) && echo ok || echo no
ok

Beware the more subtle bug of using double-square brackets:

((a = 1))
if [[ a > 2 ]]; then echo "Foo"; else echo "bar"; fi

This will, in this case, generally give the expected result, but it is evaluating in string context, probably according to the LOCALE setting. It will often fail when the number of digits differ, as in [[ 10 > 2 ]].

$: [[ 1 > 2 ]] && echo ok || echo no
no
$: [[ 10 > 2 ]] && echo ok || echo no
no
$: [[ 2 > 10 ]] && echo ok || echo no
ok
Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • [[ 10 > 2 ]] would evaluate to false because it is testing for alphabetical order being strings and 1 comes before 2 in unicode? – JustAFellowCoder Mar 22 '21 at 21:04
  • 1
    @JustAFellowCoder, correct. (Maybe not unicode -- which character set is used for the comparison depends on `LC_CTYPE` -- but the principal is right). – Charles Duffy Mar 22 '21 at 21:56
1

A redirection, like > outputfile can be placed in any position (in a simple command). These are equal, even though the last one is probably most commonly seen:

> hello.txt echo hi
echo > hello.txt hi
echo hi > hello.txt 

Hence, the command [ a > 2 ] is the same as [ a ] > 2, i.e. it runs the [ command, with the two arguments a and ]. With only a single argument besides the ending ], it tests if that argument is not the empty string. It isn't, so the test is true, and echo "Foo" runs. And you get a file called 2, an empty one, since [ doesn't output anything.

If you run [ a \> 2 ] instead, the > and 2 are passed as arguments to [, which tests if the string a sorts after the string 2 (if it supports that test, Bash's [/test does). It's not a numerical comparison, and the word a is just a static string, not a variable expansion, so the arithmetic statement ((a = 1)) is not relevant here.

ilkkachu
  • 6,221
  • 16
  • 30
  • So why doesn't it then write "true" to a file named 2? Or is it like, there's a return output and a command output. The return output being true and the command output being "". – JustAFellowCoder Mar 22 '21 at 21:01
  • 1
    @JustAFellowCoder, yep, the exit status and whatever gets printed are different things. Note that `if` takes an arbitrary command, it doesn't have to be `[ .. ]`, or `[[ .. ]]`, or `(( .. ))`. You could do `if echo hello; then echo world; fi`, which prints "hello" and "world", since `echo` returns a truthy exit status, regardless of what it prints (well, unless you redirect the output to `/dev/full`, so that writing the output actually fails). Or more usefully `if grep -q foo file.txt; then echo found match; fi` (where the `-q` tells grep to _not_ print the matching line). – ilkkachu Mar 22 '21 at 21:28
1

Redirections work the same way anywhere in a command. That is to say, the following are all identical:

> 2 [ a ]
[ > 2 a ]
[ a > 2 ]
[ a ] > 2

...every one of them creates a file named 2, and has stdout redirected to it while running the command [ a ] (which doesn't generate any output, and so leaves that file empty).

There's no such thing as redirecting "into a string". All of these redirect into a file.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

Using set -x we see the following behaviour:

+ (( a = 1 ))
+ '[' a ']'
+ echo Foo
Foo

The > 2 part is read as a redirection, so it's creating a file called 2.

Since [ a ] doesn't return any value, the file will be empty!


Then, after the redirection, [ a ] will resolve as true, so echo "Foo" is executed.

0stone0
  • 34,288
  • 4
  • 39
  • 64
  • 2
    s/return any value/write any output/, maybe, if we want to be pedantically correct. (There _is_ a return value, the truthy exit status 0). – Charles Duffy Mar 22 '21 at 17:30