【问题标题】:xargs 管道非空标准输入行到命令,同时保留双引号
【发布时间】:2022-01-23 16:38:55
【问题描述】:

我正在尝试让脚本监听 stdin(所以我运行它并且它不会立即退出)并且仅在 stdin 不为空时执行,然后将 stdin 行传递给另一个命令。 现在我正在使用来自the answer here的命令:

xargs -I {} sh -c 'echo {} | foo'

我想保留标准输入的双引号,因为人们建议使用 -d '\n' 但这会导致 foo 在空行上运行。

我研究了可能的 GNU Parallel 解决方案,但一无所获。

这是我的标准输出:

>xargs -I {} sh -c 'echo {} | foo'


bar
I have executed for 'bar'

"bar"
I have executed for 'bar' //notice the double quotes missing
^C
>xargs -I {} sh -c "echo '{}' | foo"

bar
I have executed for 'bar'

"bar"
I have executed for 'bar' //Same thing, double quotes missing
^C
>xargs -d '\n' -I {} sh -c "echo {} | foo"

i have executed for '' //doesn't ignore empty lines anymore

i have executed for ''
bar
i have executed for 'bar'
"bar"
i have executed for 'bar'

期望的输出:

bar
I have executed for 'bar'

"bar"
I have executed for '"bar"'

跑步

echo '"bar"' | foo

了解我

I have executed for '"bar"'

【问题讨论】:

  • xargs sh -c '...{}...' 会导致严重的安全问题。是的,你看到人们一直都在这样做,但不要通过自己做来助长问题。 (find ... -exec sh -c '...{}...' \; 也是如此)。数据应始终从代码带外传递以避免注入攻击——不仅在 bash 中,在其他任何地方也是如此。
  • 也就是说,听从 Tripleee 的建议。 ????

标签: linux bash shell


【解决方案1】:

如果如您的标签所示,您在 上运行,则您拥有支持-0 选项的GNU xargs。然后,您可以传入完全任意的文本,甚至包括换行符:

printf '%s\0' "foo" "'bar"' '"baz"' 'quux
with a newline' | xargs -0 foo

删除空行可以通过前面的简单grep 来完成。还有xargs -r 表示如果xargs 接收到空输入(这也是GNU 扩展),则不运行命令。

不过,您的尝试有点问题;您应该将参数作为命令行参数传递,而不是让 xargs 按字面意思将它们插入到 sh -c '... {} ...' 字符串中。

根据您的要求,这甚至可以在其他平台上移植:

xargs sh -c 'if [ $# -gt 0 ]; then echo "$@" | foo; fi' _

_ 只是一个占位符; sh -c '...' 的参数用于填充 $0$1$2 等,因此我们放入了一些东西,任何东西,以占据 $0 的插槽。

【讨论】:

  • 我会将sh 传递为$0,这样如果有任何错误,它们就会打印为sh: <error message>
【解决方案2】:

GNU Parallel 在内部使用它:

perl -e 'if(sysread(STDIN,$buf,1)){open($fh,"|-",@ARGV)||die;syswrite($fh,$buf);if($read=sysread(STDIN,$buf,131071)){syswrite($fh,$buf);}while($read=sysread(STDIN,$buf,131072)){syswrite($fh,$buf);}close$fh;exit($?&127?128+($?&127):1+$?>>8)}' /usr/bin/bash -c 'wc -l'

如果你只想要一行试试:

seq 3 | parallel --pipe -N1 wc -c
echo "'foo'" | parallel --pipe -N1 --rrs "echo -n i have executed for \"'\";cat;echo \"'\""
echo '"foo"' | parallel --pipe -N1 --rrs "echo -n i have executed for \"'\";cat;echo \"'\""

【讨论】:

    【解决方案3】:

    我想保留标准输入的双引号,因为人们建议使用 -d '\n' 但这会导致 foo 在空行上运行。

    xargs 默认执行引用处理,除非您通过-d/--delimiter-0/--null 指定分隔符。您必须使用其中之一来避免xargs 删除您尝试保留的引号。

    更重要的是,假设你设法通过xargs传递引用的输入不变,xargs启动运行命令的shell将执行它自己的引号删除,以及参数扩展,变量分配,重定向处理,等。您可以通过命令的这种变化直接观察其效果:

    $ xargs -d '\n' -I{} sh -c 'echo {} >>tmp.txt' 
    bar
    'bar'
    $ cat tmp.txt
    bar
    bar
    $
    

    请注意,尽管为 xargs 指定了分隔符,但引号仍会被删除。


    解析您的确切要求有点困难,但听起来您只是想从某些命令的标准输入中过滤空行。 sed 很容易做到这一点:

    foo() {
      while IFS= read -r line; do
        echo "I have executed for '$line'"
      done
    }
    
    $ sed '/\S/!d' | foo
    
    
    bar
    
    "bar"
    A whole line with "quotes" and 'quotes' and metacharacters > < !
    I have executed for 'bar'
    I have executed for '"bar"'
    I have executed for 'A whole line with "quotes" and 'quotes' and metacharacters > < !'
    $
    

    sed 命令解释:正则表达式 /\S/ 匹配行中任意位置的任何非空白字符。 ! 否定匹配,d 删除匹配(否定)模式的行——即任何不包含至少一个非空白字符的行。

    正如您在示例运行脚本中看到的那样,您的示例命令和使用sed 进行过滤的效果之间存在缓冲差异。不清楚这对你是否重要。

    【讨论】:

      猜你喜欢
      • 2021-04-29
      • 2012-01-07
      • 1970-01-01
      • 2018-10-09
      • 1970-01-01
      • 2010-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多