如何将命令存储在 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;
-- 之后您可以运行 Command
或 result=$(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
只包含date
时eval $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 && 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
不会这样做(它基本上等同于没有 -o
的 grep -c
)。
仅限 Bash 的评论很奇怪;这个简单的脚本中没有任何内容与任何 Bourne 家族的 shell 不兼容。【参考方案10】:
即使您以后需要使用它,也不必将命令存储在变量中。照常执行即可。如果您存储在变量中,则需要某种eval
语句或调用一些不必要的shell 进程来“执行您的变量”。
【讨论】:
我将存储的命令将取决于我发送的选项,因此我的程序中没有大量的条件语句,而是存储我需要的命令以供以后使用要容易得多。 @Benjamin,然后至少将选项存储为变量,而不是命令。例如var='*.txt'; find . -name "$var"
以上是关于如何将命令存储在 shell 脚本的变量中?的主要内容,如果未能解决你的问题,请参考以下文章
需要从 Python 脚本运行 shell 命令并存储在变量中 [重复]
Linux Shell 脚本:将命令输出保存在变量/文件中?