在 bash 函数参数中保留引号

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3260920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 19:28:36  来源:igfitidea点击:

Preserving quotes in bash function parameters

bashfunctionargumentsquotes

提问by ezpz

What I'd like to do is take, as an input to a function, a line that may include quotes (single or double) and echo that line exactly as it was provided to the function. For instance:

我想做的是将一行可能包含引号(单引号或双引号)的行作为函数的输入,并完全按照提供给函数的方式回显该行。例如:

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

Which, given the following input

其中,给定以下输入

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

Yields the following:

产生以下结果:

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

So the semantics of the variable expansion are preserved as I'd expect, but I can not get the exact format of the line as it was provided to the function. What I'd like is to have doit echo 'single quote $VAR'result in echo 'single quote $VAR'.

因此,变量扩展的语义按照我的预期保留了下来,但是我无法获得提供给函数的行的确切格式。我想要的是doit echo 'single quote $VAR'结果在echo 'single quote $VAR'.

I'm sure this has to do with bash processing the arguments before they are passed to the function; I'm just looking for a way around that (if possible).

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

Edit

编辑

So what I had intended was to shadow the execution of a script while providing an exact replica of the execution that could be used as a diagnostic tool including exit status of each step.

因此,我的意图是在提供执行的精确副本的同时隐藏脚本的执行,该副本可用作诊断工具,包括每个步骤的退出状态。

While I canget the desired behavior described above by doing something like

虽然我可以通过执行类似的操作获得上述所需的行为

while read line ; do 
   doit ${line}
done < ${INPUT}

That approach fails in the face of control structures (i.e. if, while, etc). I thought about using set -xbut that has it's limitations as well: "becomes 'and exit status is not visible for commands that fail.

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

采纳答案by Peter Westlake

The reason this happens is because bash interprets the arguments, as you thought. The quotes simply aren't there any more when it calls the function, so this isn't possible. It worked in DOS because programs could interpret the command line themselves, not that it helps you!

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

回答by Dave Webb

I was in a similar position to you in that I needed a script to wrap around an existing command and pass arguments preserving quoting.

我与您的情况类似,因为我需要一个脚本来环绕现有命令并传递参数以保留引用。

I came up with something that doesn't preserve the command line exactly as typed but does pass the arguments correctly and show you what they were.

我想出了一些东西,它不会完全按照输入的方式保留命令行,但会正确传递参数并向您展示它们是什么。

Here's my script set up to shadow ls:

这是我设置为 shadow 的脚本ls

CMD=ls
PARAMS=""

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

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

And this is some sample output:

这是一些示例输出:

$ ./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

So as you can see it adds quotes which weren't originally there but it does preserve arguments with spaces in which is what I needed.

因此,正如您所看到的,它添加了最初不存在的引号,但它确实保留了我需要的带有空格的参数。

回答by Chen Levy

Although @Peter Westlake's answeris correct, and there are no quotes to preserve one can try to deduce if the quotes where required and thus passed in originally. Personally I used this requotefunction when I needed a proof in my logs that a command ran with the correct quoting:

尽管@Peter Westlake答案是正确的,并且没有引号可以保留,但可以尝试推断引号是否需要并因此最初传入。就个人而言,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}"
}

And here is how I use it:

这是我如何使用它:

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

回答by Peter

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

Both will work.

两者都会起作用。

bash will only strip the outside set of quotes when entering the function.

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

回答by ttchong

Bash will remove the quote when you pass a string with quote in as command line argument. The quote is simply not there anymore when the string is pass to your script. You have no way to know there is a single quote or double quote.

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

What you probably can do is sth like this:

你可能可以做的是这样的:

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

In your script you get

在你的脚本中你得到

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

Or do this

或者这样做

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

In your script you get

在你的脚本中你得到

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

回答by Robert Vila

This:

这个:

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

As an example has problems when the parameters have apostrophes.

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

This function:

这个功能:

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

solves the mentioned problem and you can use parameters including apostrophes inside, like "Porky's", and returns, apparently(?), the same string of parameters when each parameter is quoted; if not, it quotes it. Surprisingly, I don't understand why, if you use it recursively, it doesn't return the same list but each parameter is quoted again. But if you do echo of each one you recover the original parameter.

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

Example:

例子:

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

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

And:

和:

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

And this one:

和这个:

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

returning one level of quotation less, doesn't work either, neither alternating both recursively.

少返回一级引用,也不起作用,既不递归交替。

回答by bta

The shell is going to interpret the quotes and the $before it passes it to your function. There's not a lot your function can do to get the special characters back, because it has no way of knowing (in the double-quote example) whether 42was hard-coded or if it came from a variable. You will have to escape the special characters if you want them to survive long enough to make it to your function.

shell 将解释引号和$它传递给你的函数之前的引号。您的函数不能做很多事情来恢复特殊字符,因为它无法知道(在双引号示例中)42是硬编码还是来自变量。如果您希望特殊字符能够存活足够长的时间以使其发挥作用,您将不得不转义这些特殊字符。

回答by Greg A. Woods

If one's shell does not support pattern substitution, i.e. ${param/pattern/string}then the following sedexpression can be used to safely quote any string such that it will evalinto a single parameter again:

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

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

Combining this with printfit is possible to write a little function that will take any list of strings produced by filename expansion or "$@"and turn it into something that can be safely passed to evalto expand it into arguments for another command while safely preserving parameter separation.

结合这一点,printf可以编写一个小函数,该函数将获取由文件名扩展产生的任何字符串列表,或者"$@"将其转换为可以安全传递的内容,eval以将其扩展为另一个命令的参数,同时安全地保留参数分离。

# 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"
}

It may be easier (and maybe safer, i.e. avoid eval) in some situations to use an "impossible" character as the field separator and then use IFSto control expansion of the value again.

eval在某些情况下,使用“不可能”字符作为字段分隔符,然后IFS再次用于控制值的扩展可能更容易(也可能更安全,即避免)。