没有misescaping的eval bash函数参数

时间:2018-05-12 03:56:47

标签: bash

在Bash脚本中,我想创建一个包装命令的函数,在执行它们之前打印它们。

所以,在这样的脚本命令中:

mkdir -p "~/new/dir/tree/"
rsync -e 'ssh -p 22' -av "src/" "user@${HOST}:dest/"

我可以像其他人一样把命令放在其他人面前:

run mkdir -p "~/new/dir/tree/"
run rsync -e 'ssh -p 22' -av "src/" "user@${HOST}:dest/"

我将函数定义为:

run (){
    echo -e "FANCY FORMATING CMD> $@ FANCY FORMAT ENDING"
    eval "$@"
    return $?
}

大多数情况下都能正常工作。当我需要run前面的命令时,无论是删除run部分还是取消设置run,它都可以使用它或不使用它。我的意思是,所有三个命令都应该以完全相同的方式工作:

run original_comand arg1 "arg2 subarg2" etc;
unset run;
run original_comand arg1 "arg2 subarg2" etc;
original_comand arg1 "arg2 subarg2" etc;

但如果我尝试这一行:

run rsync -e 'ssh -p 22' -av "src/" "user@${HOST}:dest/"

-e参数变为不带引号且实际运行的命令是

run rsync -e ssh -p 22 -av src/ user@${HOST}:dest/

如果我在-e参数上转义引号,它可能与run命令一起使用,但没有它就行不了。

就我从Bash文档中读到的那样,"$@"应该有效,但显然我在这里遗漏了一些东西。

2 个答案:

答案 0 :(得分:1)

传递给run的参数在传递给它们时已经经历了各种扩展,并且已经正确定位,因此您不需要eval来调用{}中的命令{1}}。实际上,在这种情况下使用$1是错误的(错误地在另一次错误地应用扩展和分词),正如您可以通过大量错误消息所知道的那样。

eval中调用命令并将$1中的所有参数(第一个除外,即"$@")传递给它的正确方法是简单地放"${@:2}"在没有"$@"的单行上。

答案 1 :(得分:0)

通过调用您的函数,您的参数不会“未引用”。通过将它们放入一个字符串并回显它们,它们是“不加引号”的。引号不是参数的一部分,而是shell中防止分词的特殊字符(在单引号的情况下,变量扩展)。

shell执行分词(引用保护),然后引用删除。这意味着引号将被删除,是的,但参数不会在引用的空格上拆分。

utility a "b c"

该实用程序不会将"b c"作为其第二个参数,而是b c(不是bc,而是b<space>c)。你的功能也是如此。

这表明您仍然在函数中获得了正确分隔的参数:

#!/bin/sh

run () {
    printf 'Arg: %s\n' "$@"
}

HOST=example.com

run mkdir -p "~/new/dir/tree/"
echo '---'
run rsync -e 'ssh -p 22' -av "src/" "user@${HOST}:dest/"

这会生成

Arg: mkdir
Arg: -p
Arg: ~/new/dir/tree/
---
Arg: rsync
Arg: -e
Arg: ssh -p 22
Arg: -av
Arg: src/
Arg: user@example.com:dest/

正如您所看到的,ssh -p 22仍作为单独的参数传递给printf,而不是三个参数。

在你的函数中执行eval "$@"会做正确的事情。转义引号肯定做正确的事情,因为它会在实际参数中包含引号(mkdir \"dir\"会创建一个名为"dir"的目录,包括引号,以及{ {1}}将rsync -e \"ssh -p 22\"rsync-e"ssh传递给-p

另请注意,22"未展开,因为它是双引号。请改用~