【问题标题】:Parallel execution of Bash commandsBash 命令的并行执行
【发布时间】:2019-06-22 19:39:17
【问题描述】:

我有一个 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 $1}' ./$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 $1}' ./tree_matches.txt ))
col2=( $(awk '{print $2}' ./tree_matches.txt ))


printf "%s\0" {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\0" {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\0" {0..1275} | xargs -0 -I k -P $n_threads echo "index" k

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

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

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

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

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

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

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

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

【问题讨论】:

  • 我不确定为什么你的最后一个命令不起作用,但请注意-I 暗示-L 1,即如果一次只处理一行输入。
  • k 只能增加xargs 如果它看到它! ${classes[k]} 被初始脚本扩展
  • 对,我认为问题在于评估的顺序。 $(( ... ))xargs 看到它之前由shell 处理,这就是为什么$(( {} ))(或任何你用作-I 的参数)不起作用的原因。您可能必须在您的xargs 命令中使用bash -c,请参阅the manual

标签: bash parallel-processing xargs gnu-parallel


【解决方案1】:

对于有问题的行:

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

${classes[k]} 将在 xargs 有机会看到它之前被 shell 扩展(最有可能没有)。

也许您可以重新订购:

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

【讨论】:

  • 我又遇到了同样的错误,实际上我有两个向量输入到 myFunction.sh 这两个向量大小相同,我可以在每次命令执行时使用一个索引值来调用它功能
  • 您可以将sh 替换为echo 并检查是否传入了预期值。您确定classes 包含有效数据吗?
  • 它实际上是两个向量 col1 和 col2,我更新了我的主要帖子以澄清这一点。
  • 好吧,我终于想通了...我简化了 myFunction.sh 脚本,现在它接受一个参数,然后使用逗号作为分隔符将行拆分(剪切)成两部分。我遵循了您建议的结构并且工作正常。谢谢大家的建议。
【解决方案2】:

这条线没有像你想象的那样工作:

printf "%s\0" {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 以接受单个参数(= 一行输入)并在脚本中将其拆开。

【讨论】:

    【解决方案3】:

    使用 GNU Parallel,您可能会这样做:

    classes=( $(awk '{print $1}' ./$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 直接包含在您的脚本中,因此您没有外部依赖项。

    【讨论】:

      猜你喜欢
      • 2014-01-28
      • 1970-01-01
      • 1970-01-01
      • 2014-01-11
      • 2012-04-05
      • 1970-01-01
      • 2019-05-04
      • 2018-01-16
      • 1970-01-01
      相关资源
      最近更新 更多