如何在 Linux Bash 脚本中使用 eval
已发表: 2022-08-22
在所有 Bash 命令中,可怜的旧eval
可能名声最差。 有道理,还是只是坏消息? 我们讨论了这个最不受欢迎的 Linux 命令的用途和危险。
我们需要谈谈评估
使用不慎, eval
会导致不可预知的行为甚至系统不安全。 从它的声音来看,我们可能不应该使用它,对吧? 不完全是。
你可以对汽车说类似的话。 在坏人手中,它们是致命的武器。 人们在袭击和逃跑中使用它们。 我们都应该停止使用汽车吗? 不,当然不是。 但它们必须被正确使用,并且由知道如何驾驶它们的人使用。
用于eval
的常用形容词是“邪恶”。 但这一切都取决于它的使用方式。 eval
命令整理来自一个或多个变量的值。 它创建一个命令字符串。 然后它执行该命令。 当您需要处理在脚本执行期间动态派生命令内容的情况时,这非常有用。
当编写脚本以对从脚本外部某处接收到的字符串使用eval
时,就会出现问题。 它可以由用户输入、通过 API 发送、标记到 HTTPS 请求或脚本外部的任何其他位置。
如果eval
将要处理的字符串不是在本地和以编程方式派生的,则该字符串可能包含嵌入的恶意指令或其他格式错误的输入。 显然,您不希望eval
执行恶意命令。 所以为了安全起见,不要将eval
与外部生成的字符串或用户输入一起使用。
eval 的第一步
eval
命令是一个内置的 Bash shell 命令。 如果存在 Bash,则将存在eval
。
eval
将其参数连接成一个字符串。 它将使用单个空格来分隔连接的元素。 它评估参数,然后将整个字符串传递给 shell 以执行。
让我们创建一个名为wordcount
的变量。
wordcount="wc -w raw-notes.md"
字符串变量包含一个命令,用于计算名为“raw-notes.md”的文件中的单词。
我们可以使用eval
通过将变量的值传递给它来执行该命令。
eval " $wordcount "
该命令在当前 shell 中执行,而不是在子 shell 中。 我们可以很容易地证明这一点。 我们有一个名为“variables.txt”的短文本文件。 它包含这两行。
首先=操作方法 第二=极客
我们将使用cat
将这些行发送到终端窗口。 然后我们将使用eval
来评估cat
命令,以便执行文本文件中的指令。 这将为我们设置变量。
猫变量.txt eval "$(cat variables.txt)" 回声 $first $second
通过使用echo
打印变量的值,我们可以看到eval
命令在当前 shell 中运行,而不是在子 shell 中。
子shell 中的进程不能更改父shell 的shell 环境。 因为 eval 在当前 shell 中运行,所以eval
设置的变量可以从启动eval
命令的 shell 中使用。
请注意,如果您在脚本中使用eval
,那么将被eval
更改的 shell 是脚本正在运行的子 shell,而不是启动它的 shell。
相关:如何使用 Linux cat 和 tac 命令
在命令字符串中使用变量
我们可以在命令字符串中包含其他变量。 我们将设置两个变量来保存整数。
数字1=10 数字2=7
我们将创建一个变量来保存一个expr
命令,该命令将返回两个数字的和。 这意味着我们需要访问命令中两个整数变量的值。 注意expr
语句周围的反引号。
add="`expr $num1 + $num2`"
我们将创建另一个命令来显示expr
语句的结果。
显示=“回声”
请注意,我们不需要在echo
字符串的末尾包含空格,也不需要在expr
字符串的开头包含空格。 eval
负责这一点。
并执行我们使用的整个命令:
评估$显示$添加
expr
字符串中的变量值由eval
替换到字符串中,然后将其传递给 shell 以执行。
相关:如何在 Bash 中使用变量
访问变量内部的变量
您可以为变量分配一个值,然后将该变量的名称分配给另一个变量。 使用eval
,您可以访问第一个变量中保存的值,其名称是存储在第二个变量中的值。 一个例子将帮助你解开这个问题。
将此脚本复制到编辑器,并将其保存为名为“assign.sh”的文件。
#!/bin/bash title="如何极客" 网页=标题 命令=“回声” 评估 $command \${$webpage}
我们需要使用chmod
命令使其可执行。
chmod +x 分配.sh

