【问题标题】:How can I use xargs to run a function in a command substitution for each match?如何使用 xargs 在每个匹配项的命令替换中运行函数?
【发布时间】:2019-07-13 01:52:54
【问题描述】:

在为字符串替换编写 Bash 函数时,我在使用 xargs 时遇到了一个奇怪的行为。这实际上让我发疯,因为我无法让它工作。 幸运的是,我已经能够将其归结为以下简单示例:

定义一个简单的函数,将给定参数的每个字符加倍:

function subs { echo $1 | sed -E "s/(.)/\1\1/g"; }

调用函数:

echo $(subs "ABC")

正如预期的那样,输出是:

AABBCC

现在使用 xargs 调用函数:

echo "ABC" | xargs -I % echo $(subs "%")

令人惊讶的是,现在的结果是:

ABCABC

似乎函数内的 sed 命令现在将整个字符串视为单个字符。 为什么会发生这种情况以及如何预防?

你可能会问,我为什么要使用 xargs。当然,这是一个简化的示例,实际用例要复杂得多。

在最初的用例中,我有一个产生大量输出的程序。我通过几个 grep 管道输出以获取感兴趣的行。之后,我将这些行通过管道传输到 sed 以从这些行中提取我需要的数据。因为我需要对数据进行的一些转换太复杂而无法单独使用正则表达式,所以我想为这些使用一个函数。所以,我最初的想法是简单地输入函数,但我无法让它工作并最终得到 xargs 解决方案。我最初的想法是这样的:

command | grep ... | grep ... | grep ... | sed ... | subs

顺便说一句:我不是从命令行执行此操作,而是从脚本中执行此操作。该函数在使用它的同一脚本中定义。

我使用的是 Bash 3.2(Mac OS X 默认),所以花哨的 Bash 4.x 对我没有帮助,抱歉。

我会很高兴所有可能对这个话题有所启发的事情。

最好的问候

弗兰克

【问题讨论】:

  • 请记住,xargs 会启动新的子进程,不在现有 shell 中运行(或任何 shell,除非您明确指定将启动子进程的命令)。除非您跳过一些障碍来这样做,否则这些子流程根本不是外壳。
  • ...它有助于使用set -x 记录外壳级别发生的事情,这将让您看到 choroba 的答案中描述的行为。
  • (顺便说一句,关于我的建议,您可能真的不需要使用 xargs ——请参阅 BashFAQ #1 以获取有关在不使用本机 bash 的情况下循环面向行的输入的指南)。
  • 考虑您是否真的需要命令替换。在这个简单的例子中,... | xargs -I % subs % 可能就足够了。
  • @chepner, ...好吧,如果 subs 是一个可以是 exec()'d 而不是函数的外部程序就足够了。

标签: linux bash function sed xargs


【解决方案1】:

这是因为 $(subs "%") 在解析管道时被 shell 扩展,所以 xargsecho %% 一起运行。

【讨论】:

  • 感谢您的快速回复。有没有办法在 xargs 表达式中正确使用该函数,而无需在解析管道时对其进行扩展?
  • @Epiphastro, $(...) 是一个外壳结构;它不能在没有外壳的情况下运行。除非您运行 xargs sh -c '...'xargs bash -c '...',否则 在 xargs 之后没有启动 shell,因此这些扩展无法在 xargs 找出您的参数列表之后运行是。
【解决方案2】:

如果您真的需要这样做(您可能不需要,但如果没有更具代表性的样本,我们将无法提供帮助),更好的实践方法可能如下所示:

subs() { sed -E "s/(.)/\1\1/g" <<<"$1"; }
export -f subs

echo "ABC" | xargs bash -c 'for arg; do subs "$arg"; done' _
  • 使用echo "$(subs "$arg")" 而不仅仅是subs "$arg" 只会增加错误(考虑一下如果你的参数之一是-n 会发生什么——这是假设一个相对温和的echo;他们被允许消费反斜杠即使没有 -e 参数并做各种其他令人惊讶的事情)。你可以在上面做,但它会减慢你的程序并使它更容易出现令人惊讶的行为;没有意义。
  • 运行export -f subs 将您的函数导出到环境,因此它可以由作为子进程调用的其他bash 实例运行(xargs 调用的所有程序都在您的shell 之外,因此它们看不到shell-local变量或函数)。
  • 没有-I——也就是说,在它的默认操作模式下——xargs将参数附加到它给出的命令的末尾。这允许一种更有效的使用模式,它不是每行输入调用一个命令,而是将尽可能多的参数传递给尽可能少的子进程。

    这也避免了在将xargs -Ibash -c '...'sh -c '...' 结合使用时可能发生的重大安全漏洞。 (如果您曾经使用过-I% sh -c '...%...',那么您的文件名将成为您代码的一部分,并且能够用于对您的系统进行注入攻击)。

【讨论】:

  • 谢谢,这行得通。但是你和其他人注意到我可能根本不需要这样做。我会将实际用例添加到原始问题中,也许有人可以提出更好的方法。
  • 我将此解决方案合并到我的脚本中。它有效,因此我将其标记为已解决。但是我仍然对在 xargs 中启动子 shell 不满意,因为这需要我导出很多我实际上并不想导出的变量。有没有办法使用同一个 shell 来使用这个解决方案?
  • 如果你想要相同的shell,你不能使用xargs,句号。您是否查看了我之前链接的常见问题解答(在该问题的第一个 cmets 中)?如果您只想根据从标准输入读取的内容运行一段脚本,那么BashFAQ #1 是第一个开始的地方。
猜你喜欢
  • 2011-10-20
  • 1970-01-01
  • 1970-01-01
  • 2016-10-20
  • 2022-08-22
  • 2016-08-11
  • 1970-01-01
相关资源
最近更新 更多