在Bash中,双方括号[[ ]]比单方括号[ ]更可取吗?

904

在代码审查中,我的同事最近声称在像这样的构造中,[[ ]] 构造比 [ ] 更可取:

if [ "`id -nu`" = "$someuser" ] ; then
     echo "I love you madly, $someuser"
fi

他无法提供合理的解释。是否存在一个?


33
灵活一点,有时候允许自己听取建议而不要求深入解释 :) 至于使用[[,代码看起来很好很清晰,但请记住,有一天你可能需要在默认shell不是bashksh等的系统上移植脚本。[虽然难看且笨重,但在任何情况下都像AK-47一样有效。 - rook
401
@rook,你可以听取一个没有详细解释的建议,没问题。但是当你要求解释时却没有得到解释,通常这是一个警示信号。毕竟“信任,但核实”嘛。 - Josip Rodin
19
请参见Unix & Linux SE上的[什么是Bash运算符[[ vs vs ( vs ((? - codeforester
30
换句话说,就是“照着指示去做,不要问为什么”。 - 111
30
@rook说的话毫无意义。 - user2453382
显示剩余3条评论
11个回答

860

[[相对来说更为安全,使用时不容易出现意外情况。但它并不具备可移植性——POSIX未指定其功能,只有一些shell(除了bash外,据说ksh也支持)支持它。例如,您可以这样做:

[[ -e $b ]]

使用 [ 命令来测试文件是否存在。但是在其中,你需要对 $b 进行引用,因为它会分割参数并扩展类似于 "a*"(这里的 [[ 会按照字面意义处理)的东西。这也与 [ 可以是一个外部程序并像其他程序一样接收其参数有关(尽管它也可以是内置命令,但它仍然没有这种特殊处理)。

[[ 还有一些其他好用的功能,比如使用 =~ 进行正则表达式匹配,并且支持类似于 C 语言的运算符。这里有一个很好的页面介绍它:《test、[ 和 [[ 的区别》,以及 Bash 测试的最佳实践:Bash Tests


42
考虑到如今 Bash 无所不在,我认为它非常便携。唯一普遍的例外是在 BusyBox 平台上 - 不过考虑到运行它的硬件,你可能需要做出特别的努力来适配它。请注意,此处的“特别努力”指的是额外的配置和适当的测试,以确保脚本在 BusyBox 上能够正常运行。 - guns
30
@guns: 的确。我建议你的第二句话推翻了你的第一句话;如果你把软件开发作为一个整体看待,“bash 到处都是”和“busybox 平台是例外”是完全不相容的。busybox 在嵌入式开发中非常流行。 - Lightness Races in Orbit
15
即使我的计算机上安装了Bash,也可能无法执行脚本(我可能已将/bin/sh符号链接到某个dash变体上,这是在FreeBSD和Ubuntu上的标准配置)。对于Busybox还存在其他奇怪的问题:在现在的标准编译选项下,它可以解析 [[ ]] 但解释为与 [ ] 相同的意思。 - dubiousjim
84
如果你使用纯Bash语法(如此例),你应该在脚本开头使用 #!/bin/bash 而不是 #!/bin/sh。 - Nick Matteo
26
因此,我会在我的脚本中使用 #!/bin/sh,但一旦我依赖于一些特定于BASH的功能,就会切换到使用 #!/bin/bash,以表示它不再是Bourne shell可移植的。 - anthony
显示剩余7条评论

396

行为差异

Bash 4.3.11 上有一些差异:

  • POSIX vs Bash扩展:

  • 常规命令 vs 魔法

    • [只是一个名字奇怪的常规命令。

      ]只是[的最后一个参数。

      Ubuntu 16.04实际上在/usr/bin/ [提供了可执行文件,由coreutils提供,但Bash内置版本优先。

      没有改变Bash解析命令的方式。

      特别地,<是重定向,&&||连接多个命令,( )生成子shell,除非被\转义,而且单词扩展像平常一样发生。

    • [[ X ]]是一个单一的结构,使X被神奇地解析。 <&&||()被特殊处理,单词拆分规则也不同。

      还有进一步的差异,如==~

    在Bashese中:[是内置命令,而[[是一个关键字:shell builtin和shell keyword之间有什么区别?

  • <

  • &&||

    • [[ a = a && b = b ]]: true,逻辑and
    • [ a = a && b = b ]: 语法错误,&&被解析为AND命令分隔符cmd1 && cmd2
    • [ a = a ] && [ b = b ]: POSIX可靠等效
    • [ a = a -a b = b ]: 几乎等效,但被POSIX弃用,因为它是疯狂的,并且对于一些值如(无法运行,这将被解释为逻辑操作
  • (

    • [[ (a = a || a = b) && a = b ]]: false。如果没有( ),它将是true,因为[[ && ]]的优先级高于建议:始终使用[]

      我看到的每个[[ ]]结构都有POSIX等效项。

      如果你使用[[ ]],你会:

      • 失去可移植性
      • 强制读者学习另一种Bash扩展的复杂性。[只是一个带有奇怪名称的常规命令,并且没有涉及任何特殊语义。

      感谢Stéphane Chazelas提供重要的更正和补充。


2
这个说法是否正确:“在 POSIX 中可行的一切都可以在 BASH 中运行,但反之则不然。”? - Wlad
2
@Wlad Bash极大地扩展了POSIX,因此任何Bash扩展都不会是POSIX。另一方面,我不确定是否完全正确,但感觉很有可能(除非Bash扩展覆盖了POSIX语法,例如在posix中[[也许也是一个常规命令)。相关链接:https://askubuntu.com/questions/766270/what-is-posix-compatible-mode-in-linux - Ciro Santilli OurBigBook.com
1
Bash的独特特性应该是使用它们的原因。如果你在编写POSIX shell,那么可以使用任何东西。我编写bash脚本,并尝试使用语言的所有合理特性。是的,它们不可移植,但这没关系,Bash并不难掌握。特别是使用[[ ]]在许多方面更安全、更可靠。 - PePa
15
阅读所有与[相比,[[的行为非常明智行为之间的怪异行为,然后基于可移植性(这在现今很少是个问题)建议使用[,这很有趣。人们必须学习bash版本的“繁琐”,更像是你必须学习传统版本的荒谬细节。Bash版本做到了大多数其他编程语言所期望的那样。 - siride
2
这是一个很好的答案!我想补充一下[[的主要缺陷,即当您错误地进行整数比较时,您将不会收到任何错误消息。例如,[[ $n -gt 0 ]]["$n" -gt 0]相比。当n=foo时,这需要一个大声的错误消息,而您可以通过[获得它。 - William Pursell
显示剩余6条评论

71

8
这并不是批判主义,它最初是在Korn shell中引入的。 - Henk Langeveld
11
@Thomas,在许多领域中,ABS实际上被认为是一个很差的参考;虽然它有大量准确的信息,但它往往很少注意避免在其示例中展示不良实践,并且在其生命周期中花费了大量时间未得到维护。 - Charles Duffy
2
@CharlesDuffy感谢您的评论,您能否推荐一个好的替代品。我不是shell脚本方面的专家,我正在寻找一份指南,以便每半年写一次脚本时可以参考。 - Thomas
13
@Thomas,我使用的是Wooledge BashFAQ和相关的维基页面;它们由Freenode #bash频道的居民积极维护(尽管有时候会很敏感,但通常对正确性非常关注)。BashFAQ #31,http://mywiki.wooledge.org/BashFAQ/031,是与这个问题直接相关的页面。 - Charles Duffy

23

如果您遵循Google的样式指南

测试,[ … ][[ … ]]

[[ … ]]优先于[ … ]test/usr/bin/[

[[ … ]]减少了错误,因为在[[]]之间不会进行路径名展开或单词分割。此外,[[ … ]]允许使用正则表达式匹配,而[ … ]则不允许。

# This ensures the string on the left is made up of characters in
# the alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi
# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

要了解详细信息,请参见http://tiswww.case.edu/php/chet/bash/FAQ中的E14。


3
谷歌写道“_不会发生路径名扩展...”,但是 [[ -d ~ ]] 返回 true(这意味着 ~ 被扩展为 /home/user)。我认为谷歌在写作时应该更加精确。 - JamesThomasMoon
2
@JamesThomasMoon1979 这是波浪线扩展,而不是谷歌文本中提到的路径名扩展。 - maoizm
不需要在[[ ]]中的“filename”周围加引号。 - PePa

23

来自 哪种比较器更快,test、单括号或双括号?:

双括号是“复合命令”,而test和单括号是shell内置命令(实际上是相同的命令)。因此,单括号和双括号执行不同的代码。

test和单括号最具可移植性,因为它们存在于单独的外部命令中。然而,如果你使用任何稍微现代一点的BASH版本,那么双括号也是被支持的。


11
在 shell 脚本中,为什么会如此追求速度?我更希望它的可移植性,并且对 [[ 带来的改进不是很在意。但是,我是一个老派的人 :-) - Jens
1
我认为许多shell都证明了[test的内置版本,尽管外部版本也存在。 - dubiousjim
4
一般而言,我同意Jens的观点:脚本的整个目的是(过去是?)可移植性(否则,我们需要编写和编译而不是脚本)... 我能想到的两个例外是:(1)制表符补全(其中补全脚本可能会变得非常长,具有很多条件逻辑);和(2)超级提示符(PS1=...crazy stuff... 和/或 $PROMPT_COMMAND);对于这些,我不希望在执行脚本时有任何_可感知的延迟_。 - michael
2
一些对速度的追求只是风格问题。其他条件相同的情况下,为什么不将更高效的代码结构纳入您的默认样式中,特别是如果该结构还提供更好的可读性呢?至于可移植性,许多适合bash的任务本质上是不可移植的。例如,如果距离上次运行已经超过X个小时,我需要运行apt-get update。当我们可以将可移植性从已经太长的约束列表中剔除时,这是一种巨大的解脱。 - Ron Burk
@Jens和其他人,这是我刚刚在[[[之间做的一些速度测试。我知道你说你不在乎,但这很有趣,有时很重要。 - undefined

21
在一个标记了“bash”的问题中,标题中明确提到“在Bash中”,我有点惊讶于所有回答都说你应该避免使用[[...]],因为它只能在bash中工作!
确实,可移植性是主要的反对理由:如果您想编写一个Shell脚本,即使它们不是bash也可以在Bourne兼容的shell中工作,您应该避免使用[[...]]。(如果您想在更严格的POSIX shell中测试您的shell脚本,我建议使用dash;尽管它是一个不完整的POSIX实现,因为它缺少标准所需的国际化支持,但它也缺少大多数非POSIX构造,在bash、ksh、zsh等中找到)。我所看到的另一个反对意见至少适用于bash的假设:即[[...]]有其自己特殊的规则,你必须学习,而[...]则像另一个命令一样运作。这是真的(Santilli先生提供了显示所有差异的收据),但差异是好还是坏相当主观。我个人认为,双方括号结构使我可以使用(...)进行分组,&&||进行布尔逻辑,<>进行比较,以及未引用的参数展开。它就像是一个封闭的小世界,在这里表达式的工作方式更像是传统的非命令行编程语言。
我没有看到的一点是,[[...]]的这种行为完全符合算术展开结构$((...))的行为,该结构由POSIX指定,并且也允许未引用的括号和布尔和不等运算符(这里执行数字而不是词法比较)。基本上,任何时候你看到双括号字符,你都会得到相同的引号屏蔽效果。(Bash及其现代衍生版本也使用((...)) - 没有前导的$ - 作为C风格的for循环标头或用于执行算术操作的环境;这两种语法都不是POSIX的一部分。)
因此,有一些很好的理由支持使用[[...]];同时也有一些应该避免它的理由,这些理由可能适用于你的环境,也可能不适用。关于你的同事,"我们的样式指南这样说" 是一个有效的辩解,但我还会向了解样式指南为什么推荐这样做的人寻求背景信息。

6

一个典型的情况是在autotools configure.ac脚本中,您不能使用 [[。这些方括号有特殊且不同的含义,因此您必须使用 test 而不是 [[[ -- 注意,test和 [ 是相同的程序。


3
既然Autotools不是POSIX shell,那你为什么会期望[被定义为POSIX shell函数呢? - Gordon
因为autoconf脚本看起来像是一个shell脚本,并且它会生成一个shell脚本,而大多数shell命令都在其中运行。 - vy32

3
最近在代码审查中,一位同事声称[[ ]]结构优于[ ]
...
他无法提供理由。是否有理由呢?
是的,速度和效率。
我在这里没有看到提到“速度”这个词,但由于[[ ]]是Bash内置语法,它不需要生成新的进程。[则是test命令,运行它会生成一个新的进程。所以,[[ ]]语法应该比[ ]语法更快,因为它避免了每次遇到[时生成一个新的test进程。
是什么让我认为[会生成一个新的进程而不是Bash内置的呢? 嗯,当我运行which [时,它告诉我它位于/usr/bin/[。注意,]只是[命令的最后一个参数。
此外,我认为[[ ]]更加功能丰富。
我一直更喜欢[ ]而不是[[ ]],因为它更加“便携”,但最近我考虑改用[[ ]]而不是[ ],因为它更“快”,而且我经常写Bash脚本。
速度测试结果(越“低”越好):
以下是我在200万次迭代中的结果:[[ ]][ ]1.42倍

enter image description here

被测试的代码非常简单:
if [ "$word1" = "$word2" ]; then
    echo "true"
fi

vs.
if [[ "$word1" == "$word2" ]]; then
    echo "true"
fi

其中word1word2只是以下常量,所以echo "true"从未执行:

word1="true"
word2="false"

如果你想自己运行测试,下面是代码。我将一个相当复杂的Python程序嵌入到Bash脚本中作为heredoc字符串,用于绘图。
如果你将比较字符串常量改为以下内容:
word1="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetru"
word2="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetrufalse"

...然后你会得到这些结果,其中Bash内置命令([[ ]])只比test命令([ ])快1.22倍

enter image description here

在第一种情况下,字符串仅在1个字符后就不同,因此比较可以立即结束。在后一种情况下,字符串在67个字符后才不同,因此比较需要更长的时间来确定字符串的不同之处。我怀疑字符串越长,速度差异就越小,因为时间差异的主要部分最初是生成新的[进程所需的时间,但随着字符串匹配的时间越长,进程生成时间的影响就越小。这只是我的猜想。
来自我的eRCaGuy_hello_world存储库的speed_tests__comparison_with_test_cmd_vs_double_square_bracket_bash_builtin.sh
#!/usr/bin/env bash

# This file is part of eRCaGuy_hello_world: https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world

# ==============================================================================
# Python plotting program
# - is a Bash heredoc
# References:
# 1. My `plot_data()` function here:
#    https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/python/pandas_dataframe_iteration_vs_vectorization_vs_list_comprehension_speed_tests.py
# 1. See my answer here: https://dev59.com/h2Qn5IYBdhLWcg3w5qlg#77270285
# ==============================================================================
python_plotting_program=$(cat <<'PROGRAM_END'

# 3rd-party imports
import matplotlib.pyplot as plt
import pandas as pd

# standard imports
import os
import sys

assert sys.argv[0] == "-c"
# print(f"sys.argv = {sys.argv}")  # debugging

# Get the command-line arguments
FULL_PATH_TO_SCRIPT = sys.argv[1]
NUM_ITERATIONS = int(sys.argv[2])
single_bracket_sec = float(sys.argv[3])
double_bracket_sec = float(sys.argv[4])

# Obtain paths to help save the plot later.
# See my answer: https://dev59.com/T3VD5IYBdhLWcg3wNY1Z#74800814
SCRIPT_DIRECTORY = str(os.path.dirname(FULL_PATH_TO_SCRIPT))
FILENAME = str(os.path.basename(FULL_PATH_TO_SCRIPT))
FILENAME_NO_EXTENSION = os.path.splitext(FILENAME)[0]

# place into lists
labels = ['`[ ]` `test` func', '`[[ ]]` Bash built-in']
data = [single_bracket_sec, double_bracket_sec]

# place into a Pandas dataframe for easy manipulation and plotting
df = pd.DataFrame({'test_type': labels, 'time_sec': data})
df = df.sort_values(by="time_sec", axis='rows', ascending=False)
df = df.reset_index(drop=True)

# plot the data
fig = plt.figure()
plt.bar(labels, data)
plt.title(f"Speed Test: `[ ]` vs `[[ ]]` over {NUM_ITERATIONS:,} iterations")
plt.xlabel('Test Type', labelpad=8)  # use `labelpad` to lower the label
plt.ylabel('Time (sec)')

# Prepare to add text labels to each bar
df["text_x"] = df.index # use the indices as the x-positions
df["text_y"] = df["time_sec"] + 0.06*df["time_sec"].max()
df["time_multiplier"] = df["time_sec"] / df["time_sec"].min()
df["text_label"] = (df["time_sec"].map("{:.4f} sec\n".format) +
                    df["time_multiplier"].map("{:.2f}x".format))

# Use a list comprehension to actually call `plt.text()` to **automatically add
# a plot label** for each row in the dataframe
[
    plt.text(
        text_x,
        text_y,
        text_label,
        horizontalalignment='center',
        verticalalignment='center'
    ) for text_x, text_y, text_label
    in zip(
        df["text_x"],
        df["text_y"],
        df["text_label"]
    )
]

# add 10% to the top of the y-axis to leave space for labels
ymin, ymax = plt.ylim()
plt.ylim(ymin, ymax*1.1)

plt.savefig(f"{SCRIPT_DIRECTORY}/{FILENAME_NO_EXTENSION}.svg")
plt.savefig(f"{SCRIPT_DIRECTORY}/{FILENAME_NO_EXTENSION}.png")

plt.show()

PROGRAM_END
)

# ==============================================================================
# Bash speed test program
# ==============================================================================

# See my answer: https://dev59.com/yXVD5IYBdhLWcg3wL4cA#60157372
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"

NUM_ITERATIONS="2000000" # 2 million
# NUM_ITERATIONS="1000000" # 1 million
# NUM_ITERATIONS="10000" # 10k

word1="true"
word2="false"

# Get an absolute timestamp in floating point seconds.
# From:
# https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/timestamp_lib_WIP.sh
seconds_float() {
    time_sec="$(date +"%s.%N")"
    echo "$time_sec"
}

single_bracket() {
    for i in $(seq 1 "$NUM_ITERATIONS"); do
        if [ "$word1" = "$word2" ]; then
            echo "true"
        fi
    done
}

double_bracket() {
    for i in $(seq 1 "$NUM_ITERATIONS"); do
        if [[ "$word1" == "$word2" ]]; then
            echo "true"
        fi
    done
}

run_and_time_function() {
    # the 1st arg is the function to run
    func_to_time="$1"

    # NB: the "information" type prints will go to stderr so they don't
    # interfere with the actual timing results printed to stdout.

    echo -e "== $func_to_time time test start... ==" >&2  # to stderr
    time_start="$(seconds_float)"

    $func_to_time

    time_end="$(seconds_float)"
    elapsed_time="$(bc <<< "scale=20; $time_end - $time_start")"
    echo "== $func_to_time time test end. ==" >&2  # to stderr
    echo "$elapsed_time"  # to stdout
}

main() {
    echo "Running speed tests over $NUM_ITERATIONS iterations."

    single_bracket_time_sec="$(run_and_time_function "single_bracket")"
    double_bracket_time_sec="$(run_and_time_function "double_bracket")"

    echo "single_bracket_time_sec = $single_bracket_time_sec"
    echo "double_bracket_time_sec = $double_bracket_time_sec"

    # echo "Plotting the results in Python..."
    python3 -c "$python_plotting_program" \
        "$FULL_PATH_TO_SCRIPT" \
        "$NUM_ITERATIONS" \
        "$single_bracket_time_sec" \
        "$double_bracket_time_sec"
}

# Determine if the script is being sourced or executed (run).
# See:
# 1. "eRCaGuy_hello_world/bash/if__name__==__main___check_if_sourced_or_executed_best.sh"
# 1. My answer: https://dev59.com/rV0a5IYBdhLWcg3wxbHm#70662116
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    # This script is being run.
    __name__="__main__"
else
    # This script is being sourced.
    __name__="__source__"
fi

# Only run `main` if this script is being **run**, NOT sourced (imported).
# - See my answer: https://dev59.com/rV0a5IYBdhLWcg3wxbHm#70662116
if [ "$__name__" = "__main__" ]; then
    main "$@"
fi

示例运行和输出:

eRCaGuy_hello_world$ bash/speed_tests__comparison_with_test_cmd_vs_double_square_bracket_bash_builtin.sh
Running speed tests over 2000000 iterations.
== single_bracket time test start... ==
== single_bracket time test end. ==
== double_bracket time test start... ==
== double_bracket time test end. ==
single_bracket_time_sec = 5.990248014
double_bracket_time_sec = 4.230342635

参考文献

我的plot_data()函数在这里,用于如何制作带有复杂文本的条形图:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/python/pandas_dataframe_iteration_vs_vectorization_vs_list_comprehension_speed_tests.py
  1. 我的答案是这段代码:如何在Pandas中迭代DataFrame的行
  • 我的答案是:如何获取当前执行的Python文件的路径和名称?
  • 我的答案是:如何获取正在运行或被引用的任何脚本的完整文件路径、完整目录和基本文件名...
  • 我的Bash库:获取浮点秒的绝对时间戳:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/timestamp_lib_WIP.sh
  • 如何创建一个heredoc:https://linuxize.com/post/bash-heredoc/
  • 另请参阅

    1. 这个答案,链接到另一个超级简单的速度测试

    2

    使用[[ ]]有一个重要的注意事项,考虑以下内容:

    $ # For integer like strings, [ and [[ behave the same
    $ 
    $ n=5 # set n to a string that represents an integer
    $ [[ $n -ge 0 ]] && printf "\t> $n is non-negative\n"
            > 5 is non-negative
    $ [ "$n" -ge 0 ] && printf "\t> $n is non-negative\n"
            > 5 is non-negative
    $ 
    $ # But the behavior is different in several cases:
    $ n=something # set n to some non-numeric string
    $ [ "$n" -ge 0 ] && printf "\t> $n is non-negative\n"
    -bash: [: something: integer expression expected
    $ [[ $n -ge 0 ]] && printf "\t> $n is non-negative\n"
            > something is non-negative
    $ n=5foo
    $ [[ $n -ge 0 ]] && printf "\t> $n is non-negative\n"
    -bash: 5foo: value too great for base (error token is "5foo")
    

    为了澄清[[的行为不一致性,请考虑:
    $ for n in 5foo 5.2 something; do [[ $n -ge 0 ]] && echo ok; done
    -bash: 5foo: value too great for base (error token is "5foo")
    -bash: 5.2: syntax error: invalid arithmetic operator (error token is ".2")
    ok
    $ for n in 5foo 5.2 something; do [ "$n" -ge 0 ] && echo ok; done
    -bash: [: 5foo: integer expression expected
    -bash: [: 5.2: integer expression expected
    -bash: [: something: integer expression expected
    

    将一些非数字字符串解释为0以及当$n不是有效整数时,行[[ $n -ge 0 ]]产生的不一致的错误消息(或完全没有错误消息!)使得[[在整数比较方面使用不安全。出于这个原因,我强烈建议不要使用[[进行数字比较。


    [[ $n -gt 0 ]][ $n -gt 0 ] 是严格等价的,不应该返回错误信息。它们都返回一个退出码(如果条件为真则为0,如果条件为假则为1)。我不确定你想提出什么问题。 - arkan
    如果 $n 扩展为字符串 foo,那么 [ $n -gt 0 ] 应该生成一个类似于 invalid integer ‘foo’ 的错误消息。这是一个非常有用的错误消息,[[ 不提供此功能。 - William Pursell
    @arkan 但也许更重要的是,[[ foo -lt 5 ]] 的行为与 [ foo -lt 5 ] 不同。前者返回0,而后者返回非零值。在我看来,将非整数(如字符串)解释为0是一个致命的缺陷。 - William Pursell
    谢谢您的澄清,我提交了一份编辑,应该可以避免像我这样的人错过您的观点。在我看来,“致命缺陷”或“无法使用”有点过头了(特别是在谈论bash这样一个传统的堆积式编程语言时)。[[ ]]可以做到[ ]所不能做的事情,因此人们可能仍然想使用它,尽管存在这个缺陷。此外,它确实会返回错误代码'1'。 - arkan
    @arkan 感谢您的编辑。我做了一些更改,希望能进一步澄清。在原始示例中,您的评论“它确实返回错误代码'1'”是正确的,但表明您错过了一个重要细节,因此我已将示例更改为其中 [[[ 表现不同的示例。 - William Pursell

    0

    双方括号[[ ]]在某些版本的SunOS下不受支持,并且在函数声明内完全不受支持,如:

    GNU Bash,版本2.02.0(1)-release(sparc-sun-solaris2.6)


    1
    非常真实,且一点也不无关紧要。必须考虑在旧版本之间的bash可移植性。人们说“bash是无处不在和可移植的,除了可能_(插入奇怪的操作系统名称)_” - 但根据我的经验,solaris是其中一个需要特别注意可移植性的平台:不仅要考虑在新操作系统上使用旧版bash时出现的问题/错误,如数组、函数等;而且即使像tr、sed、awk、tar这样的脚本中使用的工具,在solaris上也有奇怪的特殊情况和独特性需要解决。 - michael
    2
    你说得太对了...在Solaris上有太多非POSIX的实用程序,只需看一下“df”输出和参数...Sun真是可耻。希望它会逐渐消失(除了加拿大)。 - scavenger
    12
    Solaris 2.6 是认真的吗?它于1997年发布,支持在2006年结束。我猜如果您仍在使用它,那么您肯定有其他问题!顺便说一句,它使用的是 Bash v2.02,这是引入双括号的版本,因此即使在如此老旧的系统上也应该能够工作。2005年发布的 Solaris 10 使用的是 Bash 3.2.51,而2011年发布的 Solaris 11 则使用的是 Bash 4.1.11。 - peterh
    1
    关于脚本可移植性。在Linux系统上,通常只有每个工具的一个版本,即GNU版本。在Solaris上,您通常可以选择使用Solaris本地版本或GNU版本(例如Solaris tar vs GNU tar)。如果您依赖于GNU特定扩展,则必须在脚本中声明才能实现可移植性。在Solaris上,您可以通过添加前缀"g"来实现,例如如果您想要使用GNU grep,则需要使用'ggrep'。 - peterh

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接