【问题标题】:Command output mangled on redirection重定向时命令输出损坏
【发布时间】:2017-05-13 05:36:24
【问题描述】:

给定一个包含几百万个文件的目录,我们想从这些文件中提取一些数据。

find /dir/ -type f | awk -F"|" '$2 ~ /string/{ print $3"|"$7 }' > the_good_stuff.txt

这将永远无法扩展,因此我们引入了 xargs。

find /dir/ -type f -print0 | xargs -0 -n1 -P6 awk -F"|" '$2 ~ /string/{ print $3"|"$7 }'

无论我们运行多长时间,这都会产生有效的输出。 Sweet 所以让我们通过在该命令上附加> the_good_stuff_from_xargs.txt 将其写入文件。除了现在文件包含错位的行。

令我印象深刻的是,在我的终端中查看 xargs 作为 STDOUT 打开的六个子进程的输出时,数据看起来很好。数据被重定向到文件系统的那一刻就是出现损坏的时候。

我尝试在命令中附加以下内容。

> myfile.txt

>> myfile.txt

| mawk '{print $0}' > myfile.txt

以及在将 xargs 的输出写入磁盘之前重定向或以其他方式“汇集”输出的各种其他概念,每个版本中的数据都已损坏。

我确信原始文件没有格式错误。我很肯定,当在终端中将其视为标准输出时,带有 xargs 的命令会产生有效输出长达 10 分钟的盯着它吐出的文本...

本地磁盘是 SSD...我正在从同一个文件系统读取和写入。

为什么重定向find /dir/ -type f -print0 | xargs -0 -n1 -P6 awk -F"|" '$2 ~ /string/{ print $3"|"$7 }'的输出会导致数据格式错误?

编辑

我目前无法安装 unbuffer,但 stdbuf -oL -eL 将命令输出修改为行缓冲,因此理论上应该做同样的事情。

stdbuf xargs cmdxargs stdbuf cmd 我都试过了,都导致断线非常严重。

-P6 是必需的,以便此命令在任何合理的时间内完成。

编辑 2

澄清一下...xargs-P6 标志是解决问题的必要条件,因为我们正在处理的目录有数百万个必须扫描的文件。

显然,我们可以删除 -P6 或以其他方式停止同时运行多个作业,但这并不能真正回答 为什么 输出会被破坏的问题,也不是一种现实的方法 如何在大规模完成任务的同时将输出恢复到“正确”状态。

解决方案

使用parallel 提到的已接受答案是所有答案中效果最好的。

我运行的最后一个命令看起来像。 time find -L /dir/ -type f -mtime -30 -print0 | parallel -0 -X awk -f manual.awk > the_good_stuff.txt awk 很困难,所以我将-F"|" 移到了命令本身。默认情况下,并行将在机器上为每个内核启动一个作业,如果需要,您可以使用-j 将作业数设置为更低。

用真正的科学术语来说,这是一个巨大的速度提升。花费了无法衡量的小时数(可能超过 6 小时)的工作在 6 六分钟后完成了 10%,因此可能会在一个小时内完成。

一个问题是,您必须确保在 parallel 中运行的命令没有尝试写入文件...这实际上绕过了并行对其运行的作业执行的输出处理!

最后没有-X 的并行行为类似于xargs -n1

【问题讨论】:

  • 标准输出在写入终端时是行缓冲的,但在写入管道或文件时是完全缓冲的。
  • 删除-P6;这会导致 6 个异步进程随机写入您的输出,并在缓冲区填满时写入部分行,并且不同的进程在不同的点写入不同的部分行,等等。如果您必须使用 -P6,您需要拥有6 个进程写入不同的文件,这样它们就不会践踏彼此的输出。这反过来可能意味着运行一个 shell 脚本,该脚本运行 awk 并将 I/O 重定向到一个单独的文件(也许使用 mktemp,其名称基于脚本的 PID)。
  • 听起来您应该使用parallel 而不是xargs,因为它管理命令的输出以避免此类麻烦。见this previous question
  • 当然你应该使用 GNU parallel
  • parallel -q 引用命令字符串,以便您可以使用原始 awk -F"|" 而不是单独的 .awk 文件。

标签: linux bash redirect io filesystems


【解决方案1】:

man xargs 提到了这个问题:“请注意,由被调用进程来正确管理对共享资源的并行访问。例如,如果多个进程尝试打印到标准输出,则会产生输出以不确定的顺序(并且很可能混淆)”

幸运的是,有一种方法可以让这个操作更快一个数量级,同时解决 mangling 问题:

find /dir/ -type f -print0 | xargs -0 awk -F"|" '$2 ~ /string/{ print $3"|"$7 }'

为什么?

-P6 正在打乱你的输出,所以不要使用它。 xargs -n1 为每个文件启动一个awk 进程,而没有n1xargs 启动的awk 进程要少得多,如下所示:

files | xargs -n1 awk
=>
awk file1
awk file2
...
awk fileN

vs

files | xargs awk
=>
awk file1 file2 ... fileN # or broken into a few awk commands if many files

我在大约 20k 个文本文件上运行了你的代码,每个文件大小约为 20k,有和没有-n1 -P6

with -n1 -P6  23.138s
without        3.356s

如果您想要没有 xargs 的标准输出改组的并行性,请使用 gnu parallel(也由 Gordon Davisson 建议),例如:

find /dir/ -type f -print0 | parallel --xargs -0 -q awk -F"|" '$2 ~ /string/{ print $3"|"$7 }'

注意:-q 是引用命令字符串所必需的,否则 -F"|"awk 代码周围的引号在 parallel 运行它们时会变为未引用。

parallel 节省了一点时间,但不如放弃-n1 那样:

parallel       1.704s

ps:引入 cat(Matt 在他的回答中这样做)比 xargs awk 快一点:

xargs awk        3.356s
xargs cat | awk  3.036s

【讨论】:

  • 嗯,我应该放弃-n1 我认为这是试图解开数据时遗留下来的。我不介意输出的顺序是否混合。我确实介意当输出被“损坏”时,半行被写入,另一半被写入另一行......无论哪种方式,我都会尝试您的建议并报告结果。
【解决方案2】:

我只会做以下事情:

cat /${dir}/* | awk '$2 ~ /string*/{ print $3 "|" $7 }' >> `date`.txt

文件以进程运行的日期和时间命名。

【讨论】:

  • 我可能是错的,但如果 ${dir} 中有目录,这会中断吗?就像 OP 所做的那样,使用“find -f”通常是只获取文件的好方法。它甚至会递归地找到它们,而 cat 和 glob 模式不会这样做。
  • 这个答案忽略了我们试图运行多个 awk 命令以提高从文件中选择“字符串”的速度的要求。
  • 它不会进入子目录(没有请求)。我们忽略了以这种方式执行 find type -f 的需要。
  • 我的版本在测试中用了 0.003 秒,你的用了 0.079 秒......所以上面的测试速度快了 20 倍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-30
  • 2011-01-25
  • 2016-04-10
  • 2018-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多