535

I just wondered what exactly the difference between

[[ $STRING != foo ]]

and

[ $STRING != foo ]

is, apart from that the latter is POSIX-compliant, found in sh and the former is an extension found in bash.

Lucas
  • 105
  • 4
0x89
  • 6,345
  • 3
  • 21
  • 14
  • 4
    In case you were also wondering about using no brackets at all, e.g. in the context of an `if` statement, see http://mywiki.wooledge.org/BashPitfalls#if_.5Bgrep_foo_myfile.5D – Kev Oct 15 '15 at 19:09
  • also, From Ubuntu's docs: https://wiki.ubuntu.com/DashAsBinSh#I_am_a_developer._How_can_I_avoid_this_problem_in_future.3F – radistao Feb 27 '18 at 13:23
  • 1
    See also https://stackoverflow.com/questions/13542832/difference-between-single-and-double-square-brackets-in-bash, https://unix.stackexchange.com/questions/3831/in-bash-are-if-z-1-and-if-1-the-same – pevik Dec 04 '18 at 09:26
  • See related on Stack Overflow: [unary operator expected](https://stackoverflow.com/q/13617843/3258851) – Marc.2377 Dec 13 '19 at 05:59

5 Answers5

378

There are several differences. In my opinion, a few of the most important are:

  1. [ is a builtin in Bash and many other modern shells. The builtin [ is similar to test with the additional requirement of a closing ]. The builtins [ and test imitate the functionality /bin/[ and /bin/test along with their limitations so that scripts would be backwards compatible. The original executables still exist mostly for POSIX compliance and backwards compatibility. Running the command type [ in Bash indicates that [ is interpreted as a builtin by default. (Note: which [ only looks for executables on the PATH and is equivalent to type -P [. You can execute type --help for details)
  2. [[ is not as compatible, it won't necessarily work with whatever /bin/sh points to. So [[ is the more modern Bash / Zsh / Ksh option.
  3. Because [[ is built into the shell and does not have legacy requirements, you don't need to worry about word splitting based on the IFS variable to mess up on variables that evaluate to a string with spaces. Therefore, you don't really need to put the variable in double quotes.

For the most part, the rest is just some nicer syntax. To see more differences, I recommend this link to an FAQ answer: What is the difference between test, [ and [[ ?. In fact, if you are serious about bash scripting, I recommend reading the entire wiki, including the FAQ, Pitfalls, and Guide. The test section from the guide section explains these differences as well, and why the author(s) think [[ is a better choice if you don't need to worry about being as portable. The main reasons are:

  1. You don't have to worry about quoting the left-hand side of the test so that it actually gets read as a variable.
  2. You don't have to escape less than and greater than < > with backslashes in order for them not to get evaluated as input redirection, which can really mess some stuff up by overwriting files. This again goes back to [[ being a builtin. If [ (test) is an external program the shell would have to make an exception in the way it evaluates < and > only if /bin/test is being called, which wouldn't really make sense.
li ki
  • 9
  • 3
Kyle Brandt
  • 82,107
  • 71
  • 302
  • 444
  • 5
    Thanks, the link to the bash FAQ was what I was looking for (did not know about that page, thanks). – 0x89 Aug 09 '09 at 22:07
  • 2
    I edited your post with this information, but [ and test are executed as builtins. The builtins were designed to replace /bin/[ and /bin/test but needed to reproduce the limitations of the binaries too. The command 'type [' verifies that the builtin is used. 'which [' only searches for executables on the PATH and is equivalent to 'type -P [' – klynch Jul 18 '11 at 22:55
160

In Short:

[ is a bash Builtin

[[ ]] are bash Keywords

Keywords: Keywords are quite like builtins, but the main difference is that special parsing rules apply to them. For example, [ is a bash builtin, while [[ is a bash keyword. They are both used for testing stuff, but since [[ is a keyword rather than a builtin, it benefits from a few special parsing rules which make it a lot easier:

  $ [ a < b ]
 -bash: b: No such file or directory
  $ [[ a < b ]]

The first example returns an error because bash tries to redirect the file b to the command [ a ]. The second example actually does what you expect it to. The character < no longer has its special meaning of File Redirection operator.

Source: http://mywiki.wooledge.org/BashGuide/CommandsAndArguments

Aaron Miller
  • 107
  • 2
abhiomkar
  • 1,703
  • 1
  • 11
  • 7
  • 6
    `[` is a POSIX shell command; it need not be built in. `]` is just an argument which that command looks for, so that the syntax is balanced. The command is a synonym for `test` except that `test` doesn't look for a closing `]`. – Kaz Sep 22 '17 at 23:52
  • See here: http://pubs.opengroup.org/onlinepubs/009695399/utilities/test.html – Kaz Sep 22 '17 at 23:53
102

Behavior differences

Some differences on Bash 4.3.11:

  • POSIX vs Bash extension:

  • regular command vs magic

    • [ is just a regular command with a weird name.

      ] is just the last argument of [.

    Ubuntu 16.04 actually has an executable for it at /usr/bin/[ provided by coreutils, but the bash built-in version takes precedence.

    Nothing is altered in the way that Bash parses the command.

    In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \, and word expansion happens as usual.

    • [[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different.

      There are also further differences like = and =~.

    In Bashese: [ is a built-in command, and [[ is a keyword: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && and ||

    • [[ a = a && b = b ]]: true, logical and
    • [ a = a && b = b ]: syntax error, && parsed as an AND command separator cmd1 && cmd2
    • [ a = a ] && [ b = b ]: POSIX reliable equivalent
    • [ a = a -a b = b ]: almost equivalent, but deprecated by POSIX because it is insane and fails for some values of a or b like ! or ( which would be interpreted as logical operations
  • (

    • [[ (a = a || a = b) && a = b ]]: false. Without ( ), would be true because [[ && ]] has greater precedence than [[ || ]]
    • [ ( a = a ) ]: syntax error, () is interpreted as a subshell
    • [ \( a = a -o a = b \) -a a = b ]: equivalent, but (), -a, and -o are deprecated by POSIX. Without \( \) would be true because -a has greater precedence than -o
    • { [ a = a ] || [ a = b ]; } && [ a = b ] non-deprecated POSIX equivalent. In this particular case however, we could have written just: [ a = a ] || [ a = b ] && [ a = b ] because the || and && shell operators have equal precedence unlike [[ || ]] and [[ && ]] and -o, -a and [
  • word splitting and filename generation upon expansions (split+glob)

    • x='a b'; [[ $x = 'a b' ]]: true, quotes not needed
    • x='a b'; [ $x = 'a b' ]: syntax error, expands to [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: syntax error if there's more than one file in the current directory.
    • x='a b'; [ "$x" = 'a b' ]: POSIX equivalent
  • =

    • [[ ab = a? ]]: true, because it does pattern matching (* ? [ are magic). Does not glob expand to files in current directory.
    • [ ab = a? ]: a? glob expands. So may be true or false depending on the files in the current directory.
    • [ ab = a\? ]: false, not glob expansion
    • = and == are the same in both [ and [[, but == is a Bash extension.
    • case ab in (a?) echo match; esac: POSIX equivalent
    • [[ ab =~ 'ab?' ]]: false, loses magic with '' in Bash 3.2 and above and provided compatibility to bash 3.1 is not enabled (like with BASH_COMPAT=3.1)
    • [[ ab? =~ 'ab?' ]]: true
  • =~

    • [[ ab =~ ab? ]]: true, POSIX extended regular expression match, ? does not glob expand
    • [ a =~ a ]: syntax error. No bash equivalent.
    • printf 'ab\n' | grep -Eq 'ab?': POSIX equivalent (single line data only)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX equivalent.

Recommendation: always use []

There are POSIX equivalents for every [[ ]] construct I've seen.

If you use [[ ]] you:

  • lose portability
  • force the reader to learn the intricacies of another bash extension. [ is just a regular command with a weird name, no special semantics are involved.

Thanks to Stéphane Chazelas for important corrections and additions.

  • How to use `printf 'ab' | grep -Eq 'ab?'` within `if [ … ]`? – meeDamian May 25 '16 at 05:02
  • 1
    @meeDamian `if ( printf 'ab' | grep -Eq 'a' ); then echo 'a'; fi`. `[]` is a command just like `grep`. The `()` may not be needed on that command I'm not sure: I added it because of the `|`, depends on how Bash parses things. If there was no `|` I'm sure you can write just `if cmd arg arg; then`. – Ciro Santilli OurBigBook.com May 25 '16 at 05:56
  • 1
    @meeDamian yeah, no need for `()` it seems: http://stackoverflow.com/questions/8965509/why-if-ps-aux-grep-always-succeeds-in-bash – Ciro Santilli OurBigBook.com May 25 '16 at 06:01
  • 1
    Nice list! See also: https://wiki.ubuntu.com/DashAsBinSh#I_am_a_developer._How_can_I_avoid_this_problem_in_future.3F – radistao Feb 27 '18 at 13:23
18

Single Bracket i.e. [] is POSIX shell compliant to to enclose a conditional expression.

Double Brackets i.e. [[]] is an enhanced (or extension) version of standard POSIX version, this is supported by bash and other shells(zsh,ksh).

In bash, for numeric comparison we use eq, ne,lt and gt, with double brackets for comparison we can use ==, !=, <, and > literally.

  • [ is a synonym for test command. Even if it is built in to the shell it creates a new process.
  • [[ is a new improved version of it, which is a keyword, not a program.

for example:

[ var1 lt var2] #works
[ var1 < var2] #error: var2 No such file or directory 
[ var1 \< var2] #works with escape
[[ var1 < var2]] #works
Premraj
  • 385
  • 5
  • 13
9

Based on a quick reading of the relevant sections of the manpage, the primary difference appears to be that the == and != operators match against a pattern, rather than a literal string, and also that there's the =~ regex comparison operator.

womble
  • 95,029
  • 29
  • 173
  • 228