How exactly does "/bin/[" work?

9,730

Solution 1

The [ command's job is to evaluate test expressions. It returns with a 0 exit status (that means true) when the expression resolves to true and something else (which means false) otherwise.

It's not that it does nothing, it's just that its outcome is to be found in its exit status. In a shell, you can find out about the exit status of the last command in $? for Bourne-like shells or $status in most other shells (fish/rc/es/csh/tcsh...).

$ [ a = a ]
$ echo "$?"
0
$ [ a = b ]
$ echo "$?"
1

In other languages like perl, the exit status is returned for instance in the return value of system():

$ perl -le 'print system("[", "a", "=", "a", "]")'
0

Note that all modern Bourne-like shells (and fish) have a built-in [ command. The one in /bin would typically only be executed when you use another shell or when you do things like env [ foo = bar ] or find . -exec [ -f {} ] \; -print or that perl command above...

The [ command is also known by the test name. When called as test, it doesn't require a closing ] argument.

While your system may not have a man page for [, it probably has one for test. But again, note that it would document the /bin/[ or /bin/test implementation. To know about the [ builtin in your shell, you should read the documentation for your shell instead.

For more information about the history of that utility and the difference with the [[...]] ksh test expression, you may want to have a look at this other Q&A here.

Solution 2

I am always surprised that in the folder /bin there is a [ program.

You are right to be surprised. That's one of the rare POSIX commands, with the null utility (:), that doesn't respect the commands file allowed characters convention (portable filename character set).

Is is what is called when we are doing something like: if [ something ]?

Precisely but it can be used without the if too.

By calling the [ program explicitly in a shell it asks for a corresponding ], and when I provide the closing bracket it seems to do nothing no matter what I insert between the brackets.

It does nothing visible but it actually does the very same thing than when used with if, i.e. it sets the exit status to 0 (true) or anything else (false) depending on what you put inside the brackets. It is (for a reason) the same behavior as the test command; the only difference is that it looks for the ending ]. See man test for details.

Needless to say, the usual way about getting help about a program does not work, i.e. neither man [ nor [ --help works.

That depends on your Operating System. man [ definitely works for me on a couple of mainstream Gnu/Linux distributions but it doesn't on Solaris.

[ --help might work or not depending on the implementation as it is breaking the syntax anyway, missing the ending ]. Moreover, the POSIX standard for the test / [ command explicitly rules out all options, including the -- option termination so both [ --help ] and test --help need to return true and be silent by design. Note that what you put inside the brackets or after [ and that look like options (e.g. -f file, -n string, and the likes) are not options but operands.

All modern Bourne style shell interpreters (like bash, ksh, dash and zsh to name a few) implement the test/[ utility internally as a builtin so when you use them, the right manual page to refer to might be the one of the shell, not the test one.

Before Unix System III (1981), the Bourne shell didn't implemented the test utility as a builtin so only the external binary command implementation was available. There wasn't either a [ command (internal or builtin) until Unix System III so for example under Unix Version 7 you had to type:

if test something ; then
…

instead of:

if [ something ] ; then
…

Solution 3

[ is actually more commonly known as test command. Typical use of this command is simply to evaluate expressions and return their condition - true or false. It is often used in if-then-else-fi statements, although it can be used outside of if statements to conditionally run other commands via shell's && or || operators, like so.

$ [ -e /etc/passwd  ] && echo "File exists"
File exists

$ test -e /etc/passwd && echo "File exists"
File exists

More specifically, evaluation is communicated to other commands via exit status. Some programs may choose to output exit status to signify different types of events - program completing successfully, an error of particular type occurring during execution, or syntax errors. In the case of the test command, there are 0 signifies true, and 1 signifies false. As Stephan pointed out, syntax errors produce exit status of 2.

Its location depends on your system, and it also explains why you didn't see man page when you did man [. For instance, on FreeBSD it is under /bin. On Linux (or in my particular case, Ubuntu 16.04) it is in /usr/bin/. If you do man [ or man test on a Linux system, you will see same documentation open. It's also important to note that your shell may have its own implementation of test.

It should also be noted that this command has issues, which Korn shell's implementation (commonly known as "conditional expression" reference with double square brackets, [[ "$USER" = "root" ]] ) seeks to resolve. This feature also used by other shells such as bash and zsh.

Solution 4

Neither man [ nor [ --help works

On some distros (e.g. Ubuntu), man [ follows a symlink to test(1). On others (e.g. Arch), this is not the case. (But the test man page does document usage of [ as well)


type -a [ shows you that there's both a shell builtin and an executable.

$ type -a [
[ is a shell builtin
[ is /usr/bin/[

bash-builtin [ --help just prints an error message. (But since it's a builtin, you can use help [, or look at the bash man page / docs).

/usr/bin/[ --help prints full help output (for the GNU Coreutils version), which starts out with:

$ /usr/bin/[ --help
Usage: test EXPRESSION
  or:  test
  or:  [ EXPRESSION ]
  or:  [ ]
  or:  [ OPTION
Exit with the status determined by EXPRESSION.

      --help     display this help and exit
      --version  output version information and exit

and then describes the allowed syntax for EXPRESSION.

This is another way you could have found out that [ and test are equivalent.


BTW, if you are programming for bash (or writing one-liners interactively), I'd recommend [[ instead of [. It's better in several ways, see links in Serg's answer.

Solution 5

[ command returns exit-status zero if expression, contained in its arguments, is considered true and non-zero exit-status if expression, contained in its arguments, is considered false. It also fails with error message if its last argument isn't ] (this is done purely for aesthetic reasons).

E.g.:

[ hello ]
echo "Exit-status of [ hello ] is:" $?
[ abc = abc ]
echo "Exit-status of [ abc = abc ] is:" $?
[ ]
echo "Exit-status of [ ] is:" $?
[ abc = def ]
echo "Exit-status of [ abc = def ] is:" $?

… will output:

Exit-status of [ hello ] is: 0      — because non-empty string is considered true
Exit-status of [ abc = abc ] is: 0  — because 'abc' really is same as 'abc'
Exit-status of [ ] is: 1            — because empty string is considered false
Exit-status of [ abc = def ] is: 1  — because 'abc' really differs from 'def'

However, bash and many other shells really usually don't invoke /bin/[ (or /usr/bin/[) in these cases, but call built-in command with exactly the same behavior instead (purely for performance reasons). To invoke /bin/[ (not shell built-in surrogate) you need either to explicitly specify its path (e.g. /bin/[ hello ]; you don't need to prefix ] with dirname though ☺), or to configure shell not to use a built-in surrogate (for example, enable -n [ in bash).

P. S.: As it was said in other answers, [ is related to test. But test, unlike [, doesn't require ] as its last argument (and doesn't expect it at all; adding extra ] to test arguments can cause it to fail with error message or to return wrong result). The /bin/test and /bin/[ can resolve to the same file (e.g. one is symlinked; in this case the behavior diversion is probably implemented by analyzing the currently-called command within the test/[ code itself) or to different files. For test, shell also usually invokes built-in surrogate, unless path is explicitly specified (/bin/test) or it's configured not to do so (enable -n test).

P. P. S.: Unlike test and [, modern if is never a real file. It's part of shell (e.g. bash) syntax: if commandA; then commandB; fi (newlines can be used instead of semicolons) causes commandB to be executed if-and-only-if commandA exited with zero status. This perfectly fits to behavior of test or [, allowing to combine them like if [ "$a" = foo ]; then …; fi (or if test "$a" = foo; then …; fi — just less readable). However, modern scripts often use [[ instead of test or [, which (as the if) is never a real file, but always a part of shell syntax.

P. P. P. S.: As for man — never expect man to have an article on every command in your file-system. Info on some (even "real", file-based) commands may be missing, info on some shell built-ins maybe present not only within an article dedicated to specific shell (that's the place where you most certainly will find info on test, [, if, [[). Still, many distributions have explicit man-articles for test and [. (About --help, it's not recognized with test for obvious reason: it needs to handle quietly cases like a=--help; test "$a"; on some distributions [ --help (without closing ]) still shows help, on some it doesn't.)

Share:
9,730

Related videos on Youtube

Bregalad
Author by

Bregalad

Updated on September 18, 2022

Comments

  • Bregalad
    Bregalad almost 2 years

    I am always surprised that in the folder /bin there is a [ program.

    Is this what is called when we are doing something like: if [ something ]?

    By calling the [ program explicitly in a shell it asks for a corresponding ], and when I provide the closing bracket it seems to do nothing no matter what I insert between the brackets.

    Needless to say, the usual way about getting help about a program does not work, i.e. neither man [ nor [ --help works.

    • Ipor Sircer
      Ipor Sircer over 7 years
      try man expr.
    • Sergiy Kolodyazhnyy
      Sergiy Kolodyazhnyy over 7 years
      @IporSircer [ refers to test command though, not expr , so it should be man test
    • Ipor Sircer
      Ipor Sircer over 7 years
      oops. sorry! i was tired.
    • Toby Speight
      Toby Speight over 7 years
      man '[' works fine for me - either you forgot to quote [ or you have a different definition of "works".
    • Sergiy Kolodyazhnyy
      Sergiy Kolodyazhnyy over 7 years
      @TobySpeight See my and Stephane's answers. [ has OS-specific implementations , which may or may not have a man page
    • Olivier Dulac
      Olivier Dulac over 7 years
      A few warnings: [ evaluates its arguments, so you need to have spaces between all of them. [ a=b ] is not a comparison: it will always be true (it is a single string: "a=b", which is always evaluated to true) And you should limit the number of arguments to 4 (even though recent implementations will allow more... limiting to 4 makes it more portable. For example : [ "a" = "b" ] already has 4 arguments : "a" "=" "b" and the non-necessary end of test arg : "]" ). If you need more: chain tests with for example: if [ "$Var_a" = "foo" ] && [ "$Var_b" = "bar" ] ; then : do something ; fi
    • muru
      muru over 7 years
      @OlivierDulac a ! by itself (unescaped and unquoted) won't be replaced, and even better would be if ! [ .... All of the bash expansions that use ! include at least on other character
    • Olivier Dulac
      Olivier Dulac over 7 years
      @muru: well, I stand corrected! Which is weird because I've seen "!" replacement occur so many times that I now systematically escape it (hence I didn't notice it would work there...). I'm quite sure it happened (for exemple inside double quotes), even with no trailing character, but I may be wrong.
    • Dmitry Grigoryev
      Dmitry Grigoryev over 7 years
    • Baard Kopperud
      Baard Kopperud over 7 years
      You should also note that there is also a [-builtin in bash (and possibly other shells as well), and that this may be used instead of /bin/[. Also there is the test-command, which on many systems is a symbolic link to /bin/[ (or vice versa) - but on others is a separate command.
  • franklinsijo
    franklinsijo over 7 years
    /usr/bin/ for Amazon Linux as well.
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 7 years
    @StéphaneChazelas Thank you. Answer edited accordingly
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 7 years
    “all Bourne-like shells (and fish) have a built-in [ command” That's true in practice in the 21st century, but I'm pretty sure I've used a shell without [. I don't remember if it was a Bourne variant or an antique version of ash.
  • jlliagre
    jlliagre over 7 years
    @Gilles The Bourne shell implemented both [ and test as builtins with Unix System III. Before that, there was no [ and test was only an external binary command.
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @Gilles, the original ash had test builtin (merged with expr), but optionally. According to in-ulm.de/~mascheck/various/ash, BSDs didn't have test builtin until around 2000. I've added "modern".
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    Note that if was a command up to Unix V6. Unix V7 (1979) came with the Bourne shell (with its if-then-else-fi construct) and the new test command.
  • Adam
    Adam over 7 years
    "man [ definitely works for me on mainstream Gnu/Linux distributions" Hmm.. Centos (admittedly 6.8 still) clearly isn't mainstream enough :) man test works but not man [ On the other hand my cygwin environment does known about man [!
  • Alavi
    Alavi over 7 years
    Why in the world would you want to do find . -exec [ f {} ] \; -print? The command find . -type f -print would do the same thing so much more efficiently...
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @Thisisn'tmyrealname, no, for the equivalent of -exec [ -f {} ] \; (check that the file is regular after symlink resolution), you'd need the GNU-specific -xtype f. Other find implementation have no equivalent (using -L/-follow have other undesirable side-effects).
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @Thisisn'tmyrealname. (continued), You can optimise by doing \( -type f -o -type l -exec [ -f {} ] \; \) or invoke sh with -exec {} + to run the [ builtin in a loop, but that's beyond the point of this Q&A. Note that there's no point using env [ ... ], those are just to give example of when /bin/[ may be executed.
  • jlliagre
    jlliagre over 7 years
    @Adam Yes, you are right, that's more recent than I thought although I didn't wrote all mainstream Linux distros, just that it worked for me (i.e. on the ones I tested). That is OL 7.2 (RHEL 7.2) clone and Mint 17.1 (Ubuntu 14.04).
  • Darkhogg
    Darkhogg over 7 years
    man [ doesn't seem to work on Manjaro (Arch Linux based). The manual for test lists [ --help as a valid invocation that should show help, but interestingly it doesn't, and instead complains about a missing ], same as with --version. Adding the ] doesn't do anything, so it seems impossible to get help other than from man test.
  • jlliagre
    jlliagre over 7 years
    @Darkhogg You can try /usr/bin/[ --help or /bin/[ --help.
  • Darkhogg
    Darkhogg over 7 years
    @jlliagre You are completely right, I was using the builtins. env [ --help as well as /usr/bin/[ --help work completely fine (although I need to quote /usr/bin/[ or zsh complains).