ChatGPT解决这个技术问题 Extra ChatGPT

Difference between single and double square brackets in Bash

I'm reading bash examples about if but some examples are written with single square brackets:

if [ -f $param ]
then
  #...
fi

others with double square brackets:

if [[ $? -ne 0 ]]
then
    start looking for errors in yourlog
fi

What is the difference?

You can get your answer by looking at answer of this question: unix.stackexchange.com/questions/3831/…
Tangentially, regarding the second example, see also Why is testing ”$?” to see if a command succeeded or not, an anti-pattern?

c
cmh

Single [] are posix shell compliant condition tests.

Double [[]] are an extension to the standard [] and are supported by bash and other shells (e.g. zsh, ksh). They support extra operations (as well as the standard posix operations). For example: || instead of -o and regex matching with =~. A fuller list of differences can be found in the bash manual section on conditional constructs.

Use [] whenever you want your script to be portable across shells. Use [[]] if you want conditional expressions not supported by [] and don't need to be portable.


I'd add that if your script doesn't start with a shebang that explicitly requests a shell that supports [[ ]] (e.g. bash with #!/bin/bash or #!/usr/bin/env bash), you should use the portable option. Scripts that assume /bin/sh supports extensions like this will break on OSes like recent Debian and Ubuntu releases where that's not the case.
C
Ciro Santilli Путлер Капут 六四事

Behavior differences

Tested in Bash 4.3.11:

POSIX vs Bash extension: [ is POSIX [[ is a Bash extension inspired from Korn shell

[ is POSIX

[[ is a Bash extension inspired from Korn shell

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

[ is just a regular command with a weird name. ] is just the last argument of [.

[[ 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 =~.

< [[ a < b ]]: lexicographical comparison [ a \< b ]: Same as above. \ required or else does redirection like for any other command. Bash extension. expr x"$x" \< x"$y" > /dev/null or [ "$(expr x"$x" \< x"$y")" = 1 ]: POSIX equivalents, see: How to test strings for lexicographic less than or equal in Bash?

[[ a < b ]]: lexicographical comparison

[ a \< b ]: Same as above. \ required or else does redirection like for any other command. Bash extension.

expr x"$x" \< x"$y" > /dev/null or [ "$(expr x"$x" \< x"$y")" = 1 ]: POSIX equivalents, see: How to test strings for lexicographic less than or equal in Bash?

&& 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 && 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 [

[[ (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

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 = 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.

[[ 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.


"There are POSIX equivalents for every [[ ]] construct I've seen." The same thing can be said of any Turing Complete language on the face of the planet.
@A.Rick that would be a valid answer to all "How to do X in language Y" SO questions :-) Of course, there is a "conveniently" implicit in that statement.
Fantastic summary. Thanks for the effort. I however disagree with the recommendation. It's portability versus more consistent and powerful syntax. If you can require bash >4 in your environments then the [[ ]] syntax is recommended.
@Downvoters please explain so I can learn and improve info :-)
Great answer but I think the Recommendation: always use [] should be read as My preference: use [] if you don't want to lose portability. As stated here: If portability/conformance to POSIX or the BourneShell is a concern, the old syntax should be used. If on the other hand the script requires BASH, Zsh, or KornShell, the new syntax is usually more flexible, but not necessarily backwards compatible. I'd rather go with [[ ab =~ ab? ]] if I can and have no concern about backward compatibility than printf 'ab' | grep -Eq 'ab?'
s
sampson-chen

Inside single brackets for condition test (i.e. [ ... ]), some operators such as single = is supported by all shells, whereas use of operator == is not supported by some of the older shells.

Inside double brackets for condition test (i.e. [[ ... ]]), there is no difference between using = or == in old or new shells.

Edit: I should also note that: In bash, always use double brackets [[ ... ]] if possible, because it is safer than single brackets. I'll illustrate why with the following example:

if [ $var == "hello" ]; then

if $var happens to be null / empty, then this is what the script sees:

if [ == "hello" ]; then

which will break your script. The solution is to either use double brackets, or always remember to put quotes around your variables ("$var"). Double brackets is better defensive coding practice.


Putting quotes around all reads of variables unless you have a very good reason not to is a much better defensive coding practice, since it applies to all reads of variables, not just those in conditions. An iTunes installer bug once deleted people's files if the hard drive name contained spaces (or something like that). It also solves the problem you mention.
c
codeforester

[[ is a bash keyword similar to (but more powerful than) the [ command.

See

http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals

Unless you're writing for POSIX sh, we recommend [[.


C
Cromax

[ is a builtin like printf. Bash syntax expect to see it at the same place as commands. And ] is nothing to Bash except the fact that it is expected by the [ builtin. (man bash / SHELL BUILTIN COMMANDS)

[[ is a keyword like if. Bash syntax starts also expect it at the same place as command but instead of executing it, it enters the conditional context. And ]] is also a keyword ending this context. (man bash / SHELL GRAMMAR / Compound Commands)

In order, bash tries to parse: Syntax Keywords > User Alias > Builtin Function > User Function > Command in $PATH

type [  # [ is a shell builtin
type [[  # [[ is a shell keyword
type ]  # bash: type: ]: not found
type ]]  # ]] is a shell keyword
compgen -k  # Keywords: if then else ...
compgen -b  # Builtins: . : [ alias bg bind ...
which [  # /usr/bin/[

[ is slower <= it executes more parsing code I guess. But I know that it calls the same number of syscall (tested with

[[ is syntactically easier to parse even for human as it starts a context. For arithmetical condition, think about using ((.

time for i in {1..1000000}; do [ 'a' = 'b' ] ; done  # 1.990s
time for i in {1..1000000}; do [[ 'a' == 'b' ]] ; done  # 1.371s

time for i in {1..1000000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi ; done  # 3.512s
time for i in {1..1000000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi; done  # 2.143s

strace -cf  bash -c "for i in {1..100000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi  ; done;"  # 399
strace -cf  bash -c "for i in {1..100000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi  ; done;"  # 399

I recommend using [[: If you do not explicitly care about posix compatibility, it means that you are not, so do no care about getting "more" compatible a script that is not.


a
asf107

you can use the double square brackets for light regex matching, e.g. :

if [[ $1 =~ "foo.*bar" ]] ; then

(as long as the version of bash you are using supports this syntax)


Except you've quoted the pattern, so it's now treated as a literal string.
very true. sometimes this annoys me :)
u
user5500105

Bash manual says:

When used with [[, the ‘<’ and ‘>’ operators sort lexicographically using the current locale. The test command uses ASCII ordering.

(The test command is identical to [ ] )