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

Posted

技术标签:

【中文标题】如何将命令存储在 shell 脚本的变量中?【英文标题】:How can I store a command in a variable in a shell script? 【发布时间】:2011-08-02 17:02:45 【问题描述】:

我想在变量中存储一个稍后使用的命令(不是命令的输出,而是命令本身)。

我有一个简单的脚本如下:

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

但是,当我尝试一些更复杂的东西时,它会失败。例如,如果我让

command="ls | grep -c '^'";

输出是:

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

如何将这样的命令(带有管道/多个命令)存储在变量中以供以后使用?

【问题讨论】:

使用函数! 看到这个帖子:Why should eval be avoided in Bash, and what should I use instead?. 【参考方案1】:

使用评估:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

【讨论】:

现在建议使用 $(...) 而不是 backticks。 y=$(eval $x) mywiki.wooledge.org/BashFAQ/082 eval 是一种可接受的做法如果您信任变量的内容。如果您正在运行,比如说,x="ls $name | wc"(甚至是x="ls '$name' | wc"),那么如果该变量可以由权限较低的人设置,那么此代码是注入或权限提升漏洞的快速通道。 (例如,遍历/tmp 中的所有子目录?您最好相信系统上的每个用户都不会创建一个名为$'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/' 的用户。 eval 是一个巨大的 bug 磁铁,如果没有警告意外解析行为的风险(即使没有恶意字符串,如 @CharlesDuffy 的示例),不应该推荐它。例如,尝试x='echo $(( 6 * 7 ))',然后尝试eval $x。您可能希望它打印“42”,但它可能不会。你能解释为什么它不起作用吗?你能解释我为什么说“可能”吗?如果这些问题的答案对你来说不是很明显,你不应该接触eval @Student,尝试预先运行set -x 来记录运行的命令,这样可以更轻松地查看发生了什么。 @Student 我还推荐shellcheck.net 指出常见错误(以及你不应该养成的坏习惯)。【参考方案2】:

不要不要使用eval!它存在引入任意代码执行的重大风险。

BashFAQ-50 - 我正在尝试将命令放入变量中,但复杂的情况总是失败。

把它放在一个数组中,把所有带双引号"$arr[@]"的单词展开成IFS分割单词,因为Word Splitting。

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

并查看数组里面的内容。 declare -p 允许您查看内部数组的内容,每个命令参数位于单独的索引中。如果其中一个参数包含空格,则在添加到数组时在内部引用将防止它因分词而被拆分。

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

并以如下方式执行命令

"$cmdArgs[@]"
23:15:18

(或)完全使用bash 函数来运行命令,

cmd() 
   date '+%H:%M:%S'

然后调用函数

cmd

POSIX sh 没有数组,因此最接近的方法是在位置参数中建立元素列表。这是运行邮件程序的 POSIX sh 方式

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() 
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"

请注意,这种方法只能处理没有重定向的简单命令。它不能处理重定向、管道、for/while 循环、if 语句等

另一个常见的用例是在运行 curl 时使用多个标头字段和有效负载。您始终可以像下面这样定义 args 并在扩展的数组内容上调用 curl

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "$curlArgs[@]"

另一个例子,

payload=''
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

现在变量已经定义好了,使用一个数组来存储你的命令参数

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

现在做一个适当的引用扩展

curl "$curlCMD[@]"

【讨论】:

@Student,如果您的原始字符串包含管道,那么该字符串需要通过 bash 解析器的不安全部分才能作为代码执行。在这种情况下不要使用字符串;改用函数:Command() echo aaa | grep a; -- 之后您可以运行 Commandresult=$(Command) 等。 @Student,对;但这会故意失败,因为您要求做的事情本质上是不安全的 @Student:我在最后添加了一条注释,提到它在某些条件下不起作用 是的,完全正确。例如,你不能sendto if true; then echo poo; fi,因为看起来你在发送if true,这显然是一个语法错误,并且以下语句与sendto调用无关。 @tripleee:有点感谢提供赏金,希望我能与您和其他有用的贡献者分享;)【参考方案3】:
var=$(echo "asdf")
echo $var
# => asdf

使用这种方法,命令会立即被计算并存储它的返回值。

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

反引号也一样

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

$(...) 中使用 eval 不会使其在以后被评估:

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

使用eval,使用eval时进行求值

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

在上面的例子中,如果你需要运行一个带参数的命令,把它们放在你存储的字符串中:

