如何在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
这种方法在控制结构(即if
、while
等)面前失败了。我考虑过使用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脚本中通过函数调用将参数/参数从一个函数传递到另一个函数[重复]