Bash bracket hacking

I am no shell scripting expert, but I have written a lot of bash code in my day. Most of my shell scripting know-how has come from trial-and-error and desperate googling. This has (slowly) taught me a few nifty tricks and some of the more interesting details of how bash actually “works” (for lack of a better word). I’d like to share one small tidbit with you now.

The “[” program.

No, that’s not a mistake. There actually is a program on UNIX systems called “[“, and it generally lives in /usr/bin. Don’t believe me? Check out this which command:

# which [
/usr/bin/[

Isn’t that odd? On some systems (namely Fedora-variants) the “[” program is actually a symbolic link (“symlink” among friends) to a program called “test“:

# ls -l /usr/bin/[
lrwxrwxrwx root /usr/bin/[ -> test

You see, every good UNIX-like OS ships with a handy utility called test (man 1 test). It’s a simple program that can answer questions like “is variable $foo greater than 42?”, like this:

# foo=43
# test $foo -gt 42
# echo $?
0
# test $foo -lt 42
# echo $?
1

It can do a whole slew of other comparisons too, including math, string comparisons, file operations (does file $foo exist?), and a bunch of other stuff (man 1 test for full details)

The “test” program gives output solely by its exit status (that’s what $? stores after you run a program in bash). That way, you can use it in bash if statements, like this:

foo=43
if test $foo -gt 42; then
   echo $foo is greater than 42
else
   echo $foo is NOT greater than 42
fi

It turns out that our good UNIX forebears hated typing, so they invented the “[” program, which does exactly the same thing, but it looks a heck of a lot like a C conditional when used in shell scripts, like this:

foo=43
if [ $foo -gt 42 ]; then
   echo $foo is greater than 42
else
   echo $foo is NOT greater than 42
fi

The only difference between “test” and “[” is that “[” requires you to have a matching “]” as its last argument or it complains on stderr. Isn’t that nifty? I think so. This explains why, by the way, you have to have spaces before and after the “[” and “]”. It’s because “[” is actually a program name and “]” is just a command line argument like any other argument.

Now get back to that shell script you were hacking.

6 comments to “Bash bracket hacking”

You can leave a reply or Trackback this post.
  1. http://Byron%20Clark says: -#1

    Actually, when writing a bash script, both ‘[‘ and ‘test’ are shell builtins so the /usr/bin versions are not used.

  2. Ah, very nice. This makes sense, because when using ‘[‘ on the command line for a one-liner, you don’t have to include the closing ‘]’, but in a bash script, you do. I guess that proves Byron’s point.

    –Dave

  3. http://harold says: -#1

    You may also use type -P instead of which!

    help type

    type -a [
    type -p [
    type -P [

    help test

    see: http://codesnippets.joyent.com/posts/show/2067

  4. @Byron:

    It’s not a problem to disable those builtins or to explicit call the external code. Since not every shell might have them builtin, but POSIX actually requires the behaviour and the command names, they’re usually present as binaries on disk (doesn’t apply to binaries you can only implement as a builtin, of course, except the cd command maybe).

    @Dave:

    I don’t really understand that. Code?

  5. @TheBonsai:

    I’ll explain with an example:

    Run this (this uses the binary, you might need /usr/bin instead of /bin on your system):

    /bin/[ 42 -gt 43

    And it works as expected even though it’s missing the closing bracket.

    Now run this (this uses the built-in):

    [ 42 -gt 43
    -bash: [: missing `]’

    The built-in wants the closing bracket, but the [ binary doesn’t care.

  6. I’m sorry for the delay :)

    @Dave

    It’s just that your external [ is fault tolerant.