As of 2016-02-26, there will be no more posts for this blog. s/blog/pba/
Showing posts with label expression. Show all posts

A couple of years back I wrote about the builtin vs keyword conditional expressions, but I didnt think about another conditional use with arithmetic, that is with tests like [ $a -gt $b] or [[ $a -ne 0 ]].

Use similar code to benchmark 1 > 0 as shown below, not the real code, but you get the idea:

time for ((i = 0; i < 10000; i++)); do <test> 1 >/-gt 0 ; done

The result is:

<test> with time % slower
[ 6.647s 47.9%
[[ 4.692s 04.4%
(( 4.493s fastest
test (builtin) 6.538s 45.5%

(( is just marginally faster than [[ by my definition. Of course, they both are faster than builtin [ and test, as for /usr/bin/test, its an external command, there is no point to test, because its slow for sure.

With this result, there really isnt much difference between two, however, 1 > 0 is more readable than 1 -gt 0, literally and mathematically.

Note

Yes, [[ 1 > 0 ]] is a valid syntax, but it doesnt do what you think, its not arithmetically but lexicographically. In short, its for strings, see bash(1), and there is no such this as [[ 1 >= 0 ]].

Even since I started writing Bash script, I always use [[ for if conditional statement, the double square brackets, never the single one version unless compatibility has to be taken into account. Why did I choose it? Its because I read [[ is faster. Ive never tried to confirm it by myself, although I do know that [ is a type of shell builtin command and [[ is type of shell keyword, and [ is equivalent to test, which is also a builtin command in Bash for very long time.1

Note

[ and [[ can also be used for arithmetic comparison, such as the use of -gt operator, see another test with Arithmetic Evaluation, ((. This post only focuses on the strings.

From time to time, I often see people still using [ even in the script with a few Bash-only syntaxes or features. They are clearly not writing with compatibility as a requirement. If I have a chance, then I would probably advise the coder to change to [[. I had done so a few times in the past.

However, I never see the numbers, so I used the following to test:

time for ((i = 0; i < 10000; i++)); do <test> -z '' ; done

The result is:

<test> with time % slower
[ 00.149s 00,063.7%
[[ 00.091s fastest
test (builtin) 00.150s 00,064.8%
/usr/bin/test 13.798s 15,063.6%

[ and test possibly are synonyms since they are pretty close.

As you can see [[ definitely is the winner, and /usr/bin/test external command is the slowest. The problem with /usr/bin/test isnt that is inefficient, but external command is costly.

If you are new to Bash, just use [[.

[1]If you dont know the differences between keyword, builtin command, and external command, google them.

if in Bash is similar to if conditional statements in other programming languages. The statement checks the values, such as equality or empty value. The one difference is the syntax of how if statement looks.

1   Syntax

The syntax in Bash is, from bash(1):

if list; then list; [ elif list; then list; ] ... [ else list; ] fi

Where list is, from bash(1)

a sequence of one or more pipelines separated by one of the operators ;, &, &&, or , and optionally terminated by one of ;, &, or <newline>.

and A pipeline is, from bash(1)

a sequence of one or more commands separated by one of the control operators | or |&. The format for a pipeline is:

[time [-p]] [ ! ] command [ [||&] command2 \... ]

If you understand above, you may ask where does that put [ ] and [[ ]] or even (( ))? Almost all of the code you will read would look like:

if [[ -z "$foobar" ]]; then
  do_something
fi

Those three are actually commands, first one is the shell builtin (= test), last two are compound commands (and keywords). What does that mean? It means that [[ ]] and other two are not part of if syntax.

I believe many of you are like me used to think [[ ]] is part of if syntax, therefore you may code like this:

command
ret=$?
if (( $? == 0 )); then :; fi

You can see the better way to code it in next section.

2   Checking exit status

if command; then :; fi

if relies on the last command in the list whose exit status (return value) will be used to decide which branch it should go next. When command returns 0, it runs the code; or runs code in else when command returns other than 0.

Sometimes, you may want to want to run code when the command fails, i.e. returns non-zero exit status:

if ! command; then :; fi

Again, ! is not part of if syntax, but is part of the pipeline, from bash(1):

If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status as described above.

If you are crazy enough, you can

if
  command1
  command2
  command3
  ...
  command99
then
  :
fi

Only the exit status of command99 will be checked. I know nobody code like this, but you can do like that. To be more practical, it maybe more practical for while:

while prepare_something; command_to_check; do :; done

3   Checking values

There are plenty of pages about checking values, I am not going to write about it. Read bash(1), it should be enough, actually. One thing I want to mention is how to check multiple conditions:

if [[ "$foo" == "foo" ]] && [[ "$bar" == "bar" ]]; then command; fi

There is a shortcut for this, this can be rewritten as

[[ "$foo" == "foo" ]] && [[ "$bar" == "bar" ]] && command

3.1   test and [ ] vs. [[ ]]

One may ask the difference, to be perfectly honest, I dont know completely. The former is shell builtin command, the latter is shell reserved keyword. Shell built-in command is faster than external command and I believe reserved keyword may be faster, because from what I know about shell built-in command is loadable extension. (I have written one of my own for prompt) So reversed keyword may be faster because it may not need to access data through another layer. But I dont know what exact the implementation detail is, so dont quote me on this.

What I am certain is that [ ] will give you more compatibility. But the thumb of rule is to code with the one you are comfortable with.

3.2   (( )) vs. [[ ]]

[[ 1 -gt 0 ]] and (( 1 > 0 )) test exactly the same thing, however, there is slight performance difference, the latter, Arithmetic Evaluation, is a bit of faster, but hardly noticeable unless you do at million times scale.