详解eval

eval 是 shell 内建的一个命令。
它的主要作用是:

  • 对其输入参数做2次解析
  • 第1次解析是相关变量的替换
  • 第2次解析就是把第1次的解析结果交给shell去执行

eval 的常见用法

下面一段代码包含了 eval 的三种常见用法。

ID=5

# 给变量赋值
eval TEST_CASE_${ID}=1005

# 获取该变量名
TEST_CASE_NAME=$(eval echo TEST_CASE_${ID})
echo $TEST_CASE_NAME   # print TEST_CASE_5

# 打印该变量值
eval VAL=\$${TEST_CASE_NAME}
echo $VAL    # print 1005

常见用法-1. 给一个变量名中含有变量的变量赋值

就是这句 eval TEST_CASE_${ID}=1005

这里的第1次解析,就是把${ID}替换成5;
这里的第2次解析,就是把第一次解析的结果 TEST_CASE_5=1005交给shell去执行

常见用法-2. 取一个变量名中含有变量的变量的变量名

也就是,我们想知道 TEST_CASE_${ID}到底是什么,该怎么做呢?
答案就是这句 $(eval echo TEST_CASE_${ID}).
(当然在上面的代码中,是把它赋给一个变量,方便打印。)

这里的第1次解析,还是把${ID}替换成5;
这里的第2次解析,就是把第一次的解析结果 echo TEST_CASE_5交给shell去执行。

常见用法-3. 已知变量名,求该变量的值

在上面的代码中,我们已知变量名为 ${TEST_CASE_NAME},即TEST_CASE_5.
然后我们想知道 ${TEST_CASE_5}到底是什么。
这个时候,就会用到这句 eval VAL=\$${TEST_CASE_NAME}

这里的第1次解析,就是把${TEST_CASE_NAME}替换成 TEST_CASE_5
这里的第2次解析,就是把第一次的解析结果 VAL=$TEST_CASE_5交给shell去执行(可能由于shell的某些解析限制,需要在第一个美元符号前面加一个反斜杠)

其实这里还有第二种写法(本质上和第一种写法相同)

VAL="$(eval echo \$${TEST_CASE_NAME})"
echo $VAL

这种写法的第1次解析,仍是把${TEST_CASE_NAME}替换成 TEST_CASE_5
这种写法的第2次解析,就是把第一次的解析结果 echo ${TEST_CASE_5}交给shell去执行

以上的第3种用法其实经常被需要用到,即:已知变量名,求变量值。
再来看一个例子:

VAR=100
VAR_NAME=VAR
eval value=\$${VAR_NAME}
echo $value

以上会打印100.

除了上述的3种常见用法外,还有第4种常见用法,即:

常见用法-4. 利用eval来执行一个用字符串表示的命令。

比如,command="cat ./myfile.txt", 这个command是一个字符串,怎么把它当命令执行呢?
直接交给 eval 就可以了,如下

$ echo "This is my file" > myfile.txt

$ cat myfile.txt
This is my file

$ CMD="cat ./myfile.txt"

$ eval $CMD
This is my file

第1次解析,把变量$CMD替换成它所代表的字符串;
第2次解析,把这个字符串交给shell去执行。

eval 最常见的用法大约就是以上4种了。

为什么 eval 是危险的

为什么eval是危险的呢?就是因为它可能会执行一些被恶意填入字符串中的命令。
举个栗子:

#!/bin/sh

fifth() {
    _fifth_array=$1
    eval echo "\"The fifth element is \${$_fifth_array[4]}\""    # DANGER!
}

a=(zero one two three four five)
fifth a

fifth 'x}"; date; #'

以上代码的执行结果如下:

The fifth element is four
The fifth element is
Sat Sep  9 14:24:19 UTC 2023

这哪里危险了?它执行了一个用户作为参数输入的命令 - date, 如果用户输入的是 “sudo rm -rf /” 呢?

为什么会执行到date呢?
本来 fifth() 函数的参数是一个数组,但是有人恶意填了一个字符串。此时,第一次解析就用这个字符串去代入了。
经过第一次解析,传递给shell的是这么一个东西:

echo "The fifth element is ${x}"; date; #[4]}"

所以,代入之后,

  • 第一个分号结束了前面的 echo 语句;
  • 第二个分号执行了恶意命令 date;
  • 最后的井号,将后面的内容都当成注释了,从而避免了shell出现语法解析错误而不再执行恶意命令。

最后一个参考文献中还有更复杂的危险case,就不再列出了。有兴趣的读者可自行参阅。

参考文献

  • What is the ‘eval’ command in bash
  • shell中的eval命令
  • Eval command and security issues

(END)

你可能感兴趣的:(#,Shell,开发语言,bash)