stored_date="date -u"
# ...

对于 Bash 脚本,这很少相关,但最后要注意一点。小心eval。仅评估您控制的字符串,而不是来自不受信任的用户或由不受信任的用户输入构建的字符串。

【讨论】:

这并不能解决命令包含管道'|'的原始问题。 @Nate,注意当stored_date 只包含dateeval $stored_date 可能就足够了,但eval "$stored_date" 更可靠。例如,运行 str=$'printf \' * %s\\n\' *'; eval "$str" 并在最后的 "$str" 周围加引号和不加引号。 :) @CharlesDuffy 谢谢,我忘了引用。我敢打赌,如果我费心运行它,我的 linter 会抱怨的。 切线,那是useless echo【参考方案4】:

对于 bash,存储命令如下:

command="ls | grep -c '^'"

像这样运行你的命令:

echo $command | bash

【讨论】:

不确定,但这种运行命令的方式可能与使用 'eval' 的风险相同。 此外,当您echo 时不引用它,您正在破坏变量的内容。如果command 是字符串cd /tmp &amp;&amp; echo *,它将回显当前目录中的文件,而不是/tmp。另见When to wrap quotes around a shell variable【参考方案5】:

我尝试了各种不同的方法:

printexec() 
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"

输出:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

如您所见,只有第三个 "$@" 给出了正确的结果。

【讨论】:

对此有何解释?为什么只有第三个?请通过editing (changing) your answer 回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来像是今天写的)。 我不确定我是否足够在意调查eval 的复杂性。 IMO 这两个中的一个应该可以工作。【参考方案6】:

不知道为什么这么多的答案使它变得复杂! 使用 alia [command] '要执行的字符串' 示例:

alias dir='ls -l'

./dir
[pretty list of files]

【讨论】:

【参考方案7】:

首先,有这方面的功能。但如果你更喜欢变量,那么你的任务可以这样完成:

$ cmd=ls

$ $cmd # works
file  file2  test

$ cmd='ls | grep file'

$ $cmd # not works
ls: cannot access '|': No such file or directory
ls: cannot access 'grep': No such file or directory
 file

$ bash -c $cmd # works
file  file2  test

$ bash -c "$cmd" # also works
file
file2

$ bash <<< $cmd
file
file2

$ bash <<< "$cmd"
file
file2

或通过临时文件

$ tmp=$(mktemp)
$ echo "$cmd" > "$tmp"
$ chmod +x "$tmp"
$ "$tmp"
file
file2

$ rm "$tmp"

【讨论】:

我想很多人都没有注意到你提到的“首先有这个功能”,这是指向正确方向恕我直言的正确指针:“变量保存数据。函数保存代码” 【参考方案8】:

小心X=$(Command)注册订单

这个还在执行。甚至在被召唤之前。要检查并确认这一点,您可以执行以下操作:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

【讨论】:

这似乎不是对这里实际问题的回答,可能应该是评论(即使是这样)。 你说的“注册订单”是什么意思?你能详细说明一下吗?【参考方案9】:
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

【讨论】:

这会将命令的静态字符串输出存储在变量中,而不是命令本身。 grep -c -o 不是完全可移植的;您可能希望它返回搜索表达式的实际出现次数,但至少 GNU grep 不会这样做(它基本上等同于没有 -ogrep -c)。 仅限 Bash 的评论很奇怪;这个简单的脚本中没有任何内容与任何 Bourne 家族的 shell 不兼容。【参考方案10】:

即使您以后需要使用它,也不必将命令存储在变量中。照常执行即可。如果您存储在变量中,则需要某种eval 语句或调用一些不必要的shell 进程来“执行您的变量”。

【讨论】:

我将存储的命令将取决于我发送的选项,因此我的程序中没有大量的条件语句,而是存储我需要的命令以供以后使用要容易得多。 @Benjamin,然后至少将选项存储为变量,而不是命令。例如var='*.txt'; find . -name "$var"

以上是关于如何将命令存储在 shell 脚本的变量中?的主要内容,如果未能解决你的问题,请参考以下文章

如何在shell脚本中执行cd命令

需要从 Python 脚本运行 shell 命令并存储在变量中 [重复]

Linux Shell 脚本:将命令输出保存在变量/文件中?

如何将执行的shell命令的结果存储在python的变量中? [复制]

如何在 Ruby 脚本中为命令 shell 获取环境变量?

shell脚本等的操作