【问题标题】:How to use xargs to output to different file names?如何使用 xargs 输出到不同的文件名?
【发布时间】:2021-02-06 05:31:12
【问题描述】:

假设我的列表中有大量文件,像这样

$ mkdir inputs
$ for i in $(seq 1 1 10000); do printf "$i\n" > inputs/$i; done
$ find inputs/ -type f -exec readlink -f {} \; > files.txt

我想通过一个看起来像这样的脚本将它们全部传递

$ cat script.py
#!/usr/bin/env python3
import sys
args = sys.argv[1:]
output_file = args[0]
input_files = args[1:]
text = "got {} files".format(len(input_files))
print(text)
with open(output_file, "w") as fout:
    fout.write(text + '\n')

我不能一次全部传递它们,因为命令行调用对于系统来说太大而无法处理。但是,xargs 可以为您解决这个问题;

command 的命令行被建立起来,直到它到达一个 系统定义的限制(除非使用 -n 和 -L 选项)。这 指定的命令将根据需要被调用多次以使用 列出输入项列表。一般会少很多 命令的调用比输入中的项目多。这将 通常具有显着的性能优势。有些命令可以 也有用地并行执行;请参阅 -P 选项。

你可以像这样看到这个;

$ cat files.txt | xargs ./script.py output.txt
got 2151 files
got 2152 files
got 2152 files
got 2152 files
got 1393 files

这里,xargs 已将命令分解为 5 个单独的命令并运行每个命令。

但是,输出文件将只有最后一次调用的内容;

$ cat output.txt
got 1393 files

我想要的是获得如下所示的输出文件;

output1.txt # got 2151 files
output2.txt # got 2152 files
output3.txt # got 2152 files
output4.txt # got 2152 files
output5.txt # got 1393 files

有一个问题here 建议在脚本中完成此操作。但是,我的脚本 script.py 不能自己执行此操作,因为它不知道它已经在批处理输入集上运行了 n 次数。在现实生活中,myscript.py 实际上可能是任何我无法修改以完成类似操作的任意第三方程序。

因此,如果我可以对xargs 使用某种参数来自动填充已处理批次的编号n,那就更容易了,例如

$ cat files.txt | xargs ./script.py output.{n}.txt

这样的东西存在吗?有没有什么方法可以用xargs 将输入分块的增加的批次数来填充命令参数?

【问题讨论】:

  • 你可以做类似$ cat files.txt | xargs -IXYZ sh -c "./.script.py output.XYZ.txt"的事情。 -IXYZ 选项将导致文件名插入到命令中的XYZ 字符串中。可能有一种更清洁的方法,但我经常这样做。您还可以对命令使用单引号。注意:这并不完全正确,因为给xargs 的列表是一个文件列表,你要做的是给出一个文件名的唯一部分的列表,然后插入它。我想你明白了。
  • 是的,我看到了这些建议,但这不起作用。我不能将 xargs 输入行用作输出文件的一部分,因为除其他原因之外,output./path/to/input/1.txt 不是有效的文件名,而且我不希望每个输入文件都有单独的输出文件。我想使用 xargs 已经生成的批次并在输出文件名中引用它。
  • 你不能通过输出到标准输出并将标准输出重定向到文件来规避这个问题吗?我就是这样做的。另一个类似的替代方法是附加到 output.txt 而不是覆盖它,但输出到 stdout 对我来说似乎更可取。
  • 我仍然需要重定向到的文件名。我需要能够基于n'th 迭代的xargs's 命令调用生成该输出文件的名称
  • @steveb, xargs -Isigil sh -c '...sigil...' 是一个严重的安全风险;它将您的数据直接注入到您的代码中。永远不要那样做;只需省略-Ianything,并编写更像xargs sh -c 'for arg; do ...; arg' _ 的代码,因此sh 的副本将遍历传递给它的项目,如$1$2 等。一行输入'$(rm -rf ~)'$(rm -rf ~)否则会导致非常糟糕的一天。

标签: bash xargs


【解决方案1】:

这是我发现的另一种使用 GNU 并行而不是 xargs 的方法;

$ parallel -a files.txt --xargs ./script.py output.{#}.txt {}
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 631 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files

$ ls -1 output.*
output.10.txt
output.1.txt
output.2.txt
output.3.txt
output.4.txt
output.5.txt
output.6.txt
output.7.txt
output.8.txt
output.9.txt

$ cat output.*
got 631 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files
got 1041 files

【讨论】:

    【解决方案2】:

    当且仅当您是命名输入文件的人,因此确定它们不包含任何花哨的字符(即任何可能破坏事物的东西,或者最糟糕的是,被恶意利用)那么这可能会回答您的问题:

    xargs -a files.txt echo ./script.py output.NNN.txt \
    | awk 'gsub("NNN", ++n, $2)' \
    | sh
    

    这里xargs只生成命令行。然后awk 用实际数字替换NNN,并将结果发送到执行该行的sh(这就是文件名必须绝对安全的原因:落入坏人之手,这可能会造成严重破坏)

    注意:awk 也可以在命令末尾添加&,以实现并行化。

    【讨论】:

      【解决方案3】:

      这是我可能不得不使用的一种解决方案,直到我想出更好的方法;预先拆分输入文件列表并在每个拆分列表上分别运行xargs

      $ split -b 130989 files.txt files_split
      
      $ count=0
      
      $ for i in files_split*; do 
      cat $i | xargs ./script.py output.$count.txt ; 
      (( count++ )); 
      done
      
      got 2151 files
      got 2152 files
      got 2152 files
      got 2152 files
      got 1396 files
      
      $ ls output*
      output.0.txt  output.1.txt  output.2.txt  output.3.txt  output.4.txt
      
      $ cat output.*
      got 2151 files
      got 2152 files
      got 2152 files
      got 2152 files
      got 1396 files
      

      至于号码130989,我是从$ head -2151 files.txt | wc -c得到的,2151xargs最初拆分的号码。

      编辑:看起来您实际上可以从xargs --show-limits 更轻松地获得这些数字

      我希望其他人可能有更优雅的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-29
        • 2018-06-17
        • 1970-01-01
        • 2014-11-22
        • 2017-02-06
        • 1970-01-01
        • 1970-01-01
        • 2021-03-20
        相关资源
        最近更新 更多