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


113

我想将要在以后使用的命令存储在变量中(不是命令的输出,而是命令本身)

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

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

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


10
使用功能!
gniourf_gniourf 2015年

Answers:


146

使用eval:

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

27
现在建议使用$(...)代替backticks。y = $(eval $ x) mywiki.wooledge.org/BashFAQ/082
James Broadhead,

14
eval当您信任变量的内容时,可以接受。例如,如果您正在运行x="ls $name | wc"(或什至x="ls '$name' | wc"),那么该代码可以快速访问注入或特权升级漏洞,前提是该特权可以由特权较少的人设置。(/tmp例如,遍历中的所有子目录?您最好让系统上的每个用户都不要调用$'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/')。
查尔斯·达菲

9
eval是一个巨大的错误吸引者,如果没有关于意外解析行为的风险的警告(即使没有恶意字符串,如@CharlesDuffy的示例),也永远不要建议这样做。例如,尝试x='echo $(( 6 * 7 ))'然后eval $x。您可能希望将其打印为“ 42”,但可能不会。您能解释一下为什么它不起作用吗?你能解释为什么我说“大概”吗?如果这些问题的答案对您而言并不明显,则切勿触摸eval
Gordon Davisson

1
@Student,尝试set -x预先运行以记录运行的命令,这将使您更容易查看正在发生的情况。
查尔斯·达菲,

1
@学生我也建议shellcheck.net指出常见的错误(以及不应该养成的不良习惯)。
Gordon Davisson

41

千万不能使用eval!引入任意代码执行的主要风险。

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

把它放在一个阵列,并展开所有用双引号的话"${arr[@]}"没有IFS分裂由于单词分词

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没有数组,因此最接近的是在位置参数中建立元素列表。这是sh运行邮件程序的POSIX 方法

# 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"'"'

现在已经定义了变量,请使用数组存储命令args

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

现在进行适当的引用扩展

curl "${curlCMD[@]}"

这并不为我工作,我曾尝试Command=('echo aaa | grep a')"${Command[@]}",希望它的字面运行命令echo aaa | grep a。没有。我想知道是否有安全的替换方法eval,但是似乎每个解决方案都具有相同的作用力,eval因此可能很危险。是不是
学生

简而言之,如果原始字符串包含管道'|',这将如何工作?
学生

@Student,如果您的原始字符串包含管道,则该字符串需要通过bash解析器的不安全部分才能作为代码执行。在这种情况下,请勿使用字符串。请改用一个函数:Command() { echo aaa | grep a; }-之后,您可以运行Command,或result=$(Command)等等。
查尔斯·达菲

1
@学生,对;但这故意失败了,因为您要求做的事情本质上是不安全的
查尔斯·达菲

1
@学生:我最后添加了一条便条,提到它在某些情况下
不起作用

25
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。仅评估您控制的字符串,绝不评估来自不受信任用户或由不受信任用户输入构建的字符串。

  • 感谢@CharlesDuffy提醒我引用命令!

这不能解决命令包含管道“ |”的原始问题。
学生

@Nate,请注意,eval $stored_date如果stored_date仅包含date,可能就足够了,但eval "$stored_date"更加可靠。例如,在str=$'printf \' * %s\\n\' *'; eval "$str"有和没有引号的情况下运行"$str"。:)
Charles Duffy

@CharlesDuffy谢谢,我忘了报价。我敢打赌,如果我不愿意运行它,我的短毛猫会抱怨。
内特

0

对于bash,请像这样存储您的命令:

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

像这样运行您的命令:

echo $command | bash

1
不确定,但也许这种运行命令的方式与使用“ eval”一样具有相同的风险。
德里克·哈泽尔

0

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

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

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


0

在以下情况下谨慎注册订单: X=$(Command)

即使在被调用之前,该命令仍会执行。要检查并确认这一点,您可以执行以下操作:

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

-1
#!/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.

-8

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


1
我将存储的命令将取决于我发送的选项,因此与其在程序的大部分中没有大量的条件语句,不如存储我以后使用的命令要容易得多。
本杰明

1
@Benjamin,然后至少将选项存储为变量,而不是命令。例如var='*.txt'; find . -name "$var"
kurumi 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.