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

我希望将一个命令存储在一个变量中(不是命令的输出,而是命令本身) ,以便以后使用。

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

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

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

273190 次浏览

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

使用 eval:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"
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。仅对您控制的字符串进行求值,绝不要使用来自不受信任用户的字符串或由不受信任用户输入生成的字符串。

没有使用 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'
}

并将函数调用为 just

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

既然已经定义了变量,那么就使用数组来存储命令 args

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

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

curl "${curlCMD[@]}"

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

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

像这样运行你的命令:

echo $command | bash

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

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

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

Be 小心点用: X=$(Command)注册订单

这个程序仍然在执行。甚至在被调用之前。要检查和确认这一点,您可以这样做:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed
#!/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.

首先,这里有一些函数。但是如果你更喜欢变量,那么你的任务可以这样做:

$ 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"

不知道为什么这么多答案,使它复杂! 使用 alias[ command ]‘ string 来执行’ 例如:

alias dir='ls -l'


./dir
[pretty list of files]

我用以下命令面对这个问题:

awk '{printf "%s[%s]\n", $1, $3}' "input.txt"

我需要动态构建这个命令:

目标文件名 input.txt是动态的,可能包含空间。

{}大括号 printf "%s[%s]\n", $1, $3中的 awk脚本是动态的。

挑战:

  1. 如果 awk脚本中有很多 ",避免大量的引号转义逻辑。
  2. 避免对每个 $字段变量进行参数扩展。

下面的解决方案与 eval命令和联合 arrays不工作。由于 bash变量扩展和报价。

解决方案:

动态构建 bash变量,避免 bash扩展,使用 printf模板。

 # dynamic variables, values change at runtime.
input="input file 1.txt"
awk_script='printf "%s[%s]\n" ,$1 ,$3'


# static command template, preventing double-quote escapes and avoid variable  expansions.
awk_command=$(printf "awk '{%s}' \"%s\"\n" "$awk_script" "$input")
echo "awk_command=$awk_command"


awk_command=awk '{printf "%s[%s]\n" ,$1 ,$3}' "input file 1.txt"

执行变量命令:

bash -c "$awk_command"

另一个办法也行得通

bash << $awk_command

由于您没有指定任何脚本语言,我建议使用 tcl,即 工具命令语言

然后在第一行,添加适当的 shebang:

#!/usr/local/bin/tclsh

与适当的位置,您可以检索与 which tclsh

tcl脚本中,可以使用 exec调用操作系统命令。