Подтвердить что ты не робот

Выполнять команды bash параллельно, отслеживать результаты и подсчитывать

Мне было интересно, как, если возможно, я могу создать простое управление заданиями в BASH для обработки нескольких команд параллельно. То есть, у меня есть большой список команд для запуска, и я бы хотел, чтобы их работала в любой момент времени.

Я знаю немного о bash, поэтому вот те требования, которые делают его сложным:

  • Команды имеют переменное время работы, поэтому я не могу просто создать 2, подождать, а затем продолжить следующие два. Как только одна команда будет выполнена, должна быть запущена следующая команда.
  • Контрольный процесс должен знать код выхода каждой команды, чтобы он мог содержать в совокупности количество неудачных

Я думаю, как-то я могу использовать trap, но я не вижу простого способа получить выходное значение дочернего элемента внутри обработчика.

Итак, любые идеи о том, как это можно сделать?


Ну, вот некоторые доказательства кода концепции, которые, вероятно, должны работать, но он разбивает bash: неверные строки команд, созданные, зависающие, а иногда и дампы ядра.

# need monitor mode for trap CHLD to work
set -m
# store the PIDs of the children being watched
declare -a child_pids

function child_done
{
    echo "Child $1 result = $2"
}

function check_pid
{
    # check if running
    kill -s 0 $1
    if [ $? == 0 ]; then
        child_pids=("${child_pids[@]}" "$1")
    else
        wait $1
        ret=$?
        child_done $1 $ret
    fi
}

# check by copying pids, clearing list and then checking each, check_pid
# will add back to the list if it is still running
function check_done
{
    to_check=("${child_pids[@]}")
    child_pids=()

    for ((i=0;$i<${#to_check};i++)); do
        check_pid ${to_check[$i]}
    done
}

function run_command
{
    "[email protected]" &
    pid=$!
    # check this pid now (this will add to the child_pids list if still running)
    check_pid $pid
}

# run check on all pids anytime some child exits
trap 'check_done' CHLD

# test
for ((tl=0;tl<10;tl++)); do
    run_command bash -c "echo FAIL; sleep 1; exit 1;"
    run_command bash -c "echo OKAY;"
done

# wait for all children to be done
wait

Обратите внимание, что это не то, чего я в конечном счете хочу, но будет основой для получения того, что я хочу.


Followup. Я реализовал систему для этого в Python. Таким образом, любой, кто использует Python для скриптинга, может обладать вышеуказанной функциональностью. См. shelljob

4b9b3361

Ответ 1

GNU Parallel - это awesomesauce:

$ parallel -j2 < commands.txt
$ echo $?

Он установит статус выхода на количество неудачных команд. Если у вас более 253 команд, проверьте --joblog. Если вы не знаете все команды спереди, проверьте --bg.

Ответ 2

Могу ли я убедить вас использовать make? Это имеет то преимущество, что вы можете сказать, сколько команд нужно выполнять параллельно (изменить номер -j)

echo -e ".PHONY: c1 c2 c3 c4\nall: c1 c2 c3 c4\nc1:\n\tsleep 2; echo c1\nc2:\n\tsleep 2; echo c2\nc3:\n\tsleep 2; echo c3\nc4:\n\tsleep 2; echo c4" | make -f - -j2

Вставьте его в Makefile, и он будет намного читабельнее

.PHONY: c1 c2 c3 c4
all: c1 c2 c3 c4
c1:
        sleep 2; echo c1
c2:
        sleep 2; echo c2
c3:
        sleep 2; echo c3
c4:
        sleep 2; echo c4

Остерегайтесь, это не пробелы в начале строк, они TAB, поэтому вырезать и вставлять здесь не будем.

Поместите "@" infront каждой команды, если вы не выполните команду эхом. например:.

        @sleep 2; echo c1

Это остановит первую неудачную команду. Если вам нужно подсчет сбоев, вам нужно каким-то образом спроектировать это в make файле. Возможно, что-то вроде

command || echo F >> failed

Затем проверьте длину отказа.

Ответ 3

Проблема заключается в том, что вы не можете дождаться завершения одного из нескольких фоновых процессов. Если вы наблюдаете статус задания (используя задания), то завершенные фоновые задания удаляются из списка заданий. Вам нужен другой механизм, чтобы определить, закончилось ли фоновое задание.

В следующем примере используются запуски фоновых процессов (спит). Затем он выполняет петли с помощью ps, чтобы убедиться, что они все еще запущены. Если он не использует wait для сбора кода выхода и запускает новый фоновый процесс.

#!/bin/bash

sleep 3 &
pid1=$!
sleep 6 &
pid2=$!

while ( true ) do
    running1=`ps -p $pid1 --no-headers | wc -l`
    if [ $running1 == 0 ]
    then
        wait $pid1
        echo process 1 finished with exit code $?
        sleep 3 &
        pid1=$!
    else
        echo process 1 running
    fi

    running2=`ps -p $pid2 --no-headers | wc -l`
    if [ $running2 == 0 ]
    then
        wait $pid2
        echo process 2 finished with exit code $?
        sleep 6 &
        pid2=$!
    else
        echo process 2 running
    fi
    sleep 1
done

Изменить: использование SIGCHLD (без опроса):

#!/bin/bash

set -bm
trap 'ChildFinished' SIGCHLD

function ChildFinished() {
    running1=`ps -p $pid1 --no-headers | wc -l`
    if [ $running1 == 0 ]
    then
        wait $pid1
        echo process 1 finished with exit code $?
        sleep 3 &
        pid1=$!
    else
        echo process 1 running
    fi

    running2=`ps -p $pid2 --no-headers | wc -l`
    if [ $running2 == 0 ]
    then
        wait $pid2
        echo process 2 finished with exit code $?
        sleep 6 &
        pid2=$!
    else
        echo process 2 running
    fi
    sleep 1
}

sleep 3 &
pid1=$!
sleep 6 &
pid2=$!

sleep 1000d

Ответ 5

Существует еще один пакет для систем debian с именем xjobs.

Вы можете проверить это:

http://packages.debian.org/wheezy/xjobs

Ответ 6

Если вы не можете установить parallel по какой-либо причине, это будет работать в простой оболочке или bash

# String to detect failure in subprocess
FAIL_STR=failed_cmd

result=$(
    (false || echo ${FAIL_STR}1) &
    (true  || echo ${FAIL_STR}2) &
    (false || echo ${FAIL_STR}3)
)
wait

if [[ ${result} == *"$FAIL_STR"* ]]; then
    failure=`echo ${result} | grep -E -o "$FAIL_STR[^[:space:]]+"`
    echo The following commands failed:
    echo "${failure}"
    echo See above output of these commands for details.
    exit 1
fi

Где true и false являются заполнителями для ваших команд. Вы можете также эхо $? вместе с FAIL_STR, чтобы получить статус команды.