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 次 |
最近记录: |