您需要对从本文复制的任何脚本执行此操作。 只需在每种情况下使用适当的脚本名称即可。
当我们运行我们的脚本时,我们会看到来自变量title
的文本,即使eval
命令使用了变量webpage
。
./assign.sh
转义的美元符号“ $
”和大括号“ {}
”导致 eval 查看名称存储在webpage
变量中的变量中保存的值。
使用动态创建的变量
我们可以使用eval
动态创建变量。 该脚本称为“loop.sh”。
#!/bin/bash 总计=0 label="循环完成。总计:" 对于 {1..10} 中的 n 做 评估 x$n=$n 回声“循环”$x$n ((总计+=$x$n)) 完毕 回声 $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10 回声$标签$总计
它创建了一个名为total
的变量,它保存了我们创建的变量值的总和。 然后它创建一个名为label
的字符串变量。 这是一个简单的文本字符串。
我们将循环 10 次并创建 10 个变量,称为x1
到x10
。 循环体中的eval
语句提供“x”并获取循环计数器$n
的值来创建变量名。 同时,它将新变量设置为循环计数器$n
的值。
它将新变量打印到终端窗口,然后用新变量的值增加total
变量。
在循环之外,再次打印 10 个新变量,全部在一行上。 请注意,我们也可以通过它们的真实名称来引用变量,而无需使用它们名称的计算或派生版本。
最后,我们打印total
变量的值。
./loop.sh
相关:入门:Bash 循环:for、while 和 until
使用 eval 和数组
想象一个场景,您有一个长时间运行并为您执行一些处理的脚本。 它使用从时间戳创建的名称写入日志文件。 有时,它会启动一个新的日志文件。 脚本完成后,如果没有错误,它会删除它创建的日志文件。
您不希望它简单地rm *.log
,您只希望它删除它创建的日志文件。 此脚本模拟该功能。 这是“clear-logs.sh”。
#!/bin/bash 声明 -a 日志文件 文件数=0 rm_string="回声" 功能创建日志文件(){ ((++filecount)) 文件名=$(日期 +"%Y-%m-%d_%H-%M-%S").log 日志文件[$filecount]=$filename echo $filecount "创建" ${logfiles[$filecount]} } # 脚本的主体。 这里做了一些处理 # 定期生成日志文件。 我们将模拟它 创建日志文件 睡觉 3 创建日志文件 睡觉 3 创建日志文件 睡觉 3 创建日志文件 # 有没有要删除的文件? for ((file=1; file<=$filecount; file++)) 做 # 删除日志文件 eval $rm_string ${logfiles[$file]} "已删除..." 日志文件[$file]="" 完毕
该脚本声明了一个名为logfiles
的数组。 这将保存脚本创建的日志文件的名称。 它声明了一个名为filecount
的变量。 这将保存已创建的日志文件的数量。
它还声明了一个名为rm_string
的字符串。 在现实世界的脚本中,这将包含rm
命令,但我们正在使用echo
,因此我们可以以非破坏性方式演示该原理。
函数create_logfile()
是每个日志文件的命名位置和打开位置。 我们只是在创建filename ,并假装它是在文件系统中创建的。
该函数递增filecount
变量。 它的初始值为零,因此我们创建的第一个文件名存储在数组中的位置一。 这是有意为之的,稍后见。
文件名是使用date
命令和“.log”扩展名创建的。 该名称存储在数组中由filecount
指示的位置。 该名称将打印到终端窗口。 在真实世界的脚本中,您还需要创建实际文件。
脚本的主体是使用sleep
命令模拟的。 它创建第一个日志文件,等待三秒钟,然后创建另一个。 它创建了四个日志文件,这些文件相互隔开,以使它们文件名中的时间戳不同。
最后,有一个删除日志文件的循环。 循环计数器文件设置为 1。 它计数到并包括filecount
的值,该值保存已创建的文件数。
如果filecount
仍然设置为零——因为没有创建日志文件——循环体将永远不会被执行,因为 1 不小于或等于零。 这就是为什么filecount
变量在声明时设置为零以及为什么在创建第一个文件之前递增的原因。
在循环内部,我们将eval
与我们的非破坏性rm_string
和从数组中检索到的文件的名称一起使用。 然后我们将数组元素设置为空字符串。
这是我们在运行脚本时看到的。
./clear-logs.sh
不全是坏事
备受诟病的eval
肯定有它的用途。 像大多数工具一样,鲁莽使用它是危险的,而且方式不止一种。
如果您确保它所处理的字符串是在内部创建的,而不是从人、API 或 HTTPS 请求之类的东西中捕获的,那么您将避免主要的陷阱。
相关:如何在 Linux 终端中显示日期和时间(并在 Bash 脚本中使用它)