在Shell中进行函数声明时评估变量

时间:2020-08-02 20:13:39

标签: bash shell zsh variable-expansion

我正在设置我的Shell环境,我希望能够在zsh中使用与bash中相同的功能/别名。这些功能之一在编辑器中打开.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' 

2 个答案:

答案 0 :(得分:3)

在声明函数时是否有一种聪明的方法来评估$ _rc?

_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"
}'

但是对我来说,使用此处引用的文档定界符可以简化编写过程。

答案 1 :(得分:0)

虽然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="$1"
  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用单引号引起来。我们可能不希望替换其值。可以通过一些巧妙的正则表达式后缀来解决此问题,以计算未转义的'的数量,但我对此很满意。