保留bash函数参数中的引号

时间:2010-07-15 23:44:04

标签: bash function arguments quotes

我想要做的是,作为函数的输入,可以包含引号(单引号或双引号)的行,并且回显与提供给函数的行完全相同。例如:

function doit {
   printf "%s " ${@} 
   eval "${@}"
   printf " # [%3d]\n" ${?}
}

其中,给出以下输入

doit VAR=42
doit echo 'single quote $VAR'
doit echo "double quote $VAR"

产生以下内容:

VAR=42  # [  0]
echo single quote $VAR  # [  0]
echo double quote 42  # [  0]

因此我可以保留变量扩展的语义,但是我无法获得提供给函数的行的确切格式。我希望将doit echo 'single quote $VAR'结果导入echo 'single quote $VAR'

我确信这与bash在传递给函数之前处理参数有关;我只是想找个方法(如果可能的话)。

修改

所以我的目的是影响脚本的执行,同时提供执行的精确副本,可以用作诊断工具,包括每个步骤的退出状态。

虽然我可以通过执行类似

的操作获得上述所需的行为
while read line ; do 
   doit ${line}
done < ${INPUT}

这种方法在控制结构面前失败(即ifwhile等)。我考虑过使用set -x,但也存在限制:"变为',退出状态对于失败的命令不可见。

8 个答案:

答案 0 :(得分:10)

我处于类似的位置,因为我需要一个脚本来包装现有命令并传递保持引用的参数。

我想出的一些东西并没有完全按照键入的方式保留命令行,而是正确地传递了参数并告诉你它们是什么。

这是我的脚本设置为阴影ls

CMD=ls
PARAMS=""

for PARAM in "$@"
do
  PARAMS="${PARAMS} \"${PARAM}\""
done

echo Running: ${CMD} ${PARAMS}
bash -c "${CMD} ${PARAMS}"
echo Exit Code: $?

这是一些示例输出:

$ ./shadow.sh missing-file "not a file"
Running: ls "missing-file" "not a file"
ls: missing-file: No such file or directory
ls: not a file: No such file or directory
Exit Code: 1

因为你可以看到它添加了原来不存在的引号,但它确实保留了带有空格的参数,而这正是我所需要的。

答案 1 :(得分:7)

发生这种情况的原因是因为bash正如您所想的那样解释参数。当它调用函数时,引号根本不再存在,所以这是不可能的。它在DOS下工作,因为程序可以自己解释命令行,而不是它可以帮助你!

答案 2 :(得分:4)

虽然@Peter Westlake&#39; answer是正确的,并且没有引号可以保留,但可以尝试推断出引用是否在需要的地方并因此最初传入。就个人而言,当我在日志中需要一个使用正确引用运行命令的证明时,我使用了这个requote函数:

function requote() {
    local res=""
    for x in "${@}" ; do
        # try to figure out if quoting was required for the $x:
        grep -q "[[:space:]]" <<< "$x" && res="${res} '${x}'" || res="${res} ${x}"
    done
    # remove first space and print:
    sed -e 's/^ //' <<< "${res}"
}

以下是我如何使用它:

CMD=$(requote "${@}")
# ...
echo "${CMD}"

答案 3 :(得分:3)

doit echo "'single quote $VAR'"
doit echo '"double quote $VAR"'

两者都有效。

bash只会在输入函数时删除外部引号集。

答案 4 :(得分:1)

当您使用quote in作为命令行参数传递字符串时,Bash将删除引号。当字符串传递给您的脚本时,引用就不再存在了。你无法知道有单引号或双引号。

你可能做的就是这样:

doit VAR=42
doit echo \'single quote $VAR\'
doit echo \"double quote $VAR\"

在你的脚本中你得到了

echo 'single quote $VAR'
echo "double quote $VAR"

或者这样做

doit VAR=42
doit echo 'single quote $VAR'
doit echo '"double quote $VAR"'

在你的脚本中你得到了

echo single quote $VAR
echo "double quote $VAR"

答案 5 :(得分:1)

此:

ponerApostrofes1 () 
{
    for (( i=1; i<=$#; i++ ));
    do
        eval VAR="\${$i}"; 
        echo \'"${VAR}"\';
    done; 
    return; 
}

例如,当参数有撇号时会出现问题。

此功能:

ponerApostrofes2 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n \'${PARAM//\'/\'\\\'\'}\'' ';
    done;
    return
}

解决了上面提到的问题,你可以使用包含撇号的参数,比如“Porky's”,并且当引用每个参数时,显然(?)返回相同的参数字符串;如果没有,它引用它。令人惊讶的是,我不明白为什么,如果你递归地使用它,它不会返回相同的列表,但每个参数再次被引用。但如果你对每个回声做了回音,你就恢复了原始参数。

示例:

$ ponerApostrofes2 'aa aaa' 'bbbb b' 'c' 
'aa aaa' 'bbbb b' 'c'

$ ponerApostrofes2 $(ponerApostrofes2 'aa aaa' 'bbbb b' 'c' )
''\''aa' 'aaa'\''' ''\''bbbb' 'b'\''' ''\''c'\''' 

$ echo ''\''bbbb' 'b'\'''
'bbbb b'
$ echo ''\''aa' 'aaa'\'''
'aa aaa'
$ echo ''\''c'\''' 
'c'

这一个:

ponerApostrofes3 () 
{ 
    for ((i=1; i<=$#; i++ ))
    do
        eval PARAM="\${$i}";
        echo -n ${PARAM//\'/\'\\\'\'} ' ';
    done;
    return
}

返回一级报价, 也不起作用,也没有递归交替。

答案 6 :(得分:0)

在将函数传递给函数之前,shell将解释引号和$。你的函数可以做很多事情来获取特殊字符,因为它无法知道(在双引号示例中)42是否是硬编码的,还是来自变量。如果您希望它们存活足够长的时间以使其成为您的功能,您将不得不逃避特殊字符。

答案 7 :(得分:0)

如果一个shell不支持模式替换,即${param/pattern/string},则可以使用以下sed表达式来安全地引用任何字符串,使其eval成为单个参数:

sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"

将此与printf结合使用,可以编写一个小函数,该函数将采用文件名扩展或"$@"生成的任何字符串列表,并将其转换为可安全传递给{{1}的内容在安全地保留参数分离的同时将它扩展为另一个命令的参数。

eval

在某些情况下使用&#34;不可能的&#34;可能更容易(也许更安全,即避免# Usage: quotedlist=$(shell_quote args...) # # e.g.: quotedlist=$(shell_quote *.pdf) # filenames with spaces # # or: quotedlist=$(shell_quote "$@") # # After building up a quoted list, use it by evaling it inside # double quotes, like this: # # eval "set -- $quotedlist" # for str in "$@"; do # # fiddle "${str}" # done # # or like this: # # eval "\$a_command $quotedlist \$another_parameter" # shell_quote() { local result='' local arg for arg in "$@" ; do # Append a space to our result, if necessary # result=${result}${result:+ } # Convert each embedded ' to \' , then insert ' at the # beginning of the line, and append ' at the end of # the line. # result=${result}$(printf "%s\n" "$arg" | \ sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/") done # use printf(1) instead of echo to avoid weird "echo" # implementations. # printf "%s\n" "$result" } )。将字符作为字段分隔符,然后使用eval再次控制值的扩展。