Практическое написание сценариев командной оболочки Bash/Код-сниппеты: различия между версиями

На практике часто возникает необходимость запустить много команд за один раз в параллельном режиме, обычно в рамках одной глобальной задачи. При этом хотелось бы чтобы завершение каждой команды централизовано контролировалось. Bash предусматривает синтаксис запуска параллельной задачи через <code>&</code>, однако после такого отделения, команда начинает свою собственную жизнь в таблице процессов, о чем нужно помнить.
 
Следующая библиотека реализует простейший пул процессов. В терминологии библиотеки, все процессы в одном пуле являются частью ''стаи'' (''swarm''). Благодаря функции
 
;Код
<source lang=bash line=1>
# Файл: swarm.bash
# ==============================================================
# Библиотека распараллеливания по схеме одна задача-один процесс
# ==============================================================
# Автор: Grigorii Okhmak
#
# Лицензия
# --------
# MIT License
#
# Copyright (c) 2021 Grigorii Okhmak
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
 
[[ ! -z $_LIB_THR_PREFIX && ! -z $_LIB_THR_VERSION ]] &&
{
echo "Error: ${BASH_SOURCE##*/}:${BASH_LINENO[1]}: double library importing."
exit 129
}
 
readonly _LIB_THR_PREFIX='thr'
readonly _LIB_THR_VERSION='1.0'
 
# Compatible with Bash 4 only.
[[ -n "$BASHPID" ]] ||
{
echo "Error: ${BASH_SOURCE##*/}:${BASH_LINENO[1]}: Library '$_LIB_THR_PREFIX-$_LIB_THR_VERSION' is not supported by your Bash: $BASH_VERSION."
exit 129
}
 
declare -a __THR_SPAWNED_SWARM=()
declare _THR_PIPE='jghjYGa8.pipe'
readonly _THR_PIPE_PREFIX="/tmp"
readonly _THR_PIPE_PATH="$_THR_PIPE_PREFIX/$_THR_PIPE"
declare -i _THR_ENABLE_PROMISES=0
 
