Leo*_*ard 514 syntax bash if-statement
最近在一次代码审查中,一位同事声称该[[ ]]构造比[ ]在类似的构造中更受欢迎
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
Run Code Online (Sandbox Code Playgroud)
他无法提供理由.有吗?
Joh*_*itb 560
[[意外较少,通常使用起来更安全.但它不可移植 - POSIX没有指定它做什么,只有一些shell支持它(除了bash,我听说ksh也支持它).例如,你可以做到
[[ -e $b ]]
Run Code Online (Sandbox Code Playgroud)
测试文件是否存在.但是[,你必须引用$b,因为它分裂了参数并扩展了类似的东西"a*"([[从字面上理解).这也与如何[成为一个外部程序并通常像其他程序一样接收它的参数(尽管它也可以是内置程序,但它仍然没有这种特殊处理).
[[还有一些其他很好的功能,比如正则表达式匹配=~以及类似C语言的运算符.这是一个很好的页面:测试[和[[?之间的区别是什么?和Bash测试
Cir*_*四事件 113
行为差异
关于Bash 4.3.11的一些区别:
POSIX vs Bash扩展:
[ 是POSIX[[是一个Bash扩展¹记录在:https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs常规命令vs魔法
[ 只是一个带有奇怪名称的常规命令.
]只是一个参数,[可以防止使用更多的参数.
Ubuntu 16.04实际上有一个/usr/bin/[coreutils提供的可执行文件,但bash内置版本优先.
Bash解析命令的方式没有任何改变.
具体地讲,<是重定向,&&以及||连接多个命令,( )生成子shell除非逃脱\,和字膨胀发生如常.
[[ X ]]是一个X神奇地解析的单一构造.<,&&,||并()经特殊处理,以及分词规则是不同的.
还有其他差异,如=和=~.
在Bashese:[是一个内置命令,[[是一个关键字:https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword
<
[[ a < b ]]:词典比较[ a \< b ]:与上面相同.\需要或者像任何其他命令一样重定向.Bash扩展.expr a \< b > /dev/null:POSIX等效²,请参阅:如何在Bash中测试字典小于或等于的字典?&& 和 ||
[[ a = a && b = b ]]:真实,逻辑和[ a = a && b = b ]:语法错误,&&解析为AND命令分隔符cmd1 && cmd2 [ a = a -a b = b ]:等效,但由POSIX³弃用[ a = a ] && [ b = b ]:POSIX和可靠的等价物(
[[ (a = a || a = b) && a = b ]]:假的[ ( a = a ) ]:语法错误,()被解释为子shell[ \( a = a -o a = b \) -a a = b ]:等价,但()POSIX弃用{ [ a = a ] || [ a = b ]; } && [ a = b ]POSIX等价5扩展时的单词拆分和文件名生成(split + glob)
x='a b'; [[ $x = 'a b' ]]:是的,不需要报价x='a b'; [ $x = 'a b' ]:语法错误,扩展为 [ a b = 'a b' ]x='*'; [ $x = 'a b' ]:如果当前目录中有多个文件,则出现语法错误.x='a b'; [ "$x" = 'a b' ]:POSIX等价物=
[[ ab = a? ]]:true,因为它确实模式匹配(* ? [很神奇).不会将glob扩展为当前目录中的文件.[ ab = a? ]:a?glob扩展.因此可能是真或假,具体取决于当前目录中的文件.[ ab = a\? ]:false,不是glob扩展=而==在双方的同[和[[,不过==是一个bash扩展.case ab in (a?) echo match; esac:POSIX等价物[[ ab =~ 'ab?' ]]:假4,失去魔力''[[ ab? =~ 'ab?' ]]:是的=~
[[ ab =~ ab? ]]:true,POSIX 扩展正则表达式匹配,?不进行glob扩展[ a =~ a ]:语法错误.没有bash等价物.printf 'ab\n' | grep -Eq 'ab?':POSIX等价物(仅限单行数据)awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?':POSIX等价物.建议:始终使用[].
对于[[ ]]我见过的每个构造,都有POSIX等价物.
如果您使用[[ ]]:
[它只是一个带有奇怪名称的常规命令,不涉及特殊的语义.¹灵感来自[[...]]Korn shell中的等效构造
²但是对于某些值a或b(例如+或index)失败,并进行数字比较,如果a它b看起来像十进制整数.expr "x$a" '<' "x$b"适用于两者.
³并且也失败了某些a或b类似的值!或(.
4在bash 3.2及以上版本中提供与bash 3.1的兼容性未启用(如同BASH_COMPAT=3.1)
5尽管分组(这里与{...;}命令组,而不是(...)这将运行一个不必要的子shell)不是必要的,因为||和&&壳运营商(而不是在||与&& [[...]]运营商或-o/ -a [经营者)具有相同的优先级.所以[ a = a ] || [ a = b ] && [ a = b ]相同.
小智 55
[[ ]]有更多功能 - 我建议您查看Advanced Bash脚本编写指南以获取更多信息,特别是第7章中的扩展测试命令部分.测试.
顺便提一下,正如指南中所述,[[ ]]是在ksh88(1988年版的Korn shell)中引入的.
f3l*_*lix 11
从哪个比较,测试,支架或双支架,是最快的?(http://bashcurescancer.com)
双括号是一个"复合命令",其中test和单个括号是shell内置命令(实际上是相同的命令).因此,单支架和双支架执行不同的代码.
测试和单个括号是最便携的,因为它们作为单独和外部命令存在.但是,如果您使用任何远程现代版本的BASH,则支持双支架.
在标题中明确包含“in Bash”的标记为“bash”的问题中,我对所有说您应该避免的答复感到有些惊讶[[……]]因为它仅适用于 bash!
确实,可移植性是主要的反对意见:如果您想编写一个在 Bourne 兼容的 shell 中工作的 shell 脚本,即使它们不是 bash,您应该避免[[... ]]。(如果你想在更严格的 POSIX shell 中测试你的 shell 脚本,我建议dash;虽然它是一个不完整的 POSIX 实现,因为它缺乏标准所需的国际化支持,它也缺乏对发现的许多非 POSIX 结构的支持在 bash、ksh、zsh 等中)
我看到的另一个反对意见至少适用于 bash 的假设:[[...]]有自己的特殊规则,您必须学习,而[...]就像另一个命令一样。这又是真的(桑蒂利先生带来了显示所有差异的收据),但差异是好是坏是相当主观的。我个人觉得它释放的双支架结构让我用(...)进行分组,&&并||为布尔逻辑,<并>进行比较,并且不带引号的参数扩展。这就像它自己的封闭小世界,在那里表达式更像是在传统的非命令外壳编程语言中的工作方式。
我还没有看到提到的一点是,这种行为[[......]]是的算术扩展结构的完全一致$((...... )),这是由POSIX规定,也允许不带引号的括号和布尔和不平等运营商(其中这里执行数字而不是词法比较)。本质上,任何时候您看到双括号字符时,您都会获得相同的引号屏蔽效果。
(Bash 及其现代亲属也使用((...——))没有前导$——作为 C 风格的for循环头或执行算术运算的环境;这两种语法都不是 POSIX 的一部分。)
所以有一些很好的理由更喜欢[[... ]]; 还有一些理由可以避免它,这可能适用于您的环境,也可能不适用。至于你的同事,“我们的风格指南这么说”是一个有效的理由,就目前而言,但我也会从了解风格指南为什么推荐它的人那里寻找背景故事。
如果您想遵循Google 的风格指南:
测试,[和[[
[[ ... ]]减少错误,因为在[[和之间没有发生路径名扩展或分词]],并[[ ... ]]允许正则表达式匹配[ ... ]没有的地方。
# 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.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
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
Run Code Online (Sandbox Code Playgroud)
无法使用的典型情况[[是在 autotools configure.ac 脚本中。括号具有特殊且不同的含义,因此您必须使用ortest来代替-- 请注意, test 和是同一个程序。[[[[
一位同事最近在代码审查中声称该
[[ ]]构造优于[ ]
......
他无法提供理由。有吗?
是的,速度和效率。
我在这里没有看到“速度”这个词,但由于它[[ ]]是 Bash 内置语法,因此不需要生成新进程。[另一方面,是test命令,运行它会生成一个新进程。因此,[[ ]]应该比语法更快,因为它可以避免每次遇到 a 时生成一个新进程。[ ]test[
是什么让我认为[产生了一个新进程,而不是 Bash 内置的?
好吧,当我运行时which [它告诉我它位于/usr/bin/[. 请注意,这]只是命令的最后一个参数[。
另外,我认为更喜欢它的第二个原因[[ ]]是它的功能更丰富。它支持更多“C 风格”和现代语法。
传统上我更喜欢[ ]over[[ ]]因为它更便携,但最近我想我可能会改用[[ ]]over[ ]因为它更快,而且我写了很多 Bash。
这是我超过 200 万次迭代的结果:[[ ]]比快1.42 倍[ ]:
正在测试的代码非常简单:
if [ "$word1" = "$word2" ]; then
echo "true"
fi
Run Code Online (Sandbox Code Playgroud)
与
if [[ "$word1" == "$word2" ]]; then
echo "true"
fi
Run Code Online (Sandbox Code Playgroud)
其中word1和word2只是以下常量,因此echo "true" 从未运行:
word1="true"
word2="false"
Run Code Online (Sandbox Code Playgroud)
如果您想自己运行测试,代码如下。我已将一个相当复杂的 Python 程序作为定界符字符串嵌入到 Bash 脚本中来进行绘图。
如果将比较字符串常量更改为以下内容:
word1="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetru"
word2="truetruetruetruetruetruetruetruetruetruetruetruetruetruetruetruetrufalse"
Run Code Online (Sandbox Code Playgroud)
...然后您将得到以下结果,其中 Bash 内置 ( [[ ]]) 仅比命令 ( ) 快1.22 倍:test[ ]
在第一种情况下,字符串仅在 1 个字符后不同,因此比较可以立即结束。在后一种情况下,字符串在 67 个字符后有所不同,因此比较需要更长的时间才能识别字符串的不同。我怀疑字符串越长,速度差异越小,因为大部分时间差最初是生成新[进程所需的时间,但随着字符串匹配的时间更长,进程生成时间的影响就不那么重要了。无论如何,这就是我的怀疑。
speed_tests__comparison_with_test_cmd_vs_double_square_bracket_bash_builtin.sh来自我的eRCaGuy_hello_world存储库:
#!/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://stackoverflow.com/a/77270285/4561887
# ==============================================================================
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://stackoverflow.com/a/74800814/4561887
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://stackoverflow.com/a/60157372/4561887
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://stackoverflow.com/a/70662116/4561887
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://stackoverflow.com/a/70662116/4561887
if [ "$__name__" = "__main__" ]; then
main "$@"
fi
Run Code Online (Sandbox Code Playgroud)
示例运行和输出:
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
Run Code Online (Sandbox Code Playgroud)
plot_data()函数在这里,了解如何使用条形图上方的复杂文本制作条形图:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/python/pandas_dataframe_iteration_vs_vectorization_vs_list_compressive_speed_tests.py
| 归档时间: |
|
| 查看次数: |
200363 次 |
| 最近记录: |