从bash中的“key1='val1' key2='val2'”字符串中解析变量而不使用eval

ren*_*ack 0 linux bash shell variable-expansion single-quotes

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

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
Run Code Online (Sandbox Code Playgroud)

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

分享脚本执行的输出:

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'
Run Code Online (Sandbox Code Playgroud)

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

输入感兴趣的字符串

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

预期输出:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Cha*_*ffy 6

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

#!/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
Run Code Online (Sandbox Code Playgroud)

您可以在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" )
Run Code Online (Sandbox Code Playgroud)

...因此您可以参考"${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
Run Code Online (Sandbox Code Playgroud)

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

#!/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
Run Code Online (Sandbox Code Playgroud)