【问题标题】:How can I store a command in a variable in a shell script?如何将命令存储在 shell 脚本的变量中?
【发布时间】: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

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

【问题讨论】:

标签: linux bash variables command


【解决方案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"
    猜你喜欢
    • 2018-11-06
    • 2020-08-30
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多