在代码审查中,我的同事最近声称在像这样的构造中,[[ ]]
构造比 [ ]
更可取:
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
他无法提供合理的解释。是否存在一个?
在代码审查中,我的同事最近声称在像这样的构造中,[[ ]]
构造比 [ ]
更可取:
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
他无法提供合理的解释。是否存在一个?
[[
相对来说更为安全,使用时不容易出现意外情况。但它并不具备可移植性——POSIX未指定其功能,只有一些shell(除了bash外,据说ksh也支持)支持它。例如,您可以这样做:
[[ -e $b ]]
使用 [
命令来测试文件是否存在。但是在其中,你需要对 $b
进行引用,因为它会分割参数并扩展类似于 "a*"
(这里的 [[
会按照字面意义处理)的东西。这也与 [
可以是一个外部程序并像其他程序一样接收其参数有关(尽管它也可以是内置命令,但它仍然没有这种特殊处理)。
[[
还有一些其他好用的功能,比如使用 =~
进行正则表达式匹配,并且支持类似于 C 语言的运算符。这里有一个很好的页面介绍它:《test、[ 和 [[ 的区别》,以及 Bash 测试的最佳实践:Bash Tests
[[ ]]
但解释为与 [ ]
相同的意思。 - dubiousjim#!/bin/sh
,但一旦我依赖于一些特定于BASH的功能,就会切换到使用 #!/bin/bash
,以表示它不再是Bourne shell可移植的。 - anthony行为差异
Bash 4.3.11 上有一些差异:
POSIX vs Bash扩展:
[
是POSIX[[
是受{{link3:KornShell启发的Bash扩展}}常规命令 vs 魔法
[
只是一个名字奇怪的常规命令。
]
只是[
的最后一个参数。
Ubuntu 16.04实际上在/usr/bin/ [
提供了可执行文件,由coreutils提供,但Bash内置版本优先。
没有改变Bash解析命令的方式。
特别地,<
是重定向,&&
和||
连接多个命令,( )
生成子shell,除非被\
转义,而且单词扩展像平常一样发生。
[[ X ]]
是一个单一的结构,使X
被神奇地解析。 <
,&&
,||
和()
被特殊处理,单词拆分规则也不同。
还有进一步的差异,如=
和=~
。
在Bashese中:[
是内置命令,而[[
是一个关键字:shell builtin和shell keyword之间有什么区别?
<
[[ a < b ]]
: 字典比较[ a \< b ]
: 与上面相同。需要\
,否则会像其他任何命令一样进行重定向。Bash扩展。expr x"$x" \< x"$y" > /dev/null
或[ "$(expr x"$x" \< x"$y")" = 1 ]
: POSIX等效,参见:Bash中如何测试字符串是否按字典顺序小于或等于?&&
和||
[[ 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提供重要的更正和补充。
[[
也许也是一个常规命令)。相关链接:https://askubuntu.com/questions/766270/what-is-posix-compatible-mode-in-linux - Ciro Santilli OurBigBook.com[
相比,[[
的行为非常明智行为之间的怪异行为,然后基于可移植性(这在现今很少是个问题)建议使用[
,这很有趣。人们必须学习bash版本的“繁琐”,更像是你必须学习传统版本的荒谬细节。Bash版本做到了大多数其他编程语言所期望的那样。 - siride[[
的主要缺陷,即当您错误地进行整数比较时,您将不会收到任何错误消息。例如,[[ $n -gt 0 ]]
与["$n" -gt 0]
相比。当n=foo
时,这需要一个大声的错误消息,而您可以通过[
获得它。 - William Pursell[[ ]]
具有更多的功能 - 我建议您查看 高级Bash脚本指南 以获取更多信息,特别是 第7章. 测试 中的 扩展测试命令 部分。
顺便提一下,正如指南所述,[[ ]]
是在 ksh88(KornShell 的1988年版本)中引入的。
如果您遵循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。
[[ -d ~ ]]
返回 true(这意味着 ~
被扩展为 /home/user
)。我认为谷歌在写作时应该更加精确。 - JamesThomasMoon双括号是“复合命令”,而test和单括号是shell内置命令(实际上是相同的命令)。因此,单括号和双括号执行不同的代码。
test和单括号最具可移植性,因为它们存在于单独的外部命令中。然而,如果你使用任何稍微现代一点的BASH版本,那么双括号也是被支持的。
[[
带来的改进不是很在意。但是,我是一个老派的人 :-) - Jens[
和test
的内置版本,尽管外部版本也存在。 - dubiousjimPS1=...crazy stuff...
和/或 $PROMPT_COMMAND
);对于这些,我不希望在执行脚本时有任何_可感知的延迟_。 - michaelapt-get update
。当我们可以将可移植性从已经太长的约束列表中剔除时,这是一种巨大的解脱。 - Ron Burk[[
...]]
,因为它只能在bash中工作![[
...]]
。(如果您想在更严格的POSIX shell中测试您的shell脚本,我建议使用dash
;尽管它是一个不完整的POSIX实现,因为它缺少标准所需的国际化支持,但它也缺少大多数非POSIX构造,在bash、ksh、zsh等中找到)。我所看到的另一个反对意见至少适用于bash的假设:即[[
...]]
有其自己特殊的规则,你必须学习,而[
...]
则像另一个命令一样运作。这是真的(Santilli先生提供了显示所有差异的收据),但差异是好还是坏相当主观。我个人认为,双方括号结构使我可以使用(
...)
进行分组,&&
和||
进行布尔逻辑,<
和>
进行比较,以及未引用的参数展开。它就像是一个封闭的小世界,在这里表达式的工作方式更像是传统的非命令行编程语言。[[
...]]
的这种行为完全符合算术展开结构$((
...))
的行为,该结构由POSIX指定,并且也允许未引用的括号和布尔和不等运算符(这里执行数字而不是词法比较)。基本上,任何时候你看到双括号字符,你都会得到相同的引号屏蔽效果。(Bash及其现代衍生版本也使用((
...))
- 没有前导的$
- 作为C风格的for
循环标头或用于执行算术操作的环境;这两种语法都不是POSIX的一部分。)[[
...]]
;同时也有一些应该避免它的理由,这些理由可能适用于你的环境,也可能不适用。关于你的同事,"我们的样式指南这样说" 是一个有效的辩解,但我还会向了解样式指南为什么推荐这样做的人寻求背景信息。一个典型的情况是在autotools configure.ac脚本中,您不能使用 [[
。这些方括号有特殊且不同的含义,因此您必须使用 test
而不是 [
或 [[
-- 注意,test和 [
是相同的程序。
[
被定义为POSIX shell函数呢? - Gordon[[ ]]
结构优于[ ]
。[[ ]]
是Bash内置语法,它不需要生成新的进程。[
则是test
命令,运行它会生成一个新的进程。所以,[[ ]]
语法应该比[ ]
语法更快,因为它避免了每次遇到[
时生成一个新的test
进程。[
会生成一个新的进程而不是Bash内置的呢?
嗯,当我运行which [
时,它告诉我它位于/usr/bin/[
。注意,]
只是[
命令的最后一个参数。[[ ]]
更加功能丰富。[ ]
而不是[[ ]]
,因为它更加“便携”,但最近我考虑改用[[ ]]
而不是[ ]
,因为它更“快”,而且我经常写Bash脚本。[[ ]]
比[ ]
快1.42倍。
被测试的代码非常简单:if [ "$word1" = "$word2" ]; then
echo "true"
fi
if [[ "$word1" == "$word2" ]]; then
echo "true"
fi
其中word1
和word2
只是以下常量,所以echo "true"
从未执行:
word1="true"
word2="false"
word1="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetru"
word2="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetrufalse"
[
进程所需的时间,但随着字符串匹配的时间越长,进程生成时间的影响就越小。这只是我的猜想。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
使用[[ ]]
有一个重要的注意事项,考虑以下内容:
$ # 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[[ foo -lt 5 ]]
的行为与 [ foo -lt 5 ]
不同。前者返回0,而后者返回非零值。在我看来,将非整数(如字符串)解释为0是一个致命的缺陷。 - William Pursell[[ ]]
可以做到[ ]
所不能做的事情,因此人们可能仍然想使用它,尽管存在这个缺陷。此外,它确实会返回错误代码'1'。 - arkan[
和 [[
表现不同的示例。 - William Pursell双方括号[[ ]]在某些版本的SunOS下不受支持,并且在函数声明内完全不受支持,如:
GNU Bash,版本2.02.0(1)-release(sparc-sun-solaris2.6)
[[
,代码看起来很好很清晰,但请记住,有一天你可能需要在默认shell不是bash
或ksh
等的系统上移植脚本。[
虽然难看且笨重,但在任何情况下都像AK-47一样有效。 - rook