如何在 Linux Bash 脚本中使用 eval

已发表: 2022-08-22
显示 bash 提示符的 Linux 笔记本电脑
fatmawati achmad zaenuri/Shutterstock.com

在所有 Bash 命令中,可怜的旧eval可能名声最差。 有道理,还是只是坏消息? 我们讨论了这个最不受欢迎的 Linux 命令的用途和危险。

我们需要谈谈评估

使用不慎, eval会导致不可预知的行为甚至系统不安全。 从它的声音来看,我们可能不应该使用它,对吧? 不完全是。

你可以对汽车说类似的话。 在坏人手中,它们是致命的武器。 人们在袭击和逃跑中使用它们。 我们都应该停止使用汽车吗? 不,当然不是。 但它们必须被正确使用,并且由知道如何驾驶它们的人使用。

如何在 Bash 中使用变量
相关如何在 Bash 中使用变量

用于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 "

使用带有字符串变量的 eval 来计算文件中的单词

该命令在当前 shell 中执行,而不是在子 shell 中。 我们可以很容易地证明这一点。 我们有一个名为“variables.txt”的短文本文件。 它包含这两行。

 首先=操作方法
第二=极客

我们将使用cat将这些行发送到终端窗口。 然后我们将使用eval来评估cat命令,以便执行文本文件中的指令。 这将为我们设置变量。

 猫变量.txt
eval "$(cat variables.txt)"
回声 $first $second 

在当前 shell 中访问由 eval 设置的变量

通过使用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 

使用 chmod 使脚本可执行

您需要对从本文复制的任何脚本执行此操作。 只需在每种情况下使用适当的脚本名称即可。

当我们运行我们的脚本时,我们会看到来自变量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 个变量,称为x1x10 。 循环体中的eval语句提供“x”并获取循环计数器$n的值来创建变量名。 同时,它将新变量设置为循环计数器$n的值。

Linux Bash 脚本中 for 循环的 9 个示例
相关Linux Bash 脚本中 for 循环的 9 个示例

它将新变量打印到终端窗口,然后用新变量的值增加total变量。

在循环之外,再次打印 10 个新变量,全部在一行上。 请注意,我们也可以通过它们的真实名称来引用变量,而无需使用它们名称的计算或派生版本。

最后,我们打印total变量的值。

 ./loop.sh 

使用eval动态创建变量

相关:入门: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指示的位置。 该名称将打印到终端窗口。 在真实世界的脚本中,您还需要创建实际文件。

如何使用 Linux 睡眠命令暂停 Bash 脚本
相关如何使用 Linux Sleep 命令暂停 Bash 脚本

脚本的主体是使用sleep命令模拟的。 它创建第一个日志文件,等待三秒钟,然后创建另一个。 它创建了四个日志文件,这些文件相互隔开,以使它们文件名中的时间戳不同。

最后,有一个删除日志文件的循环。 循环计数器文件设置为 1。 它计数到并包括filecount的值,该值保存已创建的文件数。

如果filecount仍然设置为零——因为没有创建日志文件——循环体将永远不会被执行,因为 1 不小于或等于零。 这就是为什么filecount变量在声明时设置为零以及为什么在创建第一个文件之前递增的原因。

在循环内部,我们将eval与我们的非破坏性rm_string和从数组中检索到的文件的名称一起使用。 然后我们将数组元素设置为空字符串。

这是我们在运行脚本时看到的。

 ./clear-logs.sh 

删除名称存储在数组中的文件

不全是坏事

备受诟病的eval肯定有它的用途。 像大多数工具一样,鲁莽使用它是危险的,而且方式不止一种。

如果您确保它所处理的字符串是在内部创建的,而不是从人、API 或 HTTPS 请求之类的东西中捕获的,那么您将避免主要的陷阱。

相关:如何在 Linux 终端中显示日期和时间(并在 Bash 脚本中使用它)