并行执行 Bash 个命令

Parallel execution of Bash commands

我有一个 Bash 脚本,它有一个循环,在循环中有一个 Bash 命令调用另一个 Bash 脚本,该脚本又调用 Python 脚本。

循环中的每个 bash 命令都可以 运行 彼此独立。当我稍后 运行 它在实际数据集上时,执行每个命令需要一些时间。因此,我想利用并并行化这部分脚本。

我花了几天时间查看 Bash 中执行并行执行的选项,同时还让我可以选择要并行化代码的内核数量,这样我就不会淹没服务器.在寻找 GNU 的选项后,xargs -P 在我看来是最合理的,因为我不需要特定的 Bash 版本,它无需安装额外的库就可以工作。然而,我很难让它发挥作用,尽管它看起来很简单。

#!/bin/bash

while getopts i:t: option
do
case "${option}"
in
    i) in_f=${OPTARG};;
    t) n_threads=${OPTARG};;
esac
done    

START=$(date +%s)
class_file=$in_f
classes=( $(awk '{print }' ./$class_file))
rm -r tree_matches.txt
n="${#classes[@]}"
for i in $(seq 0  $n);
   do
     for j in $(seq $((i+1)) $((n-1)));
         do
            echo ${classes[i]}"    "${classes[j]} >> tree_matches.txt
         done
   done
col1=( $(awk '{print }' ./tree_matches.txt ))
col2=( $(awk '{print }' ./tree_matches.txt ))


printf "%s[=11=]" {0..1275} | xargs -0 -I k -P $n_threads sh myFunction.sh -1 ${classes[k]} -2 ${classes[k]}

n_pairs="${#col1[@]}"

END=$(date +%s)
DIFF=$(( $END - $START ))
echo "Exec time $DIFF seconds"

您可以忽略最初的两个嵌套循环,为了完整起见,我只是粘贴了整个脚本。将要并行化的部分是从脚本末尾算起的第 4 行代码:

printf "%s[=12=]" {0..1275} | xargs -0 -I k -P $n_threads sh myFunction.sh -1 ${classes[k]} -2 ${classes[k]}

这将遍历所有对,在我的例子中总共是 1275 对,理想情况下将使用变量 $n_threads.

与指定数量的线程并行执行 myFunction.sh

但是,我做错了什么,因为该行中的迭代器 k 没有索引我的两个数组 ${classes[k]}${classes[k]}.

循环持续迭代 1275 次,但当我回显它们时它只索引两个数组的第一个元素。后来我将该行更改为此行以进行故障排除:

printf "%s[=13=]" {0..1275} | xargs -0 -I k -P $n_threads echo "index" k

它实际上是在每次循环时递增 k 的值,但是当我将该行更改为:

printf "%s[=14=]" {0..1275} | xargs -0 -I k -P $n_threads echo "index" "$((k))"

它正在打印 0,作为 k 的值的 1275 次。我不知道我做错了什么。

我实际上有两个大小相同的向量,它们是 myFunction.sh 脚本的输入。我只希望一个整数索引能够同时对它们进行索引,并使用从这两个向量索引的这两个值调用我的函数。我根据您的建议修改了我的代码如下:

 for x in {0..10};
    do
        printf "%d[=15=]" "$x"; done| xargs -0 -I @@ -P $n_threads sh markerGenes2TreeMatch.sh -1 ${col1[@@]}-2 ${col2[@@]}

但是现在当我执行代码时出现以下错误:

@@: syntax error: operand expected (error token is "@@")

我猜这个索引 @@ 仍然是字符串格式。我只希望在循环时生成整数索引并可以并行执行此命令。

对于有问题的行:

printf "%s[=10=]" {0..1275} | xargs -0 -I k -P $n_threads sh myFunction.sh -1 ${classes[k]} -2 ${classes[k]}
在 xargs 有机会看到它之前,

${classes[k]} 将被 shell 扩展(很可能什么都没有)。

也许您可以重新订购:

for x in {0..1275}; do printf "%s[=11=]" "${classes[$x]}"; done |\
xargs -0 -I @@ -P $n_threads sh myFunction.sh -1 @@ -2 @@

这条线路并没有像您想象的那样工作:

printf "%s[=10=]" {0..1275} | xargs -0 -I k -P $n_threads sh myFunction.sh -1 ${classes[k]} -2 ${classes[k]}

发生的事情是 BASH 将首先将 $n_threads${classes[k]} 之类的内容扩展为字符串,然后调用 xargs。顺便提一句。 ${classes[k]} 始终是 "",因为键 "k" 不在数组 classes 中。尝试 ${classes[$k]};然后 BASH 将首先替换变量 k,然后使用结果在 classes.

中查找值

也许更好的方法是将 classes 中的值写入文件并将其用作 xargs 的输入。您可能必须更改 myFunction.sh 以接受单个参数(= 一行输入)并在脚本中将其拆分。

使用 GNU Parallel 你可能会做:

classes=( $(awk '{print }' ./$class_file))
parallel markerGenes2TreeMatch.sh -1 {=1 'if($arg[1] ge $arg[2]) { skip() }' =} -2 {2} ::: ${classes[@]} ::: ${classes[@]}

或:

parallel --plus markerGenes2TreeMatch.sh -1 {1choose_k} -2 {2choose_k} ::: ${classes[@]} ::: ${classes[@]}

那么可以跳过整代tree_match.txt,$col1/$col2.

使用 parallel --embed 将 GNU Parallel 直接包含在您的脚本中,因此您没有外部依赖项。