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

Linux 如何在shell脚本的变量中存储命令?,linux,bash,variables,command,Linux,Bash,Variables,Command,我想将稍后使用的命令存储在变量中(不是命令的输出,而是命令本身) 我有一个简单的脚本,如下所示: command="ls"; echo "Command: $command"; #Output is: Command: ls b=`$command`; echo $b; #Output is: public_html REV test... (command worked successfully) 然而,当我尝试一些更复杂的东西时,它失败了。例如,如果我 command="ls | gre

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

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

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

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

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

使用eval:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"
使用此方法,将立即计算命令并存储其返回值

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
与backtick相同

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
对于bash脚本来说,这很少有关系,但需要注意的是最后一点。小心使用
eval
。仅评估您控制的字符串,从不评估来自不受信任用户或由不受信任用户输入生成的字符串

  • 感谢@CharlesDuffy提醒我引用命令
      不要使用
      eval
      !它有引入任意代码执行的主要风险

      -我试图将命令放入变量中,但复杂的情况总是失败。 将其放入一个数组中,并用双引号展开所有单词
      “${arr[@]}”
      ,以避免
      IFS
      因错误而拆分单词

      并查看内部数组的内容。
      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
      。您始终可以像下面这样定义参数,并在扩展的数组内容上调用
      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[@]}"
      

      对于bash,按如下方式存储命令:

      command="ls | grep -c '^'"
      
      echo $command | bash
      
      $ 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
      
      按如下方式运行命令:

      command="ls | grep -c '^'"
      
      echo $command | bash
      
      $ 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
      

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

      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
      

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

      使用以下命令注册订单时要小心:
      X=$(命令)

      这一次仍在执行中 甚至在被叫之前。 要检查并确认这一点,您可以执行以下操作:

      echo test;
      X=$(for ((c=0; c<=5; c++)); do
      sleep 2;
      done);
      echo note the 5 seconds elapsed
      
      echo测试;
      
      X=$(对于((c=0;c首先,有用于此的函数。但是如果您喜欢VAR,那么您的任务可以这样完成:

      command="ls | grep -c '^'"
      
      echo $command | bash
      
      $ 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
      

      我将存储的命令将取决于我发送的选项,因此我的程序中没有大量的条件语句,而是更容易存储我以后使用的命令。@Benjamin,然后至少将选项存储为变量,而不是命令。例如
      var='*.txt';find.-name“$var”
      $(…)现在建议使用,而不是
      backticks
      .y=$(eval$x)
      eval
      是一种可接受的做法,前提是您信任变量的内容。如果您正在运行,比如说,
      x=“ls$name | wc”
      (甚至
      x=“ls'$name'| wc”
      ),则此代码是注入或权限提升漏洞的快速通道,如果该变量可以由权限较低的人设置。(例如,迭代
      /tmp
      中的所有子目录?您最好信任系统上的每个用户,不要将其称为
      $'/tmp/evil-$(rm-rf$HOME)\'$(rm-rf$HOME)\“/”
      )。
      eval
      是一个巨大的漏洞磁铁,如果没有关于意外解析行为风险的警告(即使没有恶意字符串,如@CharlesDuffy的示例),也不应该推荐它。例如,尝试
      x='echo$(6*7)),
      然后
      eval$x
      。您可能希望打印“42”,但它可能不会。你能解释为什么它不起作用吗?你能解释为什么我说“可能”?如果这些问题的答案对你来说不明显,你不应该触摸
      eval
      @Student,试着事先运行
      set-x
      来记录命令的运行,这将更容易看到发生了什么。@Student我还建议你指出常见的错误(以及你不应该养成的坏习惯)。使用函数!请参阅本文:。这并不能解决命令中包含管道“|”的原始问题。@Nate,请注意,
      eval$stored_date
      如果
      stored_date
      仅包含
      date
      ,则可能足够了,但是
      eval“$stored_date”
      更可靠。运行
      str=$'printf\*%s\\n\*”;eval“$str”
      最后一个
      “$str”
      前后加引号,例如:)@Charlesduff谢谢,我忘了引用。我敢打赌,如果我费心运行它,我的linter会抱怨的。切记,这是一个@Student,如果原始字符串包含管道,那么该字符串需要经过bash解析器的不安全部分才能作为代码执行。在这种情况下,不要使用字符串,而是使用函数:
      Command(){echo aaa | grep a;}
      --之后您可以运行
      命令
      ,或
      结果=$(命令)
      ,或类似操作。@Student,对;