Выполнять команды 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
Ответы
Ответ 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
Ответ 4
Я думаю, что следующий пример отвечает на некоторые из ваших вопросов, я изучаю остальную часть вопроса
(cat list1 list2 list3 | sort | uniq > list123) &
(cat list4 list5 list6 | sort | uniq > list456) &
от
Выполнение параллельных процессов в подоболочках
Ответ 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
, чтобы получить статус команды.