如何在bash函数参数中引用?

Posted

技术标签:

【中文标题】如何在bash函数参数中引用?【英文标题】:How to quotes in bash function parameters? 【发布时间】:2011-03-16 17:05:00 【问题描述】:

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

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,但这也有其局限性:" 变为',并且对于失败的命令不可见退出状态。

【问题讨论】:

相关:How do I use a Bash variable (string) containing quotes in a command? 另一相关:Preserve Quotes in bash arguments 【参考方案1】:

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

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

这是我设置为影子 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

如您所见,它添加了最初不存在的引号,但它确实保留了带有空格的参数,这是我需要的。

【讨论】:

这在表面上可能没问题,但你真的需要use arrays for collecting parameters。【参考方案2】:

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

【讨论】:

【参考方案3】:

尽管@Peter Westlake 的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"

【讨论】:

这目前不处理在其内容中带有文字单引号的参数;想想$'\'"$(rm -rf $HOME)"\''。此外,$(foo) 不包含任何空格,但仍需要引用以确保安全。 最好使用printf %q,shell 保证会生成eval-safe 输出。 另外,printf '%s\n' "$res# " 是一种更有效的方式来执行删除第一个空格和打印的事情。而[[ $x = *[[:space:]]* ]] 是一种更有效的方法来检查变量是否包含空格。 我仍然在加快高端 bash 的速度,但 for x in "$@" ; do 不正是因为我们一直在讨论的原因而只迭代一次吗?它不会将所有参数收集到一个带空格的字符串中吗?我一直在研究一些传递参数的函数(这就是我阅读这个帖子的原因)。 @cycollins,在这个问题的上下文中,"$@" 只是解决方案的一部分。每次 Bash 计算某个表达式时,它都会去掉最外层的引号。虽然"$@" 保留了间距,但它不能保留引号本身,因为它已经被剥离了。一个很好的例子可以在this SO ansere中找到。【参考方案4】:
doit echo "'single quote $VAR'"
doit echo '"double quote $VAR"'

两者都可以。

bash 只会在输入函数时去掉外面的引号。

【讨论】:

这将在 OP 希望保留变量时替换变量,反之亦然。 这是我需要的答案。所以 "$@" 在 $@ 的值中保留了单引号。【参考方案5】:

当您将带有引号的字符串作为命令行参数传递时,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"

【讨论】:

【参考方案6】:

这个:

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

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

【讨论】:

【参考方案7】:

如果一个人的 shell 不支持模式替换,即$param/pattern/string,那么下面的sed 表达式可以用来安全地引用任何字符串,这样它就会再次将eval 变成一个参数:

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

将其与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"

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

【讨论】:

【参考方案8】:

shell 将在将引号和$ 传递给您的函数之前对其进行解释。您的函数无法做很多事情来取回特殊字符,因为它无法知道(在双引号示例中)42 是硬编码的还是来自变量。如果您希望特殊字符能够存活足够长的时间以实现您的功能,则必须转义它们。

【讨论】:

以上是关于如何在bash函数参数中引用?的主要内容,如果未能解决你的问题,请参考以下文章

Bash中的位置参数和特殊参数

如何使用参数制作 Bash 函数? [复制]

如何在bash脚本中通过函数调用将参数/参数从一个函数传递到另一个函数[重复]

如何将文件路径作为 bash 脚本中的参数传递给函数? [复制]

shell中的参数引用

如何在查询中引用函数参数?