如何平行我的bash脚本使用`find`而不面临竞争条件?

我试图执行这样的命令:

find ./ -name "*.gz" -print -exec ./extract.sh {} \; 

gz文件本身很小。 目前我的extract.sh包含以下内容:

 # Start delimiter echo "#####" $1 >> Info zcat $1 > temp # Series of greps to extract some useful information grep -o -P "..." temp >> Info grep -o -P "..." temp >> Info rm temp echo "####" >> Info 

显然,这是不可并行的,因为如果我运行多个extract.sh实例,它们都会写入同一个文件。 什么是这样做的聪明方式?

我有一个32位核心马力巨大的机器上的80K gz文件。

我会创建一个临时目录。 然后为每个grep创建一个输出文件(基于它处理的te文件的名称)。 在/tmp下创建的文件位于RAM磁盘上,因此不会因大量写入而使硬盘崩溃。

然后你可以在最后把所有的东西都集中在一起,或者让每个grep在完成时发出另一个进程的信号,这个进程可以立即开始捕获文件(并在完成时删除它们)。

例:

 working_dir="`pwd`" temp_dir="`mktemp -d`" cd "$temp_dir" find "$working_dir" -name "*.gz" | xargs -P 32 -n 1 extract.sh cat *.output > "$working_dir/Info" rm -rf "$temp_dir" 

extract.sh

  filename=$(basename $1) output="$filename.output" extracted="$filename.extracted" zcat "$1" > "$extracted" echo "#####" $filename > "$output" # Series of greps to extract some useful information grep -o -P "..." "$extracted" >> "$output" grep -o -P "..." "$extracted" >> "$output" rm "$extracted" echo "####" >> "$output" 

假设(只是为了简单和清晰)所有的文件都以az开头。

所以你可以同时使用26个内核,当为每个字母启动一个如上所示的查找序列时。 每个“查找”需要生成一个自己的聚合文件

 find ./ -name "a*.gz" -print -exec ./extract.sh a {} \; & find ./ -name "b*.gz" -print -exec ./extract.sh b {} \; & .. find ./ -name "z*.gz" -print -exec ./extract.sh z {} \; 

(提取需要采取第一个参数来分隔“信息”目标文件)

当你想要一个大的聚合文件只是加入所有聚合。

但是,我不相信用这种方法来取得成绩。 最后所有的文件内容都会被序列化。

可能的硬盘磁头移动将会是unzip(cpu)性能的限制。

但是,让我们试试

通过findutils源代码的快速检查发现,find为每个exec启动一个子进程。 我相信它会继续下去,尽管我可能会误解来源。 正因为如此,你已经是平行的,因为操作系统将处理你的核心共享。 通过虚拟内存的魔力,相同的可执行文件将大部分共享相同的内存空间。

你将遇到的问题是文件锁定/数据混合。 随着每个小孩的运行,它会将信息输入到您的info文件中。 这些是个别的脚本命令,所以他们会像意大利面一起混合输出。 这并不能保证文件将是有序的! 只是所有的个人文件的内容将保持在一起。

为了解决这个问题,你只需要利用shell创建一个临时文件(使用tempfile )的能力,将每个脚本转储到临时文件中,然后让每个脚本将临时文件捕获到info文件中。 使用后不要忘记删除临时文件。

如果临时文件在内存中(请参阅tmpfs ),那么除了写入最终文件并运行查找搜索之外,您将避免被IO限制。

Tmpfs是一个特殊的文件系统,使用你的内存作为“磁盘空间”。 它将占用你所允许的内存量,而不是使用超过它需要的内存量,如果内存满了,则根据需要交换到磁盘。

使用:

  1. 创建一个挂载点(我喜欢/ mnt / ramdisk或者/ media / ramdisk)
  2. 以root身份编辑/ etc / fstab
  3. 添加tmpfs /mnt/ramdrive tmpfs size=1G 0 0
  4. 以root用户身份运行umount来安装新的ramdrive。 它也将安装在启动。

有关所有可用选项,请参阅fstab上的wikipedia条目 。

您可以使用xargs并行运行搜索。 --max-procs限制执行的进程数(默认值为1):

 find ./ -name "*.gz" -print | xargs --max-args 1 --max-procs 32 ./extract.sh 

./extract.sh ,可以使用mktemp将每个.gz中的数据写入临时文件,所有这些文件稍后可以组合使用:

 # Start delimiter tmp=`mktemp -t Info.XXXXXX` src=$1 echo "#####" $1 >> $tmp zcat $1 > $tmp.unzip src=$tmp.unzip # Series of greps to extract some useful information grep -o -P "..." $src >> $tmp grep -o -P "..." $src >> $tmp rm $src echo "####" >> $tmp 

如果你有大量的马力,你可以直接使用zgrep ,而不需要先解压。 但是如果以后有很多grepzcat可能会更快。

无论如何,稍后将所有内容组合成一个文件:

 cat /tmp/Info.* > Info rm /tmp/Info.* 

如果您关心的是.gz文件的顺序, ./extract.sh第二个参数应用于./extract.sh

 find files/ -name "*.gz" | nl -n rz | sed -e 's/\t/\n/' | xargs --max-args 2 ... 

./extract.sh

 tmp=`mktemp -t Info.$1.XXXXXX` src=$2 

extract.sh中的多个grep调用可能是这里的主要瓶颈。 一个显而易见的优化是只读取一次文件,然后按照所需的顺序打印摘要。 作为一个额外的好处,我们可以推测报告可以写成一个单独的块,但它可能不会完全阻止交错输出。 不过,这是我的尝试。

 #!/bin/sh for f; do zcat "$f" | perl -ne ' /(pattern1)/ && push @pat1, $1; /(pattern2)/ && push @pat2, $1; # ... END { print "##### '"$1"'\n"; print join ("\n", @pat1), "\n"; print join ("\n", @pat2), "\n"; # ... print "#### '"$f"'\n"; }' done 

awk而不是Perl中做这个可能会稍微有效些,但是因为你正在使用grep -P我认为保持相同的正则表达式语法是有用的。

脚本接受多个.gz文件作为输入,因此您可以使用find -exec extract.sh {} \+xargs来启动大量的并行进程。 使用xargs您可以尝试在连续作业和并行作业之间找到平衡点,方法是在一个批处理中为每个新进程提供100到500个文件。 您可以节省新进程的数量,但会在并行中失败。 一些实验应该揭示平衡应该是什么,但是这只是我将我的帽子拉出一个数字,看看它是否已经足够好了。

当然,如果你的输入文件足够小,那么多个grep调用将会耗尽磁盘缓存,并且比起启动Perl的开销要快。