在 shell 中的函数声明时计算变量
Evaluate variable at time of function declaration in shell
我正在设置我的 shell 环境,我希望能够在 zsh 中使用与在 bash 中相同的一些 functions/aliases。这些函数之一在编辑器中打开 .bashrc 或 .zshrc(相关文件),等待编辑器关闭,然后重新加载 rc 文件。
# a very simplified version of this function
editrc() {
local rcfile=".$(basename $SHELL)rc"
code -w ~/$rcfile
. ~/$rcfile
}
我在其他几个函数中使用了 rcfile
的值,所以我将其从函数声明中删除。
_rc=".$(basename $SHELL)rc"
editrc() {
code -w ~/$_rc
. ~/$_rc
}
# ... other functions that use it ...
unset _rc
但是,因为我是个爱整洁的人,所以我想在脚本末尾取消设置 _rc
,但我仍然希望我的函数正确 运行。在声明函数时是否有一种聪明的方法来评估 $_rc
?
我知道我可以使用 eval
并将除 $_rc
实例之外的所有内容都放在单引号内,但这似乎很痛苦,因为我的函数的完整版本同时使用单引号和双引号-引号。
_rc=".$(basename $SHELL)rc"
eval 'editrc() {
echo Here'"'"'s a thing that uses single quotes. As you can see it'"'"'s a pain.
code -w ~/'$_rc'
. ~/'$_rc'
}'
# ... other functions using `_rc`
unset _rc
我想我可以声明我的函数,然后用 eval "$(declare -f editrc | awk)"
做一些魔术。这很可能比它的价值更痛苦,但我总是对学习新事物感兴趣。
注意:我很乐意将其概括为执行此操作的实用函数。
_myvar=foo
anothervar=bar
myfunc() {
echo $_myvar $anothervar
}
# redeclares myfunc with `$_myvar` expanded, but leaves `$anothervar` as-is
expandfunctionvars myfunc '$_myvar'
Is there a clever way to evaluate $_rc at the time the function is declared?
_rc=".$(basename "$SHELL")rc"
# while you could eval here, source lets you work with a stream
source <(
cat <<EOF
editrc() {
local _rc
# first safely trasfer context
$(declare -p _rc)
EOF
# use quoted here string to do anything inside without caring.
cat <<'EOF'
# do anything else
echo "Here's a thing that uses single quotes. As you can see it's not a pain, just choose proper quoting."
code -w "~/$_rc"
. "~/$_rc"
}
EOF
)
unset _rc
一般先用declare -p
将变量作为字符串传递给求值。然后在“导入”变量后,使用此处引用的文档像在普通脚本中一样执行任何操作。
参考阅读:
<<EOF
是一个 here document。请注意此处定界符被引号与不引号时的解析差异。
<(..)
是一个 process substitution
source
命令读取由进程替换创建的管道。在过程替换中,我输出要获取的函数。在此处的第一个文档中,我输出了函数名称定义,其中包含 local
变量,这样它就不会污染全局命名空间。然后使用 declare -p
我将变量定义输出为一个正确引用的字符串,稍后由 source
获取。然后使用此处引用的文档输出函数的其余部分,这样我就不需要关心引用了。
代码是bash具体的,我对zsh一无所知,不会使用它。
你也可以用 eval 来做:
eval '
editrc() {
local _rc
# first safely trasfer context
'"$(declare -p _rc)"'
# use quoted here string to do anything inside without caring.
# do anything else
echo "Here'\''s a thing that uses single quotes. As you can see it'\''s not a pain, just choose proper quoting."
code -w "~/$_rc"
. "~/$_rc"
}'
但对我来说,使用此处引用的文档定界符可以更轻松地书写。
当 KamilCuck 正在研究他们的答案时,我设计了一个函数,它将接受任何函数名称和一组变量名称,仅展开这些变量,然后重新声明该函数。
expandFnVars() {
if [[ $# -lt 2 ]]; then
>&2 echo 'expandFnVars requires at least two arguments: the function name and the variable(s) to be expanded'
return 1
fi
local fn=""
shift
local vars=("$@")
if [[ -z "$(declare -F $fn 2> /dev/null)" ]]; then
>&2 echo $fn is not a function.
return 1
fi
foundAllVars=true
for v in $vars; do
if [[ -z "$(declare -p $v 2> /dev/null)" ]]; then
>&2 echo $v is not a declared value.
foundAllVars=false
fi
done
[[ $foundAllVars != true ]] && return 1
fn="$(declare -f $fn)"
for v in $vars; do
local val="$(eval 'echo $'$v)" # get the value of the varable represented by $v
val="${val//\"/\\"}" # escape any double-quotes
val="${val//\/\\\}" # escape any backslashes
fn="$(echo "$fn" | sed -r 's/"?$'$v'"?/"'"$val"'"/g')" # replace instances of "$$v" and $$v with $val
done
eval "$fn"
}
用法:
foo="foo bar"
bar='$foo'
baz=baz
fn() {
echo $bar $baz
}
expandFnVars fn bar
declare -f fn
# prints:
# fn ()
# {
# echo "$foo" $baz
# }
expandFnVars fn foo
declare -f fn
# prints:
# fn ()
# {
# echo "foo bar" $baz
# }
现在来看,我看到了一个缺陷。假设原始函数中的 $bar
是单引号。我们可能不希望它的值被替换。这可以通过一些聪明的正则表达式 lookbehinds 来解决,以计算未转义的 '
s 的数量,但我对它原样很满意。
我正在设置我的 shell 环境,我希望能够在 zsh 中使用与在 bash 中相同的一些 functions/aliases。这些函数之一在编辑器中打开 .bashrc 或 .zshrc(相关文件),等待编辑器关闭,然后重新加载 rc 文件。
# a very simplified version of this function
editrc() {
local rcfile=".$(basename $SHELL)rc"
code -w ~/$rcfile
. ~/$rcfile
}
我在其他几个函数中使用了 rcfile
的值,所以我将其从函数声明中删除。
_rc=".$(basename $SHELL)rc"
editrc() {
code -w ~/$_rc
. ~/$_rc
}
# ... other functions that use it ...
unset _rc
但是,因为我是个爱整洁的人,所以我想在脚本末尾取消设置 _rc
,但我仍然希望我的函数正确 运行。在声明函数时是否有一种聪明的方法来评估 $_rc
?
我知道我可以使用 eval
并将除 $_rc
实例之外的所有内容都放在单引号内,但这似乎很痛苦,因为我的函数的完整版本同时使用单引号和双引号-引号。
_rc=".$(basename $SHELL)rc"
eval 'editrc() {
echo Here'"'"'s a thing that uses single quotes. As you can see it'"'"'s a pain.
code -w ~/'$_rc'
. ~/'$_rc'
}'
# ... other functions using `_rc`
unset _rc
我想我可以声明我的函数,然后用 eval "$(declare -f editrc | awk)"
做一些魔术。这很可能比它的价值更痛苦,但我总是对学习新事物感兴趣。
注意:我很乐意将其概括为执行此操作的实用函数。
_myvar=foo
anothervar=bar
myfunc() {
echo $_myvar $anothervar
}
# redeclares myfunc with `$_myvar` expanded, but leaves `$anothervar` as-is
expandfunctionvars myfunc '$_myvar'
Is there a clever way to evaluate $_rc at the time the function is declared?
_rc=".$(basename "$SHELL")rc"
# while you could eval here, source lets you work with a stream
source <(
cat <<EOF
editrc() {
local _rc
# first safely trasfer context
$(declare -p _rc)
EOF
# use quoted here string to do anything inside without caring.
cat <<'EOF'
# do anything else
echo "Here's a thing that uses single quotes. As you can see it's not a pain, just choose proper quoting."
code -w "~/$_rc"
. "~/$_rc"
}
EOF
)
unset _rc
一般先用declare -p
将变量作为字符串传递给求值。然后在“导入”变量后,使用此处引用的文档像在普通脚本中一样执行任何操作。
参考阅读:
<<EOF
是一个 here document。请注意此处定界符被引号与不引号时的解析差异。<(..)
是一个 process substitution
source
命令读取由进程替换创建的管道。在过程替换中,我输出要获取的函数。在此处的第一个文档中,我输出了函数名称定义,其中包含 local
变量,这样它就不会污染全局命名空间。然后使用 declare -p
我将变量定义输出为一个正确引用的字符串,稍后由 source
获取。然后使用此处引用的文档输出函数的其余部分,这样我就不需要关心引用了。
代码是bash具体的,我对zsh一无所知,不会使用它。
你也可以用 eval 来做:
eval '
editrc() {
local _rc
# first safely trasfer context
'"$(declare -p _rc)"'
# use quoted here string to do anything inside without caring.
# do anything else
echo "Here'\''s a thing that uses single quotes. As you can see it'\''s not a pain, just choose proper quoting."
code -w "~/$_rc"
. "~/$_rc"
}'
但对我来说,使用此处引用的文档定界符可以更轻松地书写。
当 KamilCuck 正在研究他们的答案时,我设计了一个函数,它将接受任何函数名称和一组变量名称,仅展开这些变量,然后重新声明该函数。
expandFnVars() {
if [[ $# -lt 2 ]]; then
>&2 echo 'expandFnVars requires at least two arguments: the function name and the variable(s) to be expanded'
return 1
fi
local fn=""
shift
local vars=("$@")
if [[ -z "$(declare -F $fn 2> /dev/null)" ]]; then
>&2 echo $fn is not a function.
return 1
fi
foundAllVars=true
for v in $vars; do
if [[ -z "$(declare -p $v 2> /dev/null)" ]]; then
>&2 echo $v is not a declared value.
foundAllVars=false
fi
done
[[ $foundAllVars != true ]] && return 1
fn="$(declare -f $fn)"
for v in $vars; do
local val="$(eval 'echo $'$v)" # get the value of the varable represented by $v
val="${val//\"/\\"}" # escape any double-quotes
val="${val//\/\\\}" # escape any backslashes
fn="$(echo "$fn" | sed -r 's/"?$'$v'"?/"'"$val"'"/g')" # replace instances of "$$v" and $$v with $val
done
eval "$fn"
}
用法:
foo="foo bar"
bar='$foo'
baz=baz
fn() {
echo $bar $baz
}
expandFnVars fn bar
declare -f fn
# prints:
# fn ()
# {
# echo "$foo" $baz
# }
expandFnVars fn foo
declare -f fn
# prints:
# fn ()
# {
# echo "foo bar" $baz
# }
现在来看,我看到了一个缺陷。假设原始函数中的 $bar
是单引号。我们可能不希望它的值被替换。这可以通过一些聪明的正则表达式 lookbehinds 来解决,以计算未转义的 '
s 的数量,但我对它原样很满意。