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

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.

A year ago, I didn't even know you can write a C code for Bash extension. Yesterday, I stumbled on Using and Writing Bash Dynamically Loadable Built-In Commands and I knew I could do something with this new discovery.

So, I wrote a C code for replacing my Bash version script. The speed is only three times fast, I am so disappointed I expected much faster. Anyway, it's really hardly can gain much from improving PS1, it's not as if your PS1 is required to update 1000 times per second.

You can go check some loadable builtins comes with Bash, they should be installed at /usr/lib[64]/bash/. Use enable <name> to enable and to disable with -d.

If you want to develop your own builtin, you will need to download the Bash source code. I doubt any distro will install header files of Bash for you. A basic ./configure is needed to generate necessary header files and also you will need to run make pathnames.h for that header at least.