为什么用声明替换 eval(用于创建动态变量)会导致空变量?

为什么用声明替换 eval(用于创建动态变量)会导致空变量?

使用 bash >5,我尝试根据变量中指定的体系结构为变量分配不同的值。我使用一个函数来做到这一点。这很完美:

# arguments:
  variable name to assign,
  value for mac arch,
  value for pi arch

create_variable_for_arch() {
  if [ "$_run_for_arch" = "mac" ]; then
    eval $1=\$2
  else
    eval $1=\$3
  fi
}

然而,由于某种原因,这破坏了我的脚本:

create_variable_for_arch() {
  if [ "$_run_for_arch" = "mac" ]; then
    declare "$1"="$2"
  else
    declare "$1"="$3"
  fi
}

这是演示如何使用 create_variable_from_arch() 的片段

declare _moonlight_opt_audio
declare _arch_specific_stream_command

#
while getopts "b:fahdr:s" options; do
  case $options in
    a)
      create_variable_for_arch "_moonlight_opt_audio" \
        "--audio-on-host" "-localaudio"
      ;;
  esac
done

create_variable_for_arch "_moonlight_opt_fps" "--fps 60" "-fps 60"

start_streaming() {
  _arch_specific_options="$_moonlight_opt_resolution $_moonlight_opt_fps $_moonlight_opt_audio $_moonlight_opt_display_type $_moonlight_opt_bitrate"
  create_variable_for_arch "_arch_specific_stream_command" "$_arch_specific_options stream $_target_computer_ip $_moonlight_opt_app_name" "stream $_arch_specific_options -app $_moonlight_opt_app_name $_target_computer_ip"

  moonlight $_arch_specific_stream_command
}

使用 eval() 时的跟踪看起来像这样

+ start_streaming
+ _arch_specific_options='--resolution 1920x1080 --fps 60   --bitrate 5000'
+ create_variable_for_arch _arch_specific_stream_command '--resolution 1920x1080 --fps 60   --bitrate 5000 stream 192.168.1.30 StreamMouse' 'stream --resolution 1920x1080 --fps 60   --bitrate 5000 -app StreamMouse 192.168.1.30'
+ '[' mac = mac ']'
+ eval '_arch_specific_stream_command=$2'
++ _arch_specific_stream_command='--resolution 1920x1080 --fps 60   --bitrate 5000 stream 192.168.1.30 StreamMouse'
+ moonlight --resolution 1920x1080 --fps 60 --bitrate 5000 stream 192.168.1.30 StreamMouse
moonlight --resolution 1920x1080 --fps 60 --bitrate 5000 stream 192.168.1.30 StreamMouse

但通过声明,它看起来像这样:

    + start_streaming
    + _arch_specific_options=
    + create_variable_for_arch _arch_specific_stream_command ' stream 192.168.1.30 ' 'stream  -app  192.168.1.30'
    + '[' mac = mac ']'
    + declare '_arch_specific_stream_command= stream 192.168.1.30 '
    + echo moonlight
    moonlight

$_arch_specific_options 最终没有任何价值。到底是怎么回事?我尝试了几种不同的方式来引用或不引用变量,但我并不真正理解引用的含义是什么。

答案1

declare(与typeset其他 shell 的 类似;也可理解bash为 的别名declare)在当前作用域中声明一个变量(并且可以设置类型和/或值)。

因此,在这里,您将声明一个函数的局部变量create_variable_for_arch。当该函数返回时,该变量就会消失。

bash's declare/typeset有一个-g声明变量的选项全球的),但你不能使用它,因为它在最外层作用域中声明变量(并设置其类型和/或值),而不是函数调用者的作用域,所以在那里非常无用(它是mksh//在仅跳过的情况下zsh更有用yash使其本地化或者使用具有静态作用域的 ksh93,请参阅`declare name` 和 `declare -g` 有什么作用?了解详情)。

所以在这里,您的选择是使用eval或使用 namerefs:

create_variable_for_arch() {
  if [ "$_run_for_arch" = mac ]; then
    eval "$1=\$2"
  else
    eval "$1=\$3"
  fi
}

或者,假设$_run_for_arch您的脚本中是恒定的:

if [ "$_run_for_arch" = "mac" ]; then
  create_variable_for_arch() { eval "$1=\$2"; }
else
  create_variable_for_arch() { eval "$1=\$3"; }
fi

或者使用 namerefs:

create_variable_for_arch() {
  typeset -n _var_name="$1"
  if [ "$_run_for_arch" = mac ]; then
    _var_name=$2
  else
    _var_name=$3
  fi
}

eval出于安全原因,通常(正确地)建议避免使用它,但eval如果使用得当,它是安全的。如果使用不当,这里declare也是namerefs不安全的,因为它们都可以评估代码。

所有的:

f() { eval "$1=\$2"; }
f() { declare "$1=$2"; }
f() { declare -n v="$1"; v=$2; }

reboot如果使用以下命令调用,将运行该命令:

f 'a[$(reboot)]' value

确保第一个参数是变量名很重要,以避免任意命令执行漏洞。

f() { declare $1=$2; }

会更糟。由于这些参数扩展未加引号,因此它们会受到 split+glob 的影响,因此即使 can 的内容$2最终也会被评估为 shell 代码,如下所示:

f var 'foo a[$(reboot)]='

相关内容