Some Bashisms are faster and more readable

There may be good reasons to stick to POSIX. But if instead you end up using Bash, and you're already sprinkling Bashisms all over your script, then you might as well make good use of it.

I keep stumbling on things like [ $x -gt 2 ] in Bash scripts, and I see no good reason for that. I think this happens mostly because people don't know Bash's arithmetic evaluation syntax exists. After all, ((x > 2)) is immediately more readable. And about twice as fast. And safer: that $x should have been quoted, even at the inevitable price of more noise.

It seems that (( is faster than [[, which is faster than test, which is faster than [. See below.

Granted, unless you're running long loops like that, speed shouldn't make much of a difference. But readability matters, and you can get readability and speed and safe variables that don't need $ or quoting.

Behold as it gets progressively simpler and more readable:

([ "$x" -lt 4 ] && [ "$y" -ne 3 ] && [ "$z" -ge "$(echo "$x + $y" | bc)" ]) && echo "Good!"
([[ "$x" -lt 4 ]] && [[ "$y" -ne 3  ]] && [[ "$z" -ge "$x + $y" ]]) && echo "Good!"
[[ "$x" -lt 4  &&  "$y" -ne 3  &&  "$z" -ge "$x + $y" ]] && echo "Good!"
[[ x -lt 4  &&  y -ne 3  &&  z -ge "x + y" ]] && echo "Good!"
((x < 4  &&  y != 3  &&  z >= x+y)) && echo "Good!"

All things considered, I prefer (( when dealing with integers, and [[ for everything else.

You can try these tests yourself:

Numerical comparison

time for i in {1..2000000}; do   (( 4  >  2 )); done         # |  41% | Fastest
time for i in {1..2000000}; do   [[ 4 -gt 2 ]]; done         # |  52% |
time for i in {1..2000000}; do test 4 -gt 2   ; done         # |  93% |
time for i in {1..2000000}; do    [ 4 -gt 2 ] ; done         # | 100% | Slowest

Files

time for i in {1..2000000}; do   [[ -e /dev/stdin ]]; done   # |  58% | Fastest
time for i in {1..2000000}; do test -e /dev/stdin   ; done   # |  97% |
time for i in {1..2000000}; do    [ -e /dev/stdin ] ; done   # | 100% | Slowest

Strings

time for i in {1..2000000}; do   [[ -n "foo" ]]; done        # |  43% | Fastest
time for i in {1..2000000}; do test -n "foo"   ; done        # |  70% |
time for i in {1..2000000}; do    [ -n "foo" ] ; done        # | 100% | Slowest

By the way, we don't really need -n with [[ to confirm that a string isn't empty:

some1-p() if [[    "$1"  ]]; then echo some; else echo empty; fi
some2-p() if [[ -n "$1"  ]]; then echo some; else echo empty; fi

some1-p "quux" #⇒ some
some2-p "quux" #⇒ some

some1-p   ""   #⇒ empty
some2-p   ""   #⇒ empty

a="foobar" b="" c=

some1-p  "$a"  #⇒ some
some2-p  "$a"  #⇒ some

some1-p  "$b"  #⇒ empty
some2-p  "$b"  #⇒ empty

some1-p  "$c"  #⇒ empty
some2-p  "$c"  #⇒ empty

some1-p  "$d"  #⇒ empty
some2-p  "$d"  #⇒ empty

I found no difference in speed between some1-p and some2-p.

📆 2025-11-02