【问题标题】:Linux - run multiple parallel commands, print sequential outputLinux - 运行多个并行命令,打印顺序输出
【发布时间】:2014-10-05 01:40:06
【问题描述】:

我对 bash 有点陌生,我需要并行运行一个短命令数百次,但要按顺序打印输出。该命令将一个相当短的输出打印到标准输出,这是我不想松散的,或者它与另一个线程的输出混淆/混淆。在 Linux 中有没有办法运行多个命令(例如,并行不超过 N 个线程),以便按顺序打印所有命令输出(以任何顺序,只要它们不重叠)。

当前的 bash 脚本(完整代码here

declare -a UPDATE_ERRORS
UPDATE_ERRORS=( )

function pull {
    git pull  # Assumes current dir is set
    if [[ $? -ne 0 ]]; then
      UPDATE_ERRORS+=("error message")
    fi

for f in extensions/*; do
  if [[ -d $f ]]; then
    ########## This code should run in parallel, but output of each thread
    ########## should be cached and printed sequentially one after another
    ########## pull function also updates a global var that will be used later
    pushd $f > /dev/null
    pull
    popd > /dev/null
  fi
done

if [[ ${#UPDATE_ERRORS[@]} -ne 0 ]]; then
  # print errors again
fi

【问题讨论】:

  • 谢谢,看起来很有希望,但是如果发生故障,我如何让每个线程向全局数组添加错误消息?
  • 第 1 点)将-k 添加到您对 GNU Parallel 的调用中,以保持输出有序。第 2 点)定义一个函数,并确保将其导出,并将该函数传递给 GNU Parallel 执行 - 在函数内部,将错误消息附加到您的数组中。 gnu.org/software/parallel/…

标签: git bash


【解决方案1】:

您可以为此使用flock。我已经模拟了类似的情况进行测试。 do_the_things proc 生成时间重叠输出。在一个 for 循环中,文本生成同时被调用了好几次。输出应该是混乱的,但是输出被提供给过程locked_print,它一直等到锁被释放,然后将接收到的输入打印到标准输出。需要导出才能从管道内部调用过程。

#!/bin/bash

do_the_things()
        {
        rand="$((RANDOM % 10))"
        sleep $rand
        for i in `seq 1 10`; do sleep 1; echo "${rand}-$i"; done
        }

locked_print()
        {
        echo Started
        flock -e testlock cat
        }

export -f do_the_things
export -f locked_print

for f in a b c d; do
        (do_the_things | locked_print) &
done
wait

【讨论】:

  • @user313294 如果有很多事情要做,比如 500 件,它会立即将它们全部放到 CPU 上去做,不是吗?有时,当任务竞争 CPU/网络/磁盘带宽时,这实际上会减慢速度。 +1 为一个漂亮、整洁的解决方案。
  • @MarkSetchell 我认为其中 499 个将填满他们的管道缓冲区,并将在 io 上阻塞,直到释放锁,而 1 将输出其输出。
【解决方案2】:

试试这样的。我没有/使用git,所以我做了一个虚拟命令来在我的版本中模拟它。

#!/bin/bash
declare -a ERRORS
ERRORS=( )

function pull {
    cd "$1"
    echo Starting pull in $1
    for i in {0..9}; do echo "$1 Output line $i";done
    sleep 5
    echo "GITERROR: Dummy error in directory $1"
}

export -f pull

for f in extensions/*; do
  if [[ -d $f ]]; then
    ########## This code should run in parallel, but output of each thread
    ########## should be cached and printed sequentially one after another
    ########## pull function also updates a global var that will be used later
    echo $f
  fi
done | parallel -k pull | tee errors.tmp

IFS=$'\n' ERRORS=($(grep "^GITERROR:" errors.tmp))
rm errors.tmp

for i in "${ERRORS[@]}"; do
   echo $i
done

您会看到,即使有 4 个目录要拉取,整个脚本也只需要 5 秒 - 尽管执行了 4 批 sleep 5

【讨论】:

  • 最好使用find extensions -type d | parallel -k pull | tee errors.tmp
  • @user3132194 我只是在可能的情况下重新使用了 OP 的代码来演示该技术。我认为find 命令也将找到OP 命令不会找到的extensions 父目录。实际上,我认为 bash4 最适合使用 for d in exrensions/*/,但我的 OSX 上没有 bash4,正如我所说,我正专注于展示 GNU Parallel 的乐趣:-)
【解决方案3】:

通过添加 / 列出目录。 Parallel 生成一个 cd 到目录的 shell。如果 git pull 失败,则会打印一个魔术字符串。所有输出也作为副本保存在 out/1/* 中。完成所有拉取操作后,检查魔术字符串出现在哪些文件中并打印出这些命令的 STDOUT/STDERR。清理。

parallel --results out 'cd {} && (git pull || echo e_R_r_O_r)' :::  extensions/*/
grep -l e_R_r_O_r out/*/stdout | parallel 'grep -v e_R_r_O_r {//}/stdout; cat {//}/stderr >&2'
rm -r out

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 2017-09-21
    • 2020-12-15
    • 1970-01-01
    • 2018-01-16
    • 1970-01-01
    相关资源
    最近更新 更多