在Bash中,是否可以编写一个“identity”函数(相对于参数传递/返回)?

3
这样的“身份”函数应满足以下2个特性:
identity $(identity a\ b c\ d)
# (Expected output:)
# a b
# c d

假设有以下的'argv_count'函数:

argv_count () { echo "argv_count('$@'):$#"; }
argv_count $(identity a\ b c\ d)
# (Expected output:)
# argv_count('a b c d'):2

如果需要,测试中可以引入其他引用。

像下面这样的简单候选者未能通过第二个测试:

identity () { for arg in "$@"; do echo "$arg"; done; }

cat不是一个正确的解决方案,因为它是相对于stdin | stdout的身份函数。

2个回答

6
不,这是不可能的。原因在于当 shell 解析命令替换(即 $(somecommand))的输出时,它执行了单词拆分和通配符扩展,但没有引号或转义评估。这意味着如果 identity 的输出包含空格,shell 将无论如何将其视为“单词”之间的分隔符(也就是其他程序的参数),而不管您添加了什么引号/转义/任何东西来避免这种情况。更糟糕的是,输出中包含通配符的单词将被扩展为匹配文件的列表(如果有)。这意味着$(identity 'foo * bar')注定会失败。
话虽如此,还有一些方法可以通过改变 shell 设置来模拟它。例如,set -f 会关闭通配符扩展,解决了这个问题——但您必须在运行 identity 之前在父 shell 中设置它,然后将其设置回正常状态,否则许多其他事情都会出错。同样地,您可以更改 IFS 以防止单词拆分将空格视为分隔符——但再次需要在父 shell 中更改它,并在之后将其设置回去,而且如果选择了其他分隔符字符,则会对替换分隔符字符造成麻烦。因此,您可以模拟它,但这是相当糟糕的伪造。
编辑:正如 Michael Kropat 指出的那样,使用 eval 是另一种“伪造”它的方法,如果小心地执行,则更灵活。

谢谢你们两位的解释!将它置于一些实际情境中:这是否意味着没有函数可以安全地返回文件名列表(可能包含空格)作为输出?因为除非在调用者中使用eval,否则您将丧失空格和分隔符之间的区别,因此无法稍后循环遍历该文件列表。 - Lucas Cimon
@LucasCimon:一个函数可以返回由空字符分隔的列表(就像find -print0一样),但是这不能与$()结构一起使用——你必须将其管道传递到类似于xargs -0while IFS= read -rd '' argument循环中。这个“安全”的原因与你不能使用它与$()相同:C字符串不能包含nulls,因此参数和$()都无法处理它们。 - Gordon Davisson

2
如果你愿意使用 eval ,你可以绕过返回值的单词拆分问题:
$ argv_count a\ b c\ d
argv_count('a b c d'):2
$ identity() { printf ' %q' "$@"; }
$ eval argv_count "$(identity a\ b c\ d)"
argv_count('a b c d'):2
$ eval argv_count "$(eval identity "$(identity a\ b c\ d)")"
argv_count('a b c d'):2

或者按照Gordon Davisson更为复杂的方法:

$ argv_count $'foo\t * bar'
argv_count('foo  * bar'):1
$ eval argv_count "$(eval identity "$(identity $'foo\t * bar')")"
argv_count('foo  * bar'):1

有一些奇怪的情况下这个方法不起作用 -- 例如尝试 eval argv_count $(identity $'foo\t * bar')。我认为如果你在它周围加上双引号,像这样 "$(identity ....)",它就可以解决这些问题了。 - Gordon Davisson
@GordonDavisson 这是一个非常有趣的例子!我不知道为什么 eval argv_count $(eval identity $(identity $'foo\t * bar')) 可以正常工作,但添加 \t 却会破坏更简单的 eval argv_count $(identity $'foo\t * bar')。至于使用双引号作为修复方法,我认为它会破坏嵌套性。 - Michael Kropat
@GordonDavisson,顺便说一下,我已经给你的答案点赞了。 我只是出于求知欲而添加了我的答案。 - Michael Kropat
实际上,我认为您的方法比我建议的更好。更复杂的版本对我来说就像更简单的版本一样失败了;不确定为什么它会对您有效,但双引号似乎可以嵌套,您只需要在每个级别上加引号:eval argv_count "$(eval identity "$(identity $'foo\t * bar')")" 正常运行。 - Gordon Davisson
@GordonDavisson 真是太棒了,它确实起作用!我不知道昨天输入了什么错误的内容,以至于得到了不同的结果。 - Michael Kropat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接