并行处理多个文件的Bash脚本

3

我看到了一些类似的关于这个话题的问题,但是它们都不能帮助我解决以下问题:

我有一个 bash 脚本,看起来像这样:

#!/bin/bash

for filename  in /home/user/Desktop/emak/*.fa; do
    mkdir ${filename%.*}
    cd ${filename%.*}
    mkdir emak
    cd ..
done

这个脚本基本上做了以下几件事情:

  • 遍历目录中的所有文件
  • 创建一个名为每个文件名的新目录
  • 进入新文件夹并创建一个名为“emak”的新文件

实际任务比创建“emak”文件要计算密集得多...

我有大约数千个文件需要遍历。由于每次迭代是独立的,所以我想将其拆分成不同的处理器(我有24个核心),这样可以同时处理多个文件。

我读过一些关于并行运行的帖子(使用:GNU),但我没有看到明显的应用方式来解决这个问题。

谢谢


1
你自己尝试过使用GNU parallel了吗?看到这将是很好的。 - Tom Fenech
并行 -j $((getconf _NPROCESSORS_ONLN-1)) <你的脚本名称> - notrai
顺便说一句,通过http://shellcheck.net/运行您的代码,以便自动发现引号错误,这样我们就不需要在此指出它们了。(如果您的文件名中有空格,则当前代码会表现得很糟糕)。 - Charles Duffy
@rai 默认值为核心数。-j-1 == 核心数减一。 - Ole Tange
2个回答

6
不需要使用“parallel”,你可以直接使用。
N=10
for filename in /home/user/Desktop/emak/*.fa; do
    mkdir -p "${filename%.*}/emak" &
    (( ++count % N == 0)) && wait
done

第二行代码会暂停每第 N 个任务,以便在继续之前完成所有先前的任务。

不错。而且比GNU parallel的方法更高效,因为它不需要启动新的shell实例。 - Charles Duffy
虽然说,减少单独的 mkdir 调用数量可以进一步提高性能。也许你想要使用管道符号 xargs -0 -P 0 mkdir -p?这样还可以避免在我们到达 wait 并开始新批处理之前等待所有 N 个进程完成时浪费 CPU。 - Charles Duffy
我开始着手处理类似于 find ... -exec mkdir -p {} + 的东西,但对于如何将其与从 filename 中去除 .fa 相结合失去了兴趣。任何想要追求这个问题的人都可以获得免费的声望! :) - chepner
"-exec bash -c 'mkdir -p "${@%.*}"' {} +",这个命令可以吗?" - Charles Duffy
这会去掉 .fa,但不会在每个后面添加 /emak - chepner
嗯。find ... -print0 | while IFS= read -r -d '' filename; do printf '%s\0' "${filename%.*}/emak"; done | xargs -0 mkdir -p -- - Charles Duffy

4

使用GNU Parallel可以做到这样,您可以创建并导出一个名为doit的bash函数:

#!/bin/bash

doit() {
    dir=${1%.*}
    mkdir "$dir"
    cd "$dir"
    mkdir emak
}
export -f doit
parallel doit ::: /home/user/Desktop/emak/*.fa

你会真正看到这种方法的好处,尤其是在你的“计算成本高”的部分时间较长或特别不确定的情况下。如果它需要,比如说最多10秒,并且具有不确定性,GNU Parallel将在最短的N个并行进程完成后立即提交下一个作业,而不是等待所有N个作业完成后再开始下一批N个作业。
作为粗略的基准测试,这需要58秒:
#!/bin/bash

doit() {
   echo $1
   # Sleep up to 10 seconds
   sleep $((RANDOM*11/32768))
}
export -f doit
parallel -j 10 doit ::: {0..99}

这是直接可比的,只需要87秒:

#!/bin/bash
N=10
for i in {0..99}; do
    echo $i
    sleep $((RANDOM*11/32768)) &
    (( ++count % N == 0)) && wait
done

当然,尽管在这种特定情况下,我会认为为每个目录旋转一个新的子进程 shell 来运行此函数的开销远远比并行化本身节省的时间更昂贵。 - Charles Duffy
1
@CharlesDuffy OP 表示实际过程的计算成本 "远高于"。 - Mark Setchell
叹气。我希望人们在他们的示例中放置一个 sleep 3 # do something expensive here 来演示那种情况。 - Charles Duffy
这个很好用!每次迭代需要47秒。使用doit函数进行24次迭代需要50秒。我尝试了48个文件(48次迭代),需要100秒。它以24个块的方式工作,我认为是因为我有24个核心。我是对的吗? 非常感谢! - aspire57
1
正确!您还可以使用 parallel --eta 来获取完成时间的估计(预计到达时间),使用 parallel -j 16 可以在 16 个核心上运行,例如。此外,如果您有多台服务器可用,只需将它们添加到命令行中即可将作业分配到多台机器上-请查看任何 GNU Parallel 教程。为了公平起见,您应该将他的 N=10 更改为 N=24 - Mark Setchell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接