0. Общие моменты
Для корректной работы приведенного ниже скрипта необходимо, чтобы на серверах 1С Предприятия, на которых активированы программные лиценизии, были установлены следующие пакеты: ring, license-tools, zabbix-agent, zabbix_sender, а так же запущен RAS.
ВАЖНО: Ответственность за любые последствия, возникшие в результате внесения изменений в работающую систему по материалам данной статьи, лежат исключительно на лице, внесшим данные изменения!
1. Настройки на сервере, содержащем программные лицензии
1.1. Поместить скрипты 1c_common_module.sh и 1c_license_server.sh в каталог /etc/zabbix/scripts. Дать права на их выполнение
[fedotov@server_a ~]$ sudo chmod 755 /etc/zabbix/scripts/1c_*.sh
1.2. В конфигурационном файле zabbix_agentd.conf необходимо задать параметр Hostname (необходим для работы zabbix_seneder).
1.3. Добавить конфигурационный файл для zabbix-agent, содержащий необходимые значения параметра UserParameter
UserParameter=1c.ls.sessions[*],/etc/zabbix/scripts/1c_license_server.sh used $1
UserParameter=1c.ls.clusters.discovery[*],/etc/zabbix/scripts/1c_license_server.sh clusters
Текст 1. Содержимое файла /etc/zabbix/zabbix_agentd.d/userparameter_1c-ls.conf
1.4. Перезапустить сервис zabbix-agent
[fedotov@server_a ~]$ sudo systemctl restart zabbix-agent
1.5. Создать задание для cron, к примеру, выполняемое каждый час. Для этого в каталоге /etc/cron.hourly необходимо создать файл следующего содержания и установить для него права на исполнение
#!/bin/bash
/usr/bin/zabbix_sender -c /etc/zabbix/zabbix_agentd.conf -k 1c.ls.licenses -o $(nice -n 19 /etc/zabbix/scripts/1c_license_server.sh info) > /dev/null
Текст 2. Содержимое файла /etc/cron.hourly/1c_ls_license_info.sh.
2. Настройки на Zabbix-сервере
2.1. Сделать импорт шаблона 1c_license_server.xml
2.2. Добавить данный шаблон узлу, на котором активированы программные лицензии.
2.3. В случае, если RAS у вас развернут на нестандартных портах, то изменить макрос {$RAS_PORT}, унаследованный от шаблона, на необходимое значение.
2.4. С помощью макроса {$LIC_UTIL_LIMIT} можно установить пороговое значение отношения количества использованных лицензий (сеансов) к максимальному количеству сеансов, лицензируемых активированными на данном сервере лицензиями. По превышению данного значения срабатывает триггер с уровнем важности "Предупреждение" (значение по-умолчанию - 0.9).
Информация: Если количество использованных лицензий равно максимальному количеству лицензируемых сеансов, то срабатывает триггер с уровнем важности "Высокая".
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>4.4</version>
<date>2020-04-03T12:24:57Z</date>
<groups>
<group>
<name>Templates (Kaminsoft)</name>
</group>
</groups>
<templates>
<template>
<template>Template App 1C Enterprise License Server</template>
<name>Template App 1C Enterprise License Server</name>
<groups>
<group>
<name>Templates (Kaminsoft)</name>
</group>
</groups>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<items>
<item>
<name>[1С/Лицензии] Количество лицензий на клиенте</name>
<type>DEPENDENT</type>
<key>1c.ls.license.client</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>summary:\d+:\d+:\d+:(\d+):\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
<triggers>
<trigger>
<expression>{last()}>0</expression>
<name>[1С/Лицензии] Используются лицензии с клиентских компьютеров</name>
<status>DISABLED</status>
<priority>INFO</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<name>[1С/Лицензии] Количество файлов пользовательских лицензий</name>
<type>DEPENDENT</type>
<key>1c.ls.license.file</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>(\d+):\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.licenses</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Общее количество сеансов</name>
<type>DEPENDENT</type>
<key>1c.ls.license.total</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>summary:\d+:\d+:(\d+):\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Количество уникальных пользователей</name>
<type>DEPENDENT</type>
<key>1c.ls.license.uniq</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>summary:\d+:(\d+):\d+:\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Количество выданных лицензий</name>
<type>DEPENDENT</type>
<key>1c.ls.license.used</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>summary:(\d+):\d+:\d+:\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Количество лицензированных сеансов</name>
<type>DEPENDENT</type>
<key>1c.ls.license.user</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>\d+:(\d+)
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.licenses</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Количество веб-клиентов</name>
<type>DEPENDENT</type>
<key>1c.ls.license.webclient</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>summary:\d+:\d+:\d+:\d+:(\d+)
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item>
<item>
<name>[1С/Лицензии] Сводная информация</name>
<type>TRAP</type>
<key>1c.ls.licenses</key>
<delay>0</delay>
<history>0</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
</item>
<item>
<name>[1С/Лицензии] Активные сеансы</name>
<type>ZABBIX_ACTIVE</type>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
<delay>60s</delay>
<history>0</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
</item>
</items>
<discovery_rules>
<discovery_rule>
<name>[1С/Лицензии] Кластеры</name>
<type>ZABBIX_ACTIVE</type>
<key>1c.ls.clusters.discovery</key>
<delay>1h</delay>
<status>DISABLED</status>
<lifetime>7d</lifetime>
<item_prototypes>
<item_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Количество лицензий на клиенте</name>
<type>DEPENDENT</type>
<key>1c.ls.license.client[{#CLSTR_NAME}]</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>{#CLSTR_NAME}:\d+:\d+:\d+:(\d+):\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item_prototype>
<item_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Общее количество сеансов</name>
<type>DEPENDENT</type>
<key>1c.ls.license.total[{#CLSTR_NAME}]</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>{#CLSTR_NAME}:\d+:\d+:(\d+):\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item_prototype>
<item_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Количество уникальных пользователей</name>
<type>DEPENDENT</type>
<key>1c.ls.license.uniq[{#CLSTR_NAME}]</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>{#CLSTR_NAME}:\d+:(\d+):\d+:\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item_prototype>
<item_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Количество выданных лицензий</name>
<type>DEPENDENT</type>
<key>1c.ls.license.used[{#CLSTR_NAME}]</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>{#CLSTR_NAME}:(\d+):\d+:\d+:\d+:\d+
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item_prototype>
<item_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Количество веб-клиентов</name>
<type>DEPENDENT</type>
<key>1c.ls.license.webclient[{#CLSTR_NAME}]</key>
<delay>0</delay>
<history>30d</history>
<trends>90d</trends>
<applications>
<application>
<name>[1С] Программные лицензии</name>
</application>
</applications>
<preprocessing>
<step>
<type>REGEX</type>
<params>{#CLSTR_NAME}:\d+:\d+:\d+:\d+:(\d+)
\1</params>
</step>
</preprocessing>
<master_item>
<key>1c.ls.sessions[{$RAS_PORT}]</key>
</master_item>
</item_prototype>
</item_prototypes>
<graph_prototypes>
<graph_prototype>
<name>[1С/Лицензии/{#CLSTR_NAME}] Использование активированных лицензий</name>
<graph_items>
<graph_item>
<color>1B5E20</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.used[{#CLSTR_NAME}]</key>
</item>
</graph_item>
<graph_item>
<sortorder>1</sortorder>
<color>2774A4</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.uniq[{#CLSTR_NAME}]</key>
</item>
</graph_item>
<graph_item>
<sortorder>2</sortorder>
<color>FC6EA3</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.client[{#CLSTR_NAME}]</key>
</item>
</graph_item>
<graph_item>
<sortorder>3</sortorder>
<color>6C59DC</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.webclient[{#CLSTR_NAME}]</key>
</item>
</graph_item>
<graph_item>
<sortorder>4</sortorder>
<color>F7941D</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.total[{#CLSTR_NAME}]</key>
</item>
</graph_item>
</graph_items>
</graph_prototype>
</graph_prototypes>
</discovery_rule>
</discovery_rules>
<macros>
<macro>
<macro>{$LIC_CORP}</macro>
<value>0</value>
</macro>
<macro>
<macro>{$LIC_UTIL_LIMIT}</macro>
<value>0.9</value>
</macro>
<macro>
<macro>{$RAS_PORT}</macro>
<value>1545</value>
</macro>
</macros>
</template>
</templates>
<triggers>
<trigger>
<expression>{$LIC_CORP}=0 and {Template App 1C Enterprise License Server:1c.ls.license.used.last()}>={Template App 1C Enterprise License Server:1c.ls.license.user.last()}
or
{$LIC_CORP}=1 and ({Template App 1C Enterprise License Server:1c.ls.license.uniq.last()})>=({Template App 1C Enterprise License Server:1c.ls.license.user.last()}*1.1)</expression>
<name>[1С/Лицензии] Использованы все доступные клиентские лицензии</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger>
<trigger>
<expression>{$LIC_CORP}=0 and {Template App 1C Enterprise License Server:1c.ls.license.used.last()}/{Template App 1C Enterprise License Server:1c.ls.license.user.last()}>{$LIC_UTIL_LIMIT} or {$LIC_CORP}=1 and {Template App 1C Enterprise License Server:1c.ls.license.uniq.last()}/({Template App 1C Enterprise License Server:1c.ls.license.user.last()}*1.1)>{$LIC_UTIL_LIMIT}</expression>
<name>[1С/Лицензии] Количество использованных лицензий близко к максимальному</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
<dependencies>
<dependency>
<name>[1С/Лицензии] Использованы все доступные клиентские лицензии</name>
<expression>{$LIC_CORP}=0 and {Template App 1C Enterprise License Server:1c.ls.license.used.last()}>={Template App 1C Enterprise License Server:1c.ls.license.user.last()}
or
{$LIC_CORP}=1 and ({Template App 1C Enterprise License Server:1c.ls.license.uniq.last()})>=({Template App 1C Enterprise License Server:1c.ls.license.user.last()}*1.1)</expression>
</dependency>
</dependencies>
</trigger>
<trigger>
<expression>{Template App 1C Enterprise License Server:1c.ls.license.client.last()}/{Template App 1C Enterprise License Server:1c.ls.license.total.last()}>0.5</expression>
<name>[1С/Лицензии] Количество лицензий с клиентских компьютеров > 50%</name>
<status>DISABLED</status>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
<graphs>
<graph>
<name>[1С/Лицензии] Использование активированных лицензий</name>
<width>600</width>
<ymin_type_1>FIXED</ymin_type_1>
<graph_items>
<graph_item>
<color>F63100</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.user</key>
</item>
</graph_item>
<graph_item>
<sortorder>1</sortorder>
<color>1B5E20</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.used</key>
</item>
</graph_item>
<graph_item>
<sortorder>2</sortorder>
<color>2774A4</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.uniq</key>
</item>
</graph_item>
<graph_item>
<sortorder>3</sortorder>
<color>FC6EA3</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.client</key>
</item>
</graph_item>
<graph_item>
<sortorder>4</sortorder>
<color>6C59DC</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.webclient</key>
</item>
</graph_item>
<graph_item>
<sortorder>5</sortorder>
<color>F7941D</color>
<item>
<host>Template App 1C Enterprise License Server</host>
<key>1c.ls.license.total</key>
</item>
</graph_item>
</graph_items>
</graph>
</graphs>
</zabbix_export>
#!/bin/bash
#
# Мониторинг 1С Предприятия 8.3 (сервер лицензирования)
#
# (c) 2019-2020, Алексей Ю. Федотов
#
# Email: fedotov@kaminsoft.ru
#
source ${0%/*}/1c_common_module.sh 2>/dev/null || { echo "ОШИБКА: Не найден файл 1c_common_module.sh!" ; exit 1; }
CLSTR_CACHE="${CACHE_DIR}/1c_rmngr_cluster_cache"
LIC_COUNT_CACHE="${CACHE_DIR}/1c_rmngr_license_cache.${$}"
function licenses_summary {
[[ ! -f /etc/1C/1CE/ring-commands.cfg ]] && echo "ОШИБКА: Не установлена утилита ring!" && exit 1
LIC_TOOL=$(grep license-tools /etc/1C/1CE/ring-commands.cfg | cut -d: -f2)
[[ -z ${LIC_TOOL} ]] && echo "ОШИБКА: Не установлена утилита license-tools!" && exit 1
RING_TOOL=${LIC_TOOL%\/*\/*}"/*ring*/ring"
LIC_LIST=$(${RING_TOOL} license list --send-statistics false | sed 's/(.*//')
LIC_COUNT=0; LIC_USERS=0
execute_tasks license_info ${LIC_LIST[*]}
while read -r CURR_COUNT
do
((LIC_COUNT+=1))
((LIC_USERS+=${CURR_COUNT}))
done < ${LIC_COUNT_CACHE}
echo ${LIC_COUNT}:${LIC_USERS}
}
function license_info {
LIC_INFO=$(${RING_TOOL} license info --send-statistics false --name ${1} | grep -Pe '(Описание|Description).*на \d+ .*' | perl -pe 's/.*на (\d+) .*/\1/;')
[[ -n ${LIC_INFO} ]] && echo ${LIC_INFO} >> ${LIC_COUNT_CACHE}
}
function get_cluster_uuid {
CURR_CLSTR=$( timeout -s HUP ${RAS_TIMEOUT} rac cluster list ${1%%:*}:${RAS_PORT} |grep -Pe '(cluster|name|port)' | \
perl -pe 's/[ "]//g; s/^name:(.*)$/\1\n/; s/^cluster:(.*)/\1,/; s/^port:(.*)/\1,/; s/\n//' | \
grep -Pe ${1##*:} | perl -pe 's/(.*,)\d+,(.*)/\1\2/; s/\n/;/' )
echo ${1%%:*}:${CURR_CLSTR} >> ${CLSTR_CACHE}
}
function get_license_counts {
CLSTR_LIST=${1##*:}
for CURR_CLSTR in ${CLSTR_LIST//;/ }
do
timeout -s HUP ${RAS_TIMEOUT} rac session list --licenses --cluster=${CURR_CLSTR%,*} ${1%%:*}:${RAS_PORT} 2>/dev/null | \
grep -Pe "(user-name|rmngr-address|app-id)" | \
perl -pe 's/ //g; s/\n/|/; s/rmngr-address:(\"(.*)\"|)\||/\2/; s/app-id://; s/user-name:/\n/;' | \
awk -F"|" -v hostname=${HOSTNAME} -v cluster=${CURR_CLSTR#*,} 'BEGIN { sc=0; hc=0; cc=0; wc=0 } \
{ if ($1 != "") { sc+=1; uc[$1]; if ( tolower($3) == tolower(hostname) ) { hc+=1 } \
if ($2 == "WebClient") { wc+=1 } if ($3 == "") { cc+=1 } } } \
END {print cluster":"hc":"length(uc)":"sc":"cc":"wc }' >> ${LIC_COUNT_CACHE}
done
}
function used_license {
RMNGR_LIST=( $(pgrep -xa rmngr | sed -re 's/.*-(reg|)port ([0-9]+) .*/\0|\2/; s/.*-(reg|)host /|/; s/ .*\|/:/; s/^(\||[^|^:]+)//' | \
sort | uniq | awk -F: '{ if ( clstr_list[$1]== "" ) { clstr_list[$1]=$2 } else { clstr_list[$1]=clstr_list[$1]"|"$2 } } \
END { for ( i in clstr_list ) { print i":"clstr_list[i]} }' ) )
SRV_LIST=()
[[ -n ${1} ]] && RAS_PORT=${1}
if [[ ! -e ${CLSTR_CACHE} ||
${#RMNGR_LIST[*]} -ne $(wc -l ${CLSTR_CACHE} | cut -f1 -d" ") ||
$(date -r ${CLSTR_CACHE} "+%s") -lt $(date -d "-1 hour" "+%s") ]]; then
cat /dev/null > ${CLSTR_CACHE}
execute_tasks get_cluster_uuid ${RMNGR_LIST[*]}
fi
while read -r CURR_SRV
do
SRV_LIST+=(${CURR_SRV})
done < ${CLSTR_CACHE}
execute_tasks get_license_counts ${SRV_LIST[*]}
awk -F: 'BEGIN {ul=0; as=0; cl=0; uu=0; wc=0} { print $0; ul+=$2; uu+=$3; as+=$4; cl+=$5; wc+=$6; } \
END { print "summary:"ul":"uu":"as":"cl":"wc }' ${LIC_COUNT_CACHE};
}
function get_clusters_list {
[[ ! -f ${CLSTR_CACHE} ]] && echo "ОШИБКА: Не найден файл списка кластеров!" && exit 1
cut -f2 -d: ${CLSTR_CACHE} | perl -pe 's/;[^\n]/\n/; s/;//' | \
awk 'BEGIN {FS=","; print "{\"data\":[" } \
{print "{\"{#CLSTR_UUID}\":\""$1"\",\"{#CLSTR_NAME}\":\""$2"\"}," } \
END { print "]}" }' | \
perl -pe 's/\n//;' | perl -pe 's/(.*),]}/\1]}\n/'
}
cat /dev/null > ${LIC_COUNT_CACHE}
case ${1} in
info) licenses_summary ;;
used) used_license ${2} ;;
clusters) get_clusters_list ;;
*) echo "ОШИБКА: Неверный режим работы скрипта!"; exit 1;;
esac
[[ -f ${LIC_COUNT_CACHE} ]] && rm ${LIC_COUNT_CACHE}
#
# Мониторинг 1С Предприятия 8.3 (общие переменные и функции)
#
# (c) 2019-2020, Алексей Ю. Федотов
#
# Email: fedotov@kaminsoft.ru
#
# Имя сервера, используемое в кластере 1С Предприятия
HOSTNAME=$(hostname -s)
# Добавление пути к бинарным файлам 1С Предприятия
PATH=${PATH}:$(ls -d /opt/1C/v8*/[xi]* | tail -n1)
# Модуль менеджера задач
TM_MODULE="1c_common_tm.sh"
[[ -f ${0%/*}/${TM_MODULE} ]] && source ${0%/*}/${TM_MODULE} 2>/dev/null && TM_AVAILABLE=1
# Каталог для различных кешей скриптов
CACHE_DIR="/var/tmp/1C"
[[ ! -d ${CACHE_DIR} ]] && mkdir -p ${CACHE_DIR}
# Порт RAS по умолчанию
RAS_PORT=1545
# Максимальное время ожидания ответа от RAS
RAS_TIMEOUT=1.5
# Вывести разделительную строку длинной ${1} из символов ${2}
# По-умолчанию раделительная строка - это 80 символов "-"
function put_brack_line {
[[ -n ${1} ]] && LIMIT=${1} || LIMIT=80
[[ -n ${2} ]] && CHAR=${2} || CHAR="-"
printf "%.${LIMIT}d\n" 0 | sed "s/0/${CHAR}/g"
}
# Выполнить серию команд ${1} с параметром, являющимся элементом массива ${@}, следующим за ${1}
function execute_tasks {
TASK_CMD=${1}
shift
if [[ -z ${TM_AVAILABLE} || ${#@} -eq 1 ]]; then
for CURR_TASK in ${@}
do
${TASK_CMD} ${CURR_TASK}
done
else
TASKS_LIST=(${@})
tasks_manager ${TASK_CMD} 0
fi
}
P.S. Теперь следить за проектом можно через GitHub