在 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将变量作为字符串传递给求值。然后在“导入”变量后,使用此处引用的文档像在普通脚本中一样执行任何操作。

参考阅读:

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 的数量,但我对它原样很满意。