Bash:执行连接变量

Bash:执行连接变量

我有一个 Linux bash 脚本,其效果如下:

COMMAND_TO_EXECUTE="foo "$1" --option1 --option2 "$2
exec $COMMAND_TO_EXECUTE

问题:脚本失败

调试线索,如果我echo $COMMAND_TO_EXECUTE将其剪切/粘贴到终端窗口中,它就能完美运行。

所以……后面的字符串$COMMAND_TO_EXECUTE在终端中有效,但在脚本中无效。$COMMAND_TO_EXECUTE在尝试执行它之前我应该​​做些什么吗?

放大信息:要执行的命令是wgetcurl,我对这两个命令都遇到了同样的问题。(我已正确引用字符串和转义字符,如 &)。如前所述,如果我回显,然后剪切/粘贴,该命令将正常工作。

我很困惑,觉得我错过了一些基本的东西,因为命令有效剪切/粘贴但不是在脚本中。

更新:以下 Polyergic 的回复对我有用。bash -c "$COMMAND_TO_EXECUTE"运行正常。

答案1

我使用的习语是这样的:

cmd="some command in a string"
"$cmd"

请注意,与使用不同exec,调用脚本将继续。

如果您的命令包含特殊字符,可能需要进行一些修改。

这是我使用过的另一种变体(我不记得为什么这样做):

cmd="some command in a string"
bash -c "$cmd"

这是一个完整的示例脚本,我用它来抑制 cronjobs 的预期输出:

#!/bin/sh

# otrap
# Copyright © 2007-2014 Shad Sterling <[email protected]>
# Released under CC-BY-SA, http://creativecommons.org/licenses/by-sa/4.0/

if [ "" = "$2" ]; then
    echo traps the output of a command and echos the output only if the size doesn\'t match
    echo USAGE: $0 \<size\> [\"test\"] \<command\>
    echo size is the expected output size
    echo \"test\" means echo the output even if the size does match
    echo command is the command to silence unless it\'s output doesn\'t match 
    exit 2
fi;

test=false
size=$1; shift
echo=false
if [ "test" == "$1" ]; then
test=true; shift
echo=true
fi
cmd="$*"

TF=~/tmp/otrap.#$PPID.log
if [ "false" != "$echo" ]; then
    echo file: "$TF"
    echo running: "$cmd"
fi
starttime=`date +%R`
$SHELL -c "$cmd" > $TF 2>&1
ret=$?
endtime=`date +%R`
ST=`\`dirname $0\`/filesize $TF 2>&1`
if [ "$size" != "$ST" ]; then
    echo=true;
fi;
if [ "false" != "$echo" ]; then
    echo " command:" "$cmd"
    echo "   start:" $starttime
    echo "  finish:" $endtime
    echo "returned:" $ret
    echo "    size:" $ST
    echo "expected:" $size
    echo --------------------------------------------
    cat $TF
fi
rm $TF
exit $ret

答案2

简短回答:参见BashFAQ #50:我尝试将命令放入变量中,但复杂的情况总是失败!

长答案:当 shell 解析命令行时,它会做一些事情,比如找出该行的哪些部分在引号中(或转义或其他)它会替换变量;因此,如果变量值中有任何引号或转义符,那么当它们被替换到命令中时,它们已经无法执行任何操作了。它确实会对替换变量的值进行一些解析:它根据空格、制表符等将它们拆分为“单词”(无论它们是否在引号中),并扩展通配符(同样,即使它们在引号中)。顺便说一句,其他一些 shell 语法(如管道、重定向等)也已经无法生效了。为了说明这一点,我有一个printargs打印其参数的命令。当我尝试将复杂命令存储在变量中时,会发生以下情况:

$ cmd='printargs " * " | cat >outfile &'
$ $cmd
Got 8 arguments:
    '"'
    'file1.txt'
    'file2.txt'
    '"'
    '|'
    'cat'
    '>outfile'
    '&'

请注意,引号、竖线等都被视为普通字符而不是 shell 语法,但星号被视为通配符并由文件名列表替换。

有几种解决方案,具体取决于您首先将命令放入变量的原因:

  • 如果你真的不需要将命令放在变量中,。命令是用来执行的,所以除非有充分理由不执行,否则就直接执行它。

  • 如果您想多次使用本质上相同的命令并且不想每次都写出整个内容(编程的“不要重复自己”规则),请使用函数:

    execute_command() {
        foo "$1" --option1 --option2 "$2"
    }
    

    ...然后反复调用它。请注意,我将变量引用放在双引号中;您应该(几乎)始终这样做,以防止对它们应用分词和通配符扩展。

  • 如果需要动态构建命令,请使用数组:

    post_data=("var1=value1" "var2=value2" ...)
    
    post_args=()
    for post_arg in "${post_data{@]}"; do   # Note that this is the correct idiom for expanding an array in bash
        post_data+=(-d "$post_arg")
    done
    curl "${post_args[@]}" "$url"
    

请注意,这适用于单个命令的复杂参数,但不适用于管道、重定向和后台处理(&),因为这些在变量替换之前再次被解析。

最后,一些警告:

  • 除非你知道,否则eval不要使用bash -c确切地shell 解析是如何工作的(如果你问了这个问题,说明你并不确切地知道 shell 解析是如何工作的)。这两种方法都会导致额外的解析层,这在测试中往往效果很好,但偶尔也会失败(原因难以理解)。它eval以产生真正奇怪和微妙的错误而闻名;并且bash -c本质上做同样的事情,只是加入了一个子 shell,让它变得更加奇怪。

  • 双引号变量引用可以防止意外的单词拆分和通配符扩展。

  • 您可能不想使用exec——它退出当前 shell(&shell 脚本),并用命令替换它;这可能不是您想要的。

答案3

由于您输入的内容COMMAND-TO-EXECUTE不是有效的 shell 变量名,请删除破折号。这可能会提供一些线索:

$ echo $命令-TO-EXECUTE
-执行

$ 命令执行=测试
COMMAND-TO-EXECUTE=测试:未找到命令

$COMMAND_TO_EXECUTE=测试

$ 回显 $COMMAND_TO_EXECUTE
测试

$

然后"foo "$1" --option1 --option2 "$2看起来有点奇怪。如果你想COMMAND_TO_EXECUTE包含"- 个字符,请将其更改为"foo \"$1\" --option1 --option2 $2"- 另请注意,我移动了末尾的引号。这样,你就可以“保护” $2 以免在将其留给 之前造成奇怪的效果exec

由于您没有提供实际的示例命令来尝试/调试,所以我想不出更多......

相关内容