Contents

将命令存储在shell脚本的变量中

1. 概述

在将命令存储到变量中时,我们通常会遇到空格字符和复杂的命令结构等问题。

在本文中,我们将了解如何将命令存储到变量中并运行它。我们还将讨论这些方法有哪些限制,以及我们在使用eval 等替代方法时面临的问题。

2. 将命令存储在数组中

我们可以使用数组存储单个命令及其参数。我们使用程序名称设置第一个元素,然后在后续数组位置中设置每个参数:

$ COMMAND=("ls" "-l" "/var/log/httpd/access_log" "/var/log/file with spaces")

然后,我们可以通过展开数组的所有元素来执行命令

$ "${COMMAND[@]}"
-rw-r--r-- 1 root root 0 Jan  9 09:38 /var/log/file with spaces
-rw-r--r-- 1 root root 0 Jan  8 17:21 /var/log/httpd/access_log

请注意,我们应该在数组周围使用引号来正确处理空格

我们可以在定义数组后更改命令或其参数:

$ COMMAND=("cat" "/var/log/messages")
$ echo ${COMMAND[@]}
cat /var/log/messages
$ COMMAND[0]="less"
$ echo ${COMMAND[@]}
less /var/log/messages
$ COMMAND[1]="/var/log/httpd/access_log"
$ echo ${COMMAND[@]}
less /var/log/httpd/access_log

此外,我们可以使用变量构建数组

$ PROGRAM="ls"
$ PARAMETER="-l"
$ FILE1="/var/log/httpd/access_log"
$ FILE2="/var/log/file with spaces"
$ COMMAND=("$PROGRAM" "$PARAMETER" "$FILE1" "$FILE2")
$ "${COMMAND[@]}"
-rw-r--r-- 1 root root 0 Jan  9 09:38 /var/log/file with spaces
-rw-r--r-- 1 root root 0 Jan  8 17:21 /var/log/httpd/access_log

或者,我们可以通过将命令存储在变量中并将参数存储在数组中来将命令与其参数分开

$ COMMAND="cat"
$ PARAMETERS=("/var/log/httpd/access_log", "/var/log/httpd/access_log.1")
$ "$COMMAND" "${PARAMETERS[@]}"

现在,我们可以通过简单地设置COMMAND=”tac”来使用tac 代替cat

此方法仅在我们有一个简单的命令及其参数时才有效。它不适用于重定向、管道或ifwhile等保留字。

如果我们想执行一个复杂的命令构造,那么我们应该定义一个函数来代替

3. 将代码存储在变量中的问题

当我们将命令存储在变量中时,我们应该考虑到创建变量是为了存储数据,而不是代码。

这意味着我们在处理更复杂的案例时会遇到问题。

当我们将某些命令存储在变量中时,bash 解释器只会将其展开并作为带有参数的简单命令执行。所以,有些事情是行不通的。其中一些最重要的是:

简而言之,bash 只会扩展变量而不解释它们的内容。有一些例外,比如文件名扩展 ,但一般来说,我们不能在变量中存储任何任意脚本

我们可以测试是否可以在变量中使用更复杂的命令。让我们首先尝试使用管道:

$ COMMAND=("stat" "/var/log/httpd/access_log" "|" "grep" "Modify")
$ ${COMMAND[@]}
  File: /var/log/httpd/access_log
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: fd02h/64770d    Inode: 3148361     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2021-01-08 17:21:23.031001506 -0300
Modify: 2021-01-08 17:21:23.031001506 -0300
Change: 2021-01-08 17:21:23.031001506 -0300
 Birth: 2021-01-08 17:21:23.031001506 -0300
stat: cannot statx '|': No such file or directory
stat: cannot statx 'grep': No such file or directory
stat: cannot statx 'Modify': No such file or directory

它没有按预期工作,因为我们可以看到管道作为参数传递给stat

现在,让我们尝试在数组中使用一个变量:

$ FILE="/var/log/httpd/access_log" 
$ COMMAND=('ls' '-l' '$FILE')
$ ${COMMAND[@]}
ls: cannot access '$FILE': No such file or directory

当我们考虑将复杂命令存储在变量中作为解决方案时,我们应该记住这是一种不好的做法,相反,我们应该只写一个函数

另一种常见的方法是使用eval来解决前面提到的问题。在下一节中,我们将探讨这种方法的一些安全问题。

4. 使用eval 的问题

处理复杂情况的一种方法是使用eval 。还有其他类似的替代方案,例如echo - 变量的内容并将其通过管道传输到 shell 输入。他们都有同样的问题。

eval扩展其参数,然后 bash 解释器将执行它们。因此,当我们使用eval时,我们会解决上一节中提到的问题。但是,这种执行命令的方法可能是不安全的

当我们使用eval时,对我们执行的代码没有任何性质的检查,因此我们为恶意代码打开了大门。当我们的脚本使用外部输入时,这一点尤其值得关注。让我们在一个例子中使用eval

$ test_eval() {
    COMMAND="stat"
    if [ "$DEREFERENCE" == "yes" ]; then
        COMMAND="$COMMAND -L"
    fi
    COMMAND=$COMMAND\ /home/backup/db_*
    if [ "$REDIRECT" == "yes" ]; then
        COMMAND=$COMMAND\ " > ${REDIRECT_OUTPUT:-/tmp/stat_output}"
    fi
    eval $COMMAND
}

在这个脚本中,我们想对一些文件运行stat,可选地添加*-L*和/或重定向输出。让我们看看我们有哪些潜在的安全问题:

  • /home/backup中可能有恶意文件
  • 变量REDIRECT_OUTPUT可能包含恶意代码

让我们生成一个名为*/home/backup/db_;date的文件并调用test_eval()*:

$ touch '/home/backup/db_;date'
$ test_eval
/usr/bin/stat: cannot statx '/home/backup/db_': No such file or directory
Sun Jan 10 17:09:55 2021

如我们所见,分号破坏了命令,date命令被执行

现在,我们可以使用REDIRECT_OUTPUT遵循类似的想法:

$ REDIRECT=yes
$ REDIRECT_OUTPUT='/dev/null; date'
$ test_eval
Sun Jan 10 17:14:34 2021

再次,date被执行,我们当然可以想到更危险的例子