如何使用 xargs 输出到不同的文件名?

How to use xargs to output to different file names?

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

$ 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 可以为您解决这个问题;

The command line for command is built up until it reaches a system-defined limit (unless the -n and -L options are used). The specified command will be invoked as many times as necessary to use up the list of input items. In general, there will be many fewer invocations of command than there were items in the input. This will normally have significant performance benefits. Some commands can usefully be executed in parallel too; see the -P option.

你可以像这样看到这个动作;

$ 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 实际上可能是任何我无法修改来完成类似事情的任意第 3 方程序。

因此,如果我可以对 xargs 使用某种参数,它会自动填充已处理的批次的数量 n,例如

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

有这样的东西吗?是否有一些方法可以使用 xargs 将输入分块到其中的递增批次数来填充命令参数?

这是我可能不得不使用的一种解决方案,直到我想出更好的解决方案;在每个拆分列表上分别预拆分输入文件列表和 运行 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

中更轻松地获得这些数字

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

这是我发现的另一种使用 GNU parallel 而不是 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

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

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

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

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