我如何使用bash在参数数组上并行执行多个命令,并且如果其中至少一个失败则失败

时间:2019-01-03 09:37:47

标签: bash parallel-processing xargs gnu-parallel

我有一个bash脚本,其功能需要与不同的参数并行运行。 我需要知道是否至少有一个执行失败(返回非零)-多少失败都没有关系。

该命令接受用于执行的参数数组。 由于高负载,我需要将并发限制为4个并发运行。 我还需要在父进程(运行bash脚本的父进程)中打印日志

这是我正在运行的功能:

function run_and_retry {
  EXIT_STATUS=0
  $COMMAND || EXIT_STATUS=$?

  if [ $EXIT_STATUS -ne 0 ]; then
    EXIT_STATUS=0
    $COMMAND || EXIT_STATUS=$?

  fi

  return $EXIT_STATUS
}

我尝试使用GNU parallel和xargs并遇到了问题。

使用xargs :(无法获得退出状态,并且当我在TravisCI中运行它时也无法正常工作)

PARAMETERS=(first-parameter second-parameter third-parameter)
export -f run_and_retry
echo "${PARAMETERS[@]}" | xargs -P 4 -n 1 -I {} bash -c "run_and_retry {}"

与GNU并行:

PARAMETERS=(first-parameter second-parameter third-parameter)
export -f run_and_retry
parallel -j 4 -k --lb 2 run_and_retry {} ::: echo "${PARAMETERS[@]}" 

2 个答案:

答案 0 :(得分:6)

如此您接近使GNU Parallel的语法正确:

COMMAND=echo
PARAMETERS=(first-parameter second-parameter third-parameter)
parallel -j 4 -k --retries 2 "$COMMAND" {} ::: "${PARAMETERS[@]}" ||
  echo $? commands failed. More than 99 if $? = 100

或者,如果您真的坚持要自己重试:

PARAMETERS=(first-parameter second-parameter third-parameter)
export -f run_and_retry
parallel -j 4 -k run_and_retry {} ::: "${PARAMETERS[@]}" ||
  echo One or more commands failed

答案 1 :(得分:2)

  

我需要知道至少有一个执行失败(返回非零)

来自posix xargs

  

退出状态

     

1-125
  不能满足要求的命令行进行汇编,一个或多个实用程序调用返回非零退出状态,或发生其他错误。

man xargs似乎有点不同:

  

退出状态

     

123,如果以状态1-125退出命令的任何调用

但是我会检查命令的返回状态,并从函数中返回预定义的数字(例如1)来处理该数字。

parameters=(1 2 3 fail)

func() { 
    COMMAND=sleep
    # I guess OP intends to try running COMMAND twice
    if ! "$COMMAND" 0."$1" && ! "$COMMAND" 0."$1"; then
        return 1
    fi
}

export -f func
if printf "%s\0" "${parameters[@]}" | xargs -0 -P4 -n1 -t -- bash -c 'func $1' -- ; then
   echo "Success!"
else
   echo "Error!"
fi

可通过tutorialspoint获得实时版本。

好吧,我们甚至可以手动计算孩子的数量,而使用wait -n则非常简单。来自stackoverflow - WAIT for “1 of many process” to finish

  

bash 4.3在内置的wait命令中添加了-n标志,该标志使脚本等待下一个孩子完成。

所以我们可以:

cnt=0
failed=false
for i in "${parameters[@]}"; do
    ( func "$i" ) &
    if (( cnt < 4 )); then
        cnt=$((cnt+1))
    else
        # handle more then 4 processes
        if ! wait -n; then
           failed=true
        fi
    fi
done
# handle still running processes after all have been forked
for i in $(seq $cnt); do
    if ! wait -n; then
        failed=true
    fi
done

if "$failed"; then
    echo "One of the jobs failed!"
fi