【问题标题】:bash: split ascii file into n parts; iterate over ONLY those filesbash:将 ascii 文件拆分为 n 个部分;仅迭代那些文件
【发布时间】:2016-04-07 17:21:22
【问题描述】:

我有一个几千行的 ASCII 文件,由 bash 脚本一次处理一行。因为处理是令人尴尬的并行,我想将文件分成大小大致相同的部分,保留换行符,每个 CPU 内核一个部分。不幸的是,split r/numberOfCores 制作的文件后缀不容易被迭代。

split --numeric-suffixes=1 r/42 ... 生成文件foo.01foo.02、...、foo.42,可以使用for i in `seq -w 1 42 ` 进行迭代,因为-w 添加了前导零)。但是如果 42 更改为小于 10 的值,文件仍然有前导零,但 seq 没有,所以它失败了。这种担忧是有道理的,因为现在有些 PC 的内核少于 10 个,有些超过 10 个。一个可怕的解决方法:

[[ $numOfCores < 10 ]] && optionForSeq="" || optionForSeq="-w"

简单的解决方案for f in foo.* 是有风险的:通配符可能匹配split 生成的文件以外的文件。

一种使后缀seq友好的丑陋方式,但风险相同:

split -n r/numOfCores infile foo.
for i in `seq 1 $numOfCores`; do
    mv `ls foo.* | head -1` newPrefix.$i
done
for i in `seq 1 $numofCores`; do
    ... newPrefix.$i ...
done

是否有一种更简洁、更可靠的方法将文件拆分为 n 个部分,其中 1split 只进入新创建的目录?

编辑:为了澄清“如果 42 更改为小于 10”,相同的代码应该在具有 8 个内核的 PC 和另一台具有 42 个内核的 PC 上运行。)

基于seq 的解决方案很笨拙。基于通配符的解决方案是有风险的。 split 有替代品吗? (带有行号的csplit 会更加笨拙。)gawk 单行?

【问题讨论】:

  • @Cyrus 将 9 替换为一个变量,然后花一分钟或更长时间尝试让它再次工作
  • var=9; eval echo {01..$var} 他的示例不包含实变量。

标签: bash awk split parallel-processing ascii


【解决方案1】:

使用seq 的格式字符串怎么样?

$ seq -f '%02g' 1 4
01
02
03
04


$ seq -f '%02g' 1 12
01
02
03
...
09
10
11
12

【讨论】:

  • @CamilleGoudeseune,是的,没有引号应该可以正常工作。
【解决方案2】:

使用 GNU bash 4:

使用printf 格式化您的数字:

for ((i=1;i<=4;i++)); do printf -v num "%02d" $i; echo "$num"; done

输出:

01 02 03 04

【讨论】:

  • 这是我个人比较喜欢的方法。任何时候我都不需要调用外部实用程序,例如 seq,而是让 bash 内置程序来完成工作,我会这样做。
  • 我会保持我的回答只是为了展示另一种方法,但我更喜欢这个。
  • 如果你想让它更防弹,你可以使用${numOfCores} (${numOfCores#}) 中的位数来构造 printf 掩码,而不是假设 2 位数( %02d).
  • 是的,很好。谢谢你。有一个错字:将${numOfCores#} 替换为${#numOfCores}
【解决方案3】:

你确定这不是 GNU Parallel 的工作吗?

cat file | parallel --pipe -N1 myscript_that_reads_one_line_from_stdin

这样你根本不需要临时文件。

如果您的脚本可以读取多于一行(实际上是UNIX filter),那么这应该非常接近最优:

parallel --pipepart -k --roundrobin -a file myscript_that_reads_from_stdin

它会为每个核心生成一个作业,并将文件动态拆分为每个核心的一个部分。如果某些行比其他行更难处理(即您可能会在一行中“卡住”一段时间),那么这个解决方案可能会更好:

parallel --pipepart -k -a file myscript_that_reads_from_stdin

它将为每个核心生成一个作业,并将文件动态拆分为每个核心 10 个部分,因此平均每个核心总共运行 10 个作业。

GNU Parallel 是一种通用的并行化器,可以轻松地在同一台机器上或在您可以通过 ssh 访问的多台机器上并行运行作业。它通常可以替换 for 循环。

如果您想在 4 个 CPU 上运行 32 个不同的作业,那么并行化的直接方法是在每个 CPU 上运行 8 个作业:

GNU Parallel 会在完成后生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:

安装

如果没有为您的发行版打包 GNU Parallel,您可以进行个人安装,这不需要 root 访问权限。这样做可以在 10 秒内完成:

(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash

有关其他安装选项,请参阅http://git.savannah.gnu.org/cgit/parallel.git/tree/README

了解详情

查看更多示例:http://www.gnu.org/software/parallel/man.html

观看介绍视频:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

浏览教程:http://www.gnu.org/software/parallel/parallel_tutorial.html

注册电子邮件列表以获得支持:https://lists.gnu.org/mailman/listinfo/parallel

【讨论】:

  • 这在myscript_that_reads_one_line_from_stdin 很慢时很好。但是,如果该脚本只需要几毫秒,那么生成数千个这样的脚本可能比制作临时文件和每个内核运行一个进程要慢。
【解决方案4】:

使用ls 获取文件名,然后使用正则表达式:

for n in $(ls foo.* |grep "^foo\.[0-9][0-9]*$") ; do

【讨论】:

  • 我建议使用for n in foo.[0-9][0-9]; do
  • 除非有四个核心,在这种情况下你的文件名将是foo.1, foo.2, foo.3, foo.4,没有一个与foo.[0-9][0-9]匹配。命令行上的通配符扩展使用玩具版的正则表达式。此外,grep 将在不到一毫秒的时间内返回,那么为什么不采用更强大的方法呢?
  • 我是这样理解的:split 创建的文件和“如果 42 更改为小于 10,文件仍然有前导零”。
  • @Cyrus 我刚刚在split 上查找了使用信息,上面写着:-a, --suffix-length=N use suffixes of length N (default 2) 所以它的工作原理与您所说的完全一样。
猜你喜欢
  • 2011-03-12
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多