我想暂时更改shopt
函数中某个选项的值。
对于可以使用内置函数设置的选项,set
可以local -
在函数体内进行设置。下面是示例代码
#!/bin/bash
# Enable the options globally
set -u
[[ "$SHELLOPTS" =~ nounset ]] && echo "1: nounset enabled"
shopt -s "extglob"
[[ "$BASHOPTS" =~ extglob ]] && echo "1: extglob enabled"
fun () {
#Enable the options locally just in fun
local -
# local BASHOPTS
set +u
[[ "$SHELLOPTS" =~ nounset ]] || echo "2: nounset disabled"
shopt -u "extglob"
[[ "$BASHOPTS" =~ extglob ]] || echo "2: extglob disabled"
}
fun
if [[ "$SHELLOPTS" =~ nounset ]]; then
echo "3: nounset enabled"
else
echo "3: nounset disabled"
fi
if [[ "$BASHOPTS" =~ extglob ]]; then
echo "3: extglob enabled"
else
echo "3: extglob disabled"
fi
与输出
1: nounset enabled
1: extglob enabled
2: nounset disabled
2: extglob disabled
3: nounset enabled
3: extglob disabled
可以看出,该选项只需添加到函数体nounset
即可本地化。local -
我想要同样的shopt
选项。我当前的解决方法是首先检查是否启用了某个选项
if ! shopt -q extglob; then
extglobchanged=1
shopt -s extglob
fi
然后最后把它改回之前的样子。
[[ "$extglobchanged" == 1 ]] && shopt -u extglob
这不是一个好的解决方案,因为我必须处理中间代码可能失败的每种情况(我必须确保上面的行在函数退出之前执行)。
问题:
是否可以本地化shopt
选项?
--编辑1--
动机
我的 bash 工具包中有一个函数可以查询用户是或否。我用于case
此目的,并使用扩展的 glob 来将模式与 or 链接起来。这是我的代码
yesnoquery () {
local extglobchanged=0
local returnvalue=2
while true; do
if read -p "$1" answer; then
if ! shopt -q extglob; then
extglobchanged=1
shopt -s extglob #enable for pattern matching
fi
eval '
case "$answer" in
@([Yy]|[Yy][Ee][Ss])) returnvalue=0;; #true
@([Nn]|[Nn][Oo])) returnvalue=1;; #false
* ) echo "Please answer \"y\" or \"n\"";;
esac
'
[[ "$extglobchanged" == 1 ]] && shopt -u extglob #revert changes
[[ "$returnvalue" != 2 ]] && return "$returnvalue"
else
return 2 #reading returned error
fi
done
}
对于这样一个琐碎的任务来说,看起来很漫长。如果我可以本地化shopt
选项 extglob,那么我可以直接从case
like中返回@([Yy]|[Yy][Ee][Ss])) return 0;;
。此外,我不需要这[[ "$extglobchanged" == 1 ]]...
条线和之后的线。
主要问题
目前,我必须在函数可以退出的每个位置手动添加代码以将我的更改恢复到shopt
.
我的想法是,我需要本地化这些更改,就像我可以使用local -
.没有本地化的不同解决方案就完美了。
如果这只是语言限制,那么这也回答了我的问题。
答案1
对的,这是可能的:
fun()
{
local -
# store state of all options.
oldstate="$(shopt -p)"
:
:
set +vx; eval "$oldstate"
}
有一种特殊情况errexit
。
答案2
我想我找到了一个适合我需求的解决方案在子 shell 中执行函数体。在子shell中执行函数体的优点和缺点,以及典型的用例,已经在这里讨论了很多次[1][2][3]。
如中所述[4]取自 bashman
页面,函数的一般语法是
[ function ] name () compound-command [redirection]
ACompound comamand
可以是当前 shell 的子进程,使用圆括号语法启动(list)
。man bash
给出以下定义(自己强调):
(list) list 在子 shell 环境中执行(请参阅下面的命令执行环境)。变量赋值和内置命令影响 shell 的环境命令完成后不再有效。返回状态是list的退出状态。
对于我的用例,我们需要两件事:
shopt
选项更改的本地化效果extglob
。- 保留退货状态
由于子 shell 对shopt
选项的更改extglob
不会泄漏到周围的 shell 中。此外,我们可以在函数的任何点返回。当然,缺点是启动子 shell 会花费额外的资源。
示范
现在更短的函数如下所示:
yesnoquery () (
while true; do
if read -p "$1" answer; then
shopt -s extglob
eval '
case "$answer" in
@([Yy]|[Yy][Ee][Ss]) ) return 0;; #true
@([Nn]|[Nn][Oo]) ) return 1;; #false
* ) echo "Please answer \"yes\" or \"no\"";;
esac
'
else
return 2 #reading returned error
fi
done
)
在 case 语句中,我们现在可以直接返回,而不是保存到临时变量。不再需要本地化变量,并且已保存两个条件。
shopt
通过问题中代码的示例代码可以很快看出这些选项确实是本地的
#!/bin/bash
# Enable the options globally
set -u
[[ "$SHELLOPTS" =~ nounset ]] && echo "1: nounset enabled"
shopt -s "extglob"
[[ "$BASHOPTS" =~ extglob ]] && echo "1: extglob enabled"
fun () {
set +u
[[ "$SHELLOPTS" =~ nounset ]] || echo "2: nounset disabled"
shopt -u "extglob"
[[ "$BASHOPTS" =~ extglob ]] || echo "2: extglob disabled"
}
fun
if [[ "$SHELLOPTS" =~ nounset ]]; then
echo "3: nounset enabled"
else
echo "3: nounset disabled"
fi
if [[ "$BASHOPTS" =~ extglob ]]; then
echo "3: extglob enabled"
else
echo "3: extglob disabled"
fi
所更改的只是此处的括号(
({
并删除了多余的局部变量)。输出
1: nounset enabled
1: extglob enabled
2: nounset disabled
2: extglob disabled
3: nounset enabled
3: extglob enabled
表明效果已成功定位。
概括
作为函数体的子 shell 执行环境已被证明对于本地化 bash shell 选项的效果很有用。case
函数体内的语句可以从中受益extglob
,因为对此变量的更改不会影响函数范围之外的代码。如果资源紧张,启动子 shell 的额外开销可能会不必要地降低性能。