(...)
我正在学习使用 bash 编写 shell 脚本,我需要知道和之间的区别{...}
。编写脚本时如何在两者之间进行选择?
答案1
如果你想让命令列表的副作用影响你的当前的shell,使用{...}
如果你想消除任何副作用,使用(...)
例如,我可能会使用子shell:
- 想要修改
$IFS
一些命令,但不想$IFS
对当前 shell 进行全局修改 cd
但我不想改变$PWD
当前 shell
值得注意的是,在函数定义中可以使用括号:
正常用法:括号:函数体在当前 shell 中执行;函数完成后副作用仍然存在
$ count_tmp() { cd /tmp; files=(*); echo "${#files[@]}"; } $ pwd; count_tmp; pwd /home/jackman 11 /tmp $ echo "${#files[@]}" 11
不寻常的用法:括号:函数体在子 shell 中执行;当子 shell 退出时副作用消失
$ cd ; unset files $ count_tmp() (cd /tmp; files=(*); echo "${#files[@]}") $ pwd; count_tmp; pwd /home/jackman 11 /home/jackman $ echo "${#files[@]}" 0
答案2
来自官方bash 文档:
()
( list )
将命令列表放在括号之间会导致创建子 shell 环境,并且列表中的每个命令都会在该子 shell 中执行。由于列表是在子 shell 中执行的,因此变量赋值在子 shell 完成后不再有效。
{}
{ list; }
将命令列表放在花括号之间会导致列表在当前 shell 上下文中执行。不会创建子 shell。列表后面的分号(或换行符)是必需的。
答案3
'{}' 中的代码在当前线程/进程/环境中执行,更改会保留,更简洁地说,代码在当前范围内运行。
'()' 中的代码在 bash 的一个单独的子进程内运行,该子进程在执行后被丢弃。这个子进程通常被称为子 shell,可以被认为是一个新的、类似子的作用域。
举例来说,请考虑以下内容...
~ # { test_var=test ; }
~ # echo $test_var
test
~ # ( test_var2=test2 )
~ # echo $test_var2
~ #
请注意,在第一个带有“{}”的例子中,即使在结束“}”之后,变量仍然会被设置,而在带有“()”的例子中,变量不会在“()”的范围之外设置。
还要注意 '{}' 和 '()' 语法差异。“;”分隔符是总是‘{}’ 中的代码是必需的,但 ‘()’ 中的代码不是必需的。
答案4
()
除了这里的其他答案之外,我想扩展一下和之间的主要权衡{}
:
- 速度
- 代码可读性
- 线程污染
具体优缺点举例
以下 3 个函数几乎执行完全相同的操作:它们定义一个变量,然后清除它:
funct1(){ local myvar; myvar="boo"; }
- 优点:非常快
- 优点:不会覆盖同名的全局变量
- 缺点:错误风险:此功能中的其他更改(如
cd mydirectory/
)在功能完成后仍然存在 - 反对意见:可读性:要求变量以
local myvar myvar2 ...
- 缺点:错误风险:忘记启动
local
可能会导致覆盖全局变量
funct2(){ myvar="boo"; unset myvar; }
- 优点:非常快(与 funct1 差不多,可能快到可以忽略不计)
- 缺点:错误风险:总是使用相同名称覆盖全局变量
- 缺点:错误风险:此功能中的其他更改(例如
cd mydirectory/
功能完成后仍然存在) - 反对意见:可读性:要求使用以下方式取消设置变量
unset myvar myvar2 ...
- 缺点:错误风险:忘记取消设置变量可能会导致全局变量具有非预期的值
funct3()(myvar="boo")
- 反对意见:执行时间约为 funct1 或 funct2 的 40 到 45 倍
- 优点:不会覆盖同名的全局变量
- 优点:此功能中的其他更改(例如
cd mydirectory/
在功能完成后清除) - 优点:不需要显式的变量清理命令
- 优点:简单且不易出错
基准
您可以使用此脚本来比较这 3 个函数。每个测试运行每个函数 1000 次,测试运行 5 次。之后输出每个函数的平均时间。
#!/bin/bash
funct1(){ local myvar; myvar="boo"; }
funct2(){ myvar="boo"; unset myvar; }
funct3()(myvar="boo")
typeset -A tests
tests=(
[funct1]=0
[funct2]=0
[funct3]=0
)
for n in {0..5}; do
for f in funct1 funct2 funct3; do
start=$(date +%s.%N)
for i in {1..1000}; do $f; done
dur=$(echo "$(date +%s.%N) - $start" | bc)
tests[$f]="$(echo "$(printf "%.6f" "$dur") + ${tests[$f]}"|bc)"
done
done
for i in $(printf "%s\n" "${!tests[@]}"|sort); do
echo "$i average: $(printf "%.6f" "$(echo "${tests[$i]} / 5"|bc -l)")"
done
exit
运行该脚本的示例输出:
funct1 average: 0.014117
funct2 average: 0.012473
funct3 average: 0.558457