首页 > 解决方案 > 在没有 eval 的情况下从 bash 中的“key1='val1' key2='val2'”字符串中解析变量

问题描述

我有一个特定于项目的命令,它产生以下形式的输出:

Parameter1='value1' Parameter2='Value2' ... #变量的单引号值。

但我想显式分配值并需要打印必须显示相应值的参数。

这里xtc_cmd get是项目特定的 cmd

root@renway:~# FOO=`xtc_cmd get lan_ifname lan_ipaddr lan_netmask`
root@renway:~#
root@renway:~# echo $FOO
SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1' SYSCFG_lan_netmask='255.255.255.0'
root@renway:~#
root@renway:~# echo $SYSCFG_lan_ifname

root@renway:~# echo $SYSCFG_lan_ipaddr

root@renway:~# echo $SYSCFG_lan_netmask

但是,在变量打印它们的值之后,我尝试了“ eval $FOO ”。由于安全原因,我想跳过“评估”。

分享脚本执行的输出:

root@renway:~# /tmp/test.sh
++ xtc_cmd get lan_ifname lan_ipaddr lan_netmask
+ FOO='SYSCFG_lan_ifname='\''br1'\''
SYSCFG_lan_ipaddr='\''10.0.0.1'\''
SYSCFG_lan_netmask='\''255.255.255.0'\'''
+ echo 'SYSCFG_lan_ifname='\''br1'\''' 'SYSCFG_lan_ipaddr='\''10.0.0.1'\''' 'SYSCFG_lan_netmask='\''255.255.255.0'\'''
SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1' SYSCFG_lan_netmask='255.255.255.0'

如何实际分配值并打印这些变量。

输入感兴趣的字符串

FOO='SYSCFG_lan_ipaddr='\''10.0.0.1'\'' SYSCFG_sysdate='\'''\''$(date>> /tmp/date.txt)0'\'''\'' SYSCFG_lan_pd_interfaces='\''brlan0 brlan19 brlan20'\'''

预期输出:

foo_SYSCFG_lan_ipaddr=10.0.0.1
foo_SYSCFG_sysdate='$(date>> /tmp/date.txt)0' #single quoted value
foo_SYSCFG_lan_pd_interfaces=brlan0 brlan19 brlan20 #whitespace separated string

这里的挑战是SYSCFG_sysdate与其他参数相比,仅包含单引号值'$(date>> /tmp/date.txt)0' 。

抱歉,我错过了最早强调或提及这个参数。这是为了测试恶意命令注入攻击。所以这里的期望是要按原样存储但没有命令执行的值。使用 'eval' 内置,日期命令正在执行,这是不期望的。

我在运行 Zilog80 的 POSIX V1 脚本后得到了所需的输出,该脚本使用了内置的“set”

但是 POSIX V2 脚本只有在没有 SYSCFG_sysdate参数的情况下才能正常运行。

特别感谢@Charles Duffy和 @ Zilog80对这个问题的大量宝贵意见和指导。

标签: linuxbashshellsingle-quotesvariable-expansion

解决方案


借用一个密切相关问题的答案(从字符串中正确读取引用/转义的参数):

#!/usr/bin/env bash
FOO="SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1' SYSCFG_lan_netmask='255.255.255.0'"
 
case $BASH_VERSION in
  ''|[1-3].*) echo "ERROR: Bash 4.0 required; this is ${BASH_VERSION:-not bash}" >&2; exit 1;;
esac
 
declare -A kwargs=( )
while IFS= read -r -d ''; do
  [[ $REPLY = *=* ]] || {
    printf 'ERROR: Item %q is not in assignment form\n' "$REPLY" >&2
    continue
  }
  kwargs[${REPLY%%=*}]=${REPLY#*=}
done < <(xargs printf '%s\0' <<<"$FOO")
 
# show what we parsed for demonstration purposes
declare -p kwargs >&2

您可以在https://ideone.com/KniaC4 的在线沙箱中看到它运行;它的输出是以下形式的关联数组:

declare -A kwargs=([SYSCFG_lan_ifname]="br1" [SYSCFG_lan_netmask]="255.255.255.0" [SYSCFG_lan_ipaddr]="10.0.0.1" )

...所以你可以参考"${kwargs[SYSCFG_lan_ifname]}", 或"${kwargs[SYSCFG_lan_ipaddr]}"

这比分配给常规 bash 变量更安全,因为它不允许攻击者修改 PATH、LD_PRELOAD 或其他修改 shell、链接器、加载器、标准 C 库等行为的环境变量。(请注意,即使您不要显式地export指定此代码创建的分配,分配给已导出的变量将自动导出新值;因此,仅适用于环境变量而不适用于常规 shell 变量的安全问题仍然可以在这里发挥作用)。


警告:xargs 解析字符串的方式与 POSIX sh 标准不太兼容——有关详细信息和其他选项,请参阅上面给出的链接(Python 有一个完全兼容的解析器 f/e,链接的答案描述了如何使用它来自 bash)。


或者,使用较旧的 Bash 版本

当关联数组不可用时,可以为常规变量添加前缀:

#!/usr/bin/env bash
FOO="SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1' SYSCFG_lan_netmask='255.255.255.0'"
 
while IFS= read -r -d ''; do
  [[ $REPLY = *=* ]] || {
    printf 'ERROR: Item %q is not in assignment form\n' "$REPLY" >&2
    continue
  }
  printf -v "foo_${REPLY%%=*}" '%s' "${REPLY#*=}"
done < <(xargs printf '%s\0' <<<"$FOO")
 
# show what we parsed for demonstration purposes

for var in ${!foo_*}; do
  echo "$var has value: ${!var}"
done

在https://ideone.com/7UZJkT看到这个运行,输出:

foo_SYSCFG_lan_ifname has value: br1
foo_SYSCFG_lan_ipaddr has value: 10.0.0.1
foo_SYSCFG_lan_netmask has value: 255.255.255.0

推荐阅读