# Возвращает PID последнего участника стаи
thr_last_in_swarm() {
[[ ${#__THR_SPAWNED_SWARM[@]} -eq '0' ]] && printf "" && return 1
printf "${__THR_SPAWNED_SWARM[${#__THR_SPAWNED_SWARM[@]}-1]}"
return 0
}
 
# Уничтожить стаю
thr_kill_swarm() {
[[ ${#__THR_SPAWNED_SWARM[@]} -ne '0' ]] || return 1
kill -9 ${__THR_SPAWNED_SWARM[@]} 2>&1 >/dev/null
}
 
# Добавить нового участника в стаю
#
# $1 => Имя команды
# $2 => Аргументы команды
#
thr_add_to_swarm() {
__thr_swarm_queue() {
local routine_name=$1
local feedback_pipe=$2
(__thr_swarm_run "$@") &
[[ $? -eq '0' ]] || return 1
if [[ $! -ne '0' ]]; then
while :; do
local line
if read line <$feedback_pipe; then
__THR_SPAWNED_SWARM+=($line)
break
fi
done
return 0
fi
return 1
}
__thr_swarm_run() {
local routine_name=$1
shift
local pipe=$1
shift
[[ -p $pipe ]] || return 16
echo "$BASHPID" >$pipe
$routine_name "$@"
local -i rc=$?
if [[ $_THR_ENABLE_PROMISES -ne 0 ]]; then
touch "$_THR_PIPE_PREFIX/$BASHPID.promise"
echo "$rc" >"$_THR_PIPE_PREFIX/$BASHPID.promise"
fi
return $rc
}
[[ $# -ne 0 ]] || return 1
local rc
local routine="$1"
shift
local pipe="$_THR_PIPE_PATH"
if [[ ! -p $pipe ]]; then
mkfifo "$pipe"
rc=$?
fi
[[ $rc -eq 0 ]] && __thr_swarm_queue "$routine" "$pipe" "$@"
}
 
#
# Возвращает код возврата участника стаи. Сам код печатается на терминал.
# Код возврата можно запрашивать только, если внутренняя переменная библиотеки
# _THR_ENABLE_PROMISES имеет ненулевое значение.
#
# $1 => PID процесса стаи
#
# Возвращает:
# 0 если все успешно.
# 1 если не удалось найти файл с обещанием для данного процесса.
# 2 если поддержка обещаний отключена.
#
thr_get_return_code() {
local pid=$1
local pipe="$_THR_PIPE_PREFIX/$pid.promise"
local rc
[[ $_THR_ENABLE_PROMISES -ne 0 ]] || {
echo "Feature disabled"
return 2
}
[[ -f $pipe ]] || {
echo "-1"
return 1
}
while :; do
local line
if read rc <"$pipe"; then
rm -f "$pipe"
break
fi
done
echo "$rc"
return 0
}
 
# Присоединяет слушателя к стае. Функция завершает свою работу только, когда все процессы в стае завершатся.
thr_join_to_swarm() {
local delay=0.75
local counter=0
local is_empty='false'
local guard=2
[[ -n $(declare -f "__thr_join_to_swarm_before") ]] && __thr_join_to_swarm_before
while [[ ${#__THR_SPAWNED_SWARM[@]} -ne "0" ]]; do
counter=0
[[ -n $(declare -f "__thr_join_to_swarm_before_before") ]] && __thr_join_to_swarm_before_before
for task in "${__THR_SPAWNED_SWARM[@]}"; do
if [[ -n "$(ps -e --sort cmd --format pid | awk '{print $1}' | grep "^${task}$")" ]]; then
[[ -n $(declare -f "__thr_join_to_swarm_event") ]] && __thr_join_to_swarm_event "$task" 'tick'
else
[[ $guard -eq 0 ]] && __THR_SPAWNED_SWARM=()
if [[ $guard -ne 0 ]]; then
[[ -n $(declare -f "__thr_join_to_swarm_event") ]] && __thr_join_to_swarm_event "$task" 'stop'
unset "__THR_SPAWNED_SWARM[$counter]"
fi
[[ ${#__THR_SPAWNED_SWARM[@]} -le 1 ]] && : $(( guard -= 1 ))
fi
[[ $counter -eq 0 && ! -n ${__THR_SPAWNED_SWARM[*]} ]] && is_empty='true' && break
: $((counter += 1))
done
[[ $is_empty == 'true' ]] && break
[[ -n $(declare -f "__thr_join_to_swarm_after_after") ]] && __thr_join_to_swarm_after_after
sleep $delay
done
[[ -n $(declare -f "__thr_join_to_swarm_after") ]] && __thr_join_to_swarm_after
__THR_SPAWNED_SWARM=()
return 0
}
</source>
 
;Описание
Библиотека состоит из следующих функций:
* <code>thr_last_in_swarm</code>. Печатает PID последнего участника стаи. Если участников стаи нет, то напечатает пустоту. Функция обращается к массиву <code>__THR_SPAWNED_SWARM</code>, в котором хранятся PID всех участников стаи.
* <code>thr_kill_swarm</code>. Уничтожает стаю, а именно посылает сигнал <code>SIGKILL</code> всем ее участникам.
* <code>thr_add_to_swarm <команда> <аргументы-команды></code>. Добавляет новый процесс в стаю. Процесс запускается немедленно. Функция возвращает 0, если добавление удалось, иначе не ноль.
* <code>thr_get_return_code <PID></code>. Печатает код возврата завершившегося участника стаи. Данный механизм возможен, если установить внутреннюю переменную библиотеки <code>_THR_ENABLE_PROMISES</code> в любое ненулевое значение. По умолчанию эта функция отключена. Функция возвращает 0, если печать кода завершилась успешно; 1 — если не удалось найти файла с обещанием для данного процесса; 2 — если механизм получения кода возврата выключен. В реализации этой библиотеки используется подход файлов с обещаниями, через которые наблюдатель взаимодействует с участниками стаи.
* <code>thr_join_to_swarm</code>. Присоединяет текущую оболочку к стае в качестве наблюдателя до тех пор, пока не завершится последний участник стаи. К наблюдателю неявно можно подключить 5 необязательных перехватчика событий стаи. Перехватчики подключаются автоматически. Можно использовать следующие перехватчики:
** <code>__thr_join_to_swarm_before</code>. Вызывается перед началом наблюдения за стаей.
** <code>__thr_join_to_swarm_after</code>. Вызывается после завершения последнего процесса стаи, перед выходом из <code>thr_join_to_swarm</code>.
** <code>__thr_join_to_swarm_before_before</code>. Вызывается в начале очередного опроса активных участников стаи.
** <code>__thr_join_to_swarm_after_after</code>. Вызывается в конце очередного опроса активных участников стаи.
** <code>__thr_join_to_swarm_event <PID> <event></code>. Вызывается каждый раз, когда удалось получить статус очередного участника стаи. Получает на вход на аргумента, значения которых вы можете использовать:
*** <code>PID</code> — PID участника стаи, чей статус был только что получен.
*** <code>event</code> — по сути сам статус. Поддерживается всего два статуса: <code>tick</code> (процесс еще исполняется), <code>stop</code> (процесс завершил работу).
 
;Примеры
 
== Рисование на терминале ==
992

правки