Как за 3 дня создать игровой движок для новелл

Одним дождливым питерским днем мой проект в UE4 перестал загружаться, и из-за этого я захотел сделать свой собственный движок. И одна новелла подтолкнула меня сделать движок именно для новелл. Если вам хочется узнать побольше и вы не боитесь goto, gosub и других ужасов, добро пожаловать под кат.
image

Оглавление

Акт 1. Введение.

Акт 2. Разработка.

1. Текст.
2. Графика.
3. Звуки и эффекты.

Акт 3. Редактор.

Акт 4. Заключение.

Акт 1. Введение.

image
Все началось с того, как после прохождения N-ой новеллы друг порекомендовал мне Katawa Shoujo. Как оказалось официальной версии в App Store нет и не предвидится, только .ipa на 4pda.

Этот же друг зная, что я пытаюсь создать игру уже третий месяц предложил мне портировать Катаву на iPad без Jailbreak. Выбор пал на smart BASIC, так как я пользуюсь этой программой уже полтора года и знаю синтаксис её внутреннего языка программирования (также у smart BASIC есть SDK для XCode).

Сразу же был скачан архив со всеми файлами игры кроме текста. Но ни он не я не знали, что это приведет к созданию своего движка для удобства разработки.

Акт 2. Разработка

image

1. Текст

Для начала я создал папки (спойлер).

Папки

/Sprites
/Event
/BGs
/Music
/Scenario
/Scripts
/Scripts/Ren_sB
/Scripts/Ren_sB/Functions
/UI
/UI/main
/VFX

Файл Katawa Shoujo Port.txt будет подгружать все скрипты.

scenario$ = “Habr” {Scripts/colorcodes.txt} {Scripts/Ren_sB/render.txt} 

Файл colorcodes.txt будет содержать RGB цвета имен для рендера, для примера возьму Сидзунэ и ее синее выделение:

data "Сидзунэ","107","174","239" dim colors$(1,2) read colors$(color,0) 

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

За это будет отвечать скрипт speak.txt.

Для начала в файл load.txt я прописал загрузку спрайта bg-say.png (поле для реплик) и шрифта playtime_cyrillic:

font "Font/playtime_cyrillic2.ttf" load a$ sprite "bg-say" load "UI/bg-say.png" sprite "bg-say" resize 1024,205 sprite "bg-say" at 0,screen_height()-205 sprite "bg-doublespeak" load "UI/bg-doublespeak.png" sprite "bg-doublespeak" resize 1024,205 sprite "bg-doublespeak" at 0,screen_height()-205 sprite "ctc-strip" load "UI/ctc-strip.png" get sprite "ctc_strip" size w,h sprite "ctc_strip" resize w*1.28,h*1.28 sprite "ctc_strip" at screen_width()-w*1.28-15,screen_height()-52 sprite "ctc_strip" delay 0.05 

После я решил использовать в скрипте сценария знак “|” как разделитель команды, цели команды, вторичной цели, действия и аттрибута.

Показ текста: “1|перс|текст”

1|Сейдзунэ|[Привет, Хабр] 

Файл render.txt считывает эту команду и перенаправляет на метку alone

render.txt

graphics set toolbar off set orientation landscape  file "Scenario/"&scenario$&".txt" readline hmm$ count = count+1 end = file_end("Scenario/"&scenario$&".txt") if end = 0 then goto count  file "Scenario/"&scenario$&".txt" setpos 0  dim info$(count,5)  for l=0 to count-1 file "Scenario/"&scenario$&".txt" readline line$  splite line$ to temp$,n with "|" for m = 0 to n-1 info$(l,m) = temp$(m) next m next l  'Разбор массива текста, перенаправления на подпрограммы ' show: for y = 0 to count-1 if info$(y,0) = "1" then gosub alone ... alone: {Scripts/Ren_sB/Functions/Speak.txt} return 

speak.txt

sprite "bg-say" show name$ = info$(y,1) text$ = info$(y,2)  'Поиск кода цвета данного персонажа ' for color = 0 to 6 if name$ = colors$(color,0) then r = colors$(color,1) g = colors$(color,2) b = colors$(color,3) endif next color  'Создание двух полей ' field 1 text name$ at 20,575 size 600,40 ro field 1 back alpha 0 field 1 font color r/255,g/255,b/255 field 1 font name a$ field 1 font size 28  field 2 text render$ at 20,615 size screen_width()-20,130 ro ml field 2 back alpha 0 field 2 font color 1,1,1 field 2 font size 28 field 2 font name a$  'Анимация текста ' for k = 0 to len(text$)-1 'Использование касания для пропуска анимации ' gosub 3 render$ = substr$(text$,0,k) field 2 text render$ pause 0.025 next k  'Ожидание касания для перехода ' sprite "ctc_strip" show ! sprite "ctc_strip" loop gosub 1 sprite "ctc_strip" hide 

1

1 x1 = touch_x(0) y1 = touch_y(0) if x1 > -1 or y1 > -1 then goto 2 slowdown goto 1  2 x1 = touch_x(0) y1 = touch_y(0) if x1 = -1 or y1 = -1 then return endif slowdown goto 2 

3

3 x1 = touch_x(0) y1 = touch_y(0) if x1 > -1 or y1 > -1 then tapped = 1 endif  if tapped = 1 then if x1 = -1 or y1 = -1 then tapped = 0 if len(text$) = 0 then k = lengthmax - 1 else k = len(text$) - 1 endif return else return endif endif return 

Результат:

image

“Но ведь в Катаве может говорить два персонажа одновременно!” Для этого есть отдельная команда: “2|перс1&перс2|текст1&текст2”:
2|Хисао&Лилли|«Привет!»&”Здравствуйте!"

Добавим в render.txt строки:

render.txt

... show: ... if info$(y,0) = 2 then gosub together ... together: {Scripts/Ren_sB/Functions/Double_Speak.txt} return 

Double_Speak.txt выполняет данные команды:

Double_Speak.txt

sprite "bg-doublespeak" show names$ = info$(y,1) 'Имена и тексты ' nd = instr(names$,"&") name1$ = substr$(names$,0,nd-1) name2$ = substr$(names$,nd+1,len(names$)-1) texts$ = info$(y,2) td = instr(texts$,"&") text1$ = substr$(texts$,0,td-1) text2$ = substr$(texts$,td+1,len(texts$)-1) 'Определение цвета для каждого персонажа ' for color = 0 to 6 if name1$ = colors$(color,0) then r1 = colors$(color,1) g1 = colors$(color,2) b1 = colors$(color,3) endif next color  for color = 0 to 6 if name2$ = colors$(color,0) then r2 = colors$(color,1) g2 = colors$(color,2) b2 = colors$(color,3) endif next color 'Создание полей ' field 11 text name1$ at 20,575 size 300,40 ro field 11 back alpha 0 field 11 font color r1/255,g1/255,b1/255 field 11 font name a$ field 11 font size 28  field 12 text name2$ at 535,575 size 300,40 ro field 12 back alpha 0 field 12 font color r2/255,g2/255,b2/255 field 12 font name a$ field 12 font size 28  field 21 text render1$ at 20,615 size screen_width()/2-20,130 ro ml field 21 back alpha 0 field 21 font color 1,1,1 field 21 font size 28 field 21 font name a$  field 22 text render2$ at 535,615 size screen_width()/2-20,130 ro ml field 22 back alpha 0 field 22 font color 1,1,1 field 22 font size 28 field 22 font name a$  'Определение максимальной и минимальной длинны ' lengthmax = max(len(text1$),len(text2$)) lengthmin = min(len(text1$),len(text2$))  'Анимация текста '  if len(text1$) = len(text2$) then for k = 0 to lengthmax-1 gosub 3 render1$ = substr$(text1$,0,k) field 21 text render1$ render2$ = substr$(text2$,0,k) field 22 text render2$ pause 0.025 next k endif  if len(text1$) > len(text2$) then for k = 0 to lengthmax-1 gosub 3 render1$ = substr$(text1$,0,k) field 21 text render1$ if k < lengthmin then render2$ = substr$(text2$,0,k) field 22 text render2$ endif pause 0.025 next k endif  if len(text2$) > len(text1$) then for k = 0 to lengthmax-1 gosub 3 render2$ = substr$(text2$,0,k) field 22 text render2$ if k < lengthmin then render1$ = substr$(text1$,0,k) field 21 text render1$ endif pause 0.025 next k endif  'Ожидание касания ' sprite "ctc_strip" show ! sprite "ctc_strip" loop gosub 1 sprite "ctc_strip" hide 

Результат:

image

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

Спасибо всем тем, кто дочитал до конца статьи. До встречи в следующей части!

ссылка на оригинал статьи http://habrahabr.ru/post/274399/

Ajenti 2 и прочие новости


Сначала о неприятном.

image

Так уж вышло, что некоторое время назад я создал инструмент, который позволял людям быстро и легко настраивать систему и сайты на LEMP, Node и [g]Unicorn. В то время я был студентом, и свободного времени было больше, поэтому я решил что больше — значит лучше, и пошел по следам Webmin в вопросе количества плагинов. В результате этого я не только повысил свое ЧСВ, но и стал получать по десятку support-запросов в день, причем первая половина из них была уровня how do I PHP?, а вторая — не имеющие отношения к самой панели вопросы по настройке линукса.

И теперь мне ничего не остается кроме как сказать: мне очень жаль, я все пр@срал. Я не могу в одиночку обеспечивать поддержку продукта такого размера.

Что я решил сделать по этому поводу? Я решил сделать меньше, но лучше: бета Ajenti 2. Я оставил самые необходимые инструменты администратора — файловый менеджер, редактор, терминал, сервисы, пакеты и дашборд, обернув все это в быстрый интерфейс с поддержкой мобильных устройств. Насколько хорошо это у меня вышло — судить вам.

В виду всего этого, сейчас я стою перед дилеммой: стоит мне объявить это новым релизом Ajenti, и существующая аудитория проекта заживо меня распнет ввиду уменьшившегося функционала. А вновь наращивать проект до размера webmin/cpanel у меня нет ни желания, ни возможности. Этот пост — по сути крик отчаяния, так как никакого решения этой проблемы я не вижу.

Однако…

Жить стало лучше, жить стало веселее

Новый фронтенд переписан на AngularJS, работает быстрее, надежнее и обладает модной в этом сезоне адаптивной версткой.

RAM

Занимает меньше памяти (~50-60 МБ + ~10 МБ на сессию). Заткнуты имеющиеся утечки. Можно ограничить размер пула сессий.

Упрощенное API

UI теперь не передается туда-сюда целиком после каждого клика.

Изоляция сессий на уровне ОС

Неавторизованные сессии теперь работают в «песочнице», а авторизованные — под соответствующими аккаунтами.Возможность элевации через sudo.

Аутентификация по сертификату

Augeas

Плагины могут удаленно редактировать конфиги через деревья Augeas.

Пост получился очень сумбурным, как и мои мысли. Буду рад услышать ваше мнение в комментариях. Удачного администрирования в Новом году!

Сайт | Документация | Github

ссылка на оригинал статьи http://habrahabr.ru/post/274397/

Готовность сети Yota к новогодним праздникам

Новогодние праздники — особенный период. В это время страна дружно гуляет, отдыхает, безудержно тратит деньги на пиротехнику и всякие вкусности, включая ингредиенты для Оливье. Взлетают продажи всевозможных товаров и услуг, люди ходят друг к другу в гости, а в новогоднюю ночь поздравляют всех своих родных и близких. В том числе, посредством мобильных телефонов. И в преддверии Нового Года мы решили рассказать о том, как меняется нагрузка на нашу сеть на рубеже смены календаря, и как мы к этому готовимся.

«Широка страна моя родная» — это не хвастовство, а констатация факта. Россия столь велика, что наша территория поделена на 11 часовых поясов, такого нет больше ни в одной стране мира. И первыми в Новый Год входят регионы, живущие по «Камчатскому» времени. И далее по стране идёт волна новых годов, так что к тому моменту, как европейская часть России начинает отсчитывать бой курантов, утомившаяся Камчатка давно спит крепким утренним сном.

В обычные дни показатель нагрузки на сеть (TPS — transactions per second, количество голосовых вызовов в единицу времени) составляет около 600 тысяч соединений ежесекундно (из них 53% исходящие соединения, 47% — входящие).

Поздравлять друг друга с Новым Годом люди начинают заранее. Кто-то опасается, что потом не сможет дозвониться, кто-то не хочет потом отвлекаться, кому-то просто не терпится поздравить. Поэтому нагрузка на сеть начинает возрастать примерно с 22:00. К 23:00 она достигает своего пика и держится на этом уровне примерно два часа, после чего начинается спад активности клиентов. В период пика количество голосовых вызовов увеличивается иногда в 10 раз, SMS – до 15 раз. И вскоре после 1:30 активность клиентов быстро падает почти до нуля: ведь нужно отдать должное праздничному столу, отправиться на прогулку или в гости.

В новогоднюю ночь количество голосовых соединений вырастает на 80-100%, количество отправляемых SMS вырастает на 300-400%. При этом любопытно, что объём мобильного трафика в новогодние каникулы даже несколько уменьшается. При чём потребление мобильного трафика остаётся низким на всём протяжении новогодних праздников. И это, пожалуй, правильно — в эти дни лучше уделять внимание семье и друзьям, а не гаджетам. Что же касается самого контента, то благодаря безлимитному интернету для наших пользователей подавляющая доля приходится на потоковое видео: фильмы, сериалы, онлайн-трансляции, ролики.

Итак, после 1:30 люди почти перестают звонить и писать друг другу SMS. Кто-то гуляет на всю катушку до утра, кто-то выпивает шампанское, съедает салатик и идёт спать. Утром снова начинается медленный рост нагрузки на сеть, и к 9-10 часам показатели выходят на уровень выходного дня. В эту новогоднюю ночь мы прогнозируем существенный рост трафика мессенджеров.

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

Основная подготовка к Новому Году заключается в расширении количества голосовых присоединений. Необходимое увеличение рассчитывается исходя из текущего значения стандартной нагрузки и прогнозного значения на новогоднюю ночь. При этом, в отличие от других мобильных операторов, чья абонентская база давно стабилизировалась, после новогодних праздников мы не отказываемся от этих дополнительных мощностей, поскольку количество наших клиентов постоянно растёт. И расширение количества голосовых присоединений лишним не будет. В первую очередь расширяются зоновые присоединения (звонки на местные стационарные телефоны), и МГ/МН присоединения (междугородние/международные), через которое у нас идут вызовы на других операторов, за исключением «МегаФона».

Если раньше существовала определенная мода – поздравлять друг друга именно в 00.00 — в последние годы пользователи начинают отправлять поздравления еще до 00.00. Трафик 1 января соизмерим с нагрузкой в обычный выходной день. Затем, в период новогодних каникул, традиционно наблюдается небольшое снижение активности клиентов – примерно на 30% по сравнению с обычным уикендом.

* * *
Желаем вам активно встретить 2016 год, вдруг старая примета — как встретишь, так и проведёшь — существует не просто так!
С наступающим вас!

ссылка на оригинал статьи http://habrahabr.ru/post/274377/

happy(new Year(2014));

Собственно, буду немногословен, передаю всем привет из 2016 года!
Всем-всем-всем хаброжителям хочу пожелать здоровья, удачи, добра, творческого простора и конечно безбажного кода
а на этих слова вспомнил и отискал цитату kahi4 из позапрошлого новогоднего топика:

Все желают хорошего и безбажного кода, а я же хочу отдельно поздравить всех инженеров хабра!
С Новым годом, товарищи, без которых не было бы столь большого количества таких клевых вещей)
Желаю, чтобы у Вас просто удавались все расчеты, без проблем находились оптимальные программы управления, сходились все ряды и все системы были устойчивые! Чтобы у навигационных устройств СКО не выходило за требования, чтобы электромагнитные волны распространялись так, как вам нужно, а не как вздумается природе, чтобы фильтр Калмана не «схлопывался», а радиоэлектроника не горела!
Чтобы Вы шли по намеченному пути и не отклонялись от него больше, чем на три сигмы, а все случайные шумы были Гауссовскими!
Ну и общее — чтобы Вам всегда дул попутный ветер во всех ваших начинаниях и Вы всегда достигали поставленные цели!
С Новым годом, тов. инженеры, с Новым годом, жители Хабра, с Новым годом, IT сообщество)

P.S. А тем, кому скучно встречать новый год одному или довелось провести его на смене — добро пожаловать в Новогодний хаброчатик v.2016

ссылка на оригинал статьи http://habrahabr.ru/post/274393/

Мониторим S.M.A.R.T. в Zabbix

Для тех кто использует Zabbix, и хочет научится делать свои шаблоны и мониторить не стандартные системы (которых еще нет в Zabbix), а также,
кому нужен расширенный мониторинг S.M.A.R.T., и кого не устроили уже существующие шаблоны, прошу под кат.

Все началось с того, что уже существующий шаблон для S.M.A.R.T. меня не устроил. Он позволял смотреть довольно ограниченное число атрибутов, и наращивание его до приемлемого для меня уровня становилось накладным. Особенно из-за того, что он использовал простые поля в Zabbix Agent, и при увеличении их числа становилось как-то не по себе. Давайте глянем на одну строчку в конфиге, с запросом параметра (подобных там много):

UserParameter=uHDD[*], sudo smartctl -A /dev/$1| grep "$2"| tail -1| cut -c 88-|cut -f1 -d' '

Все хорошо, если у вас только этот параметр, ну или парочка, но если у вас их десять? И дисков к примеру десяток? На каждый такой параметр мы будем дергать smartctl (лишний раз подергивая диск)? Кроме того, каждый такой параметр, это отдельный запрос от Zabbix Server (ну или групповой запрос с параметрами подставляемыми вместо *). В такой ситуации, к сожалению решения нет, Zabbix Agent не поддерживает другой способ получения данных, но нам на помощь приходит Zabbix Trapper и утилита zabbix_sender, которые позволяют отправить целую пачку параметров.

Вот подготовкой данных для них мы и займемся.
Начнем с поиска устройств, которые вообще отдают нам S.M.A.R.T., для чего нам понадобится:

  • Драйвер sg (modprobe sg), он позволяет кроме всего прочего, увидеть диски за рядом RAID контроллеров (в частности у меня Adaptec)
  • Утилита sg_map, которая даст нам список устройств ассоциированных через драйвер sg
  • И конечно smartctl

Напишем такой скрипт (smartdiscovery.sh):

#!/bin/bash # require: sg module and sg_map util # Get know generic scsi device from sg_map or from /usr/local/etc/smartdev.lst (is prefered used), # and then try to read some S.M.A.R.T. attribue, if success, echo output combination to SDTOUT  modprobe sg RUNPATH=$(dirname $0) DEV_TYPE=(sat scsi ata) while read -r -a attr; do 	if [ -z "${attr[1]}" ]; then 		DEV=${attr[0]} 	else 		DEV=${attr[1]} 	fi 	for i in "${DEV_TYPE[@]}";do 		smartctl -A -d $i $DEV | grep -q 'ID#' 		if [[ $? == 0 ]]; then 			DEV=$(basename $DEV) 			grep -q $DEV /usr/local/etc/smartdev.lst  			if [[ $? != 0 ]]; then 				echo "$DEV $i" 			fi 			break 		fi 	done done  < <(sg_map) cat /usr/local/etc/smartdev.lst 

Он поищет для нас устройства (ищет утилитами и сверяет найденное с файлом /usr/local/etc/smartdev.lst, если совпадение найдено то в последствии используется значения из файла) и выдаст список в виде пар значений: <имя устройства> <тип подключения>
Дальше мы передадим этот список другому скрипту (zabbix_smart_discovery.sh), который сформирует JSON для Zabbix:

zabbix_smart_discovery.sh

#!/bin/bash # Formating discovering device list to JSON format for zabbix  echo -e "{\n\t\"data\":[" LN=0 while IFS=' ' read -r -a attribute; do	 	if [[ $LN != 0 ]]; then 		echo "," 	fi 	echo -e "\t\t{ \"{#DEVNAME}\":\"${attribute[0]}\", \"{#DEVTYPE}\":\"${attribute[1]}\" }\c" 	LN=1 done  < /dev/stdin echo -e "\n\t]\n}" 

Вывод будет примерно такой:

smartctl.discovery

{         "data":[                 { "{#DEVNAME}":"sg1", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg2", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg3", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg4", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg5", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg6", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg7", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sg8", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sdb", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sdc", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sdd", "{#DEVTYPE}":"sat" },                 { "{#DEVNAME}":"sde", "{#DEVTYPE}":"sat" }         ] } 

{#DEVNAME} и {#DEVTYPE} это макросы, которые будут использованы Zabbix для подстановок.
Скрипт smart2zabbix.sh сформирует данные для Zabbix Trapper

smart2zabbix.sh

#!/bin/bash # Format output from smartctl to zabbix_sender input # $1 is path for examine device # $2 type of device is used in smartctld -d paramentr # $3 hostname of monitoring system, can set to '-', if using -s or -c paramentr in zabbix_sender  DEV_PATH=$1 DEV_TYPE=$2 HOSTNAME=$3 HEADERS=(id attribute_name flag value worst thresh type updated when_failed raw_value) DEVICE=$(basename $DEV_PATH) SECTION='' while IFS='' read -r line; do 	case $line in 		'=== START OF INFORMATION SECTION ===') 			SECTION='INFO' 			continue 		;; 		'=== START OF READ SMART DATA SECTION ===') 			SECTION='HEALF' 			continue 		;; 		'ID#'*) 			SECTION='ATTR' 			continue 		;; 	esac 	case $SECTION in 		'INFO') 			if [ -z "$line" ]; then 				SECTION='' 			else 				IFS=':' read -r -a attribute <<< "$line" 				PRE="$HOSTNAME smartctl.info[$DEVICE," 				ATTR_V=$( echo ${attribute[1]} | sed -e 's/^[ \t]*//' ) 				ATTR_N=$(echo ${attribute[0]} | tr '[:upper:]' '[:lower:]' | sed 's/ /_/' ) 				case ${attribute[0]} in 					'Model Family') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'Device Model') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'Serial Number') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'Firmware Version') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'User Capacity') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'Sector Size' | 'Sector Sizes') 						ATTR_N=$(echo 'Sector Size' | tr '[:upper:]' '[:lower:]' | sed 's/ /_/' ) 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 					'Rotation Rate') 						echo "${PRE}$ATTR_N] \"$ATTR_V\"" 					;; 				esac 			fi 			 		;; 		'HEALF') 			if [ -z "$line" ]; then 				SECTION='' 			else 				IFS=':' read -r -a attribute <<< "$line" 				PRE="$HOSTNAME smartctl.smart[$DEVICE," 				ATTR=$( echo ${attribute[1]} | sed -e 's/^[ \t]*//' ) 				case ${attribute[0]} in 					'SMART overall-health self-assessment test result') 						echo "${PRE}test_result] \"$ATTR\"" 					;; 				esac				 			fi 		;; 		'ATTR') 			if [ -z "$line" ]; then 				SECTION='' 			else 				read -r -a attribute <<< "$line" 				PRE="$HOSTNAME smartctl.smart[$DEVICE," 				for i in "${!attribute[@]}";do 					if [[ $i == 0 ]]; then 						continue 					fi 					case ${attribute[$i]} in 						''|*[!0-9]*) ATTR="\"${attribute[$i]}\"" ;; 						*) ATTR="$(echo ${attribute[$i]} | sed 's/0*//')" ;; 					esac 					if [ -z "$ATTR" ]; then 						ATTR=0 					fi 					echo "${PRE}${attribute[0]},${HEADERS[$i]}] $ATTR" 				done				 			fi 		;; 	esac done < /dev/stdin 

Вывод будет примерно такой:

Вывод будет примерно такой:

test.local smartctl.info[sg1,model_family] "Western Digital RE4 (SATA 6Gb/s)" test.local smartctl.info[sg1,device_model] "WDC WD2000FYYZ-01UL1B1" test.local smartctl.info[sg1,serial_number] "WD-WCC1P1175320" test.local smartctl.info[sg1,firmware_version] "01.01K02" test.local smartctl.info[sg1,user_capacity] "2 000 398 934 016 bytes [2,00 TB]" test.local smartctl.info[sg1,sector_size] "512 bytes logical/physical" test.local smartctl.info[sg1,rotation_rate] "7200 rpm" test.local smartctl.smart[sg1,test_result] "PASSED" test.local smartctl.smart[sg1,1,attribute_name] "Raw_Read_Error_Rate" test.local smartctl.smart[sg1,1,flag] "0x002f" test.local smartctl.smart[sg1,1,value] 200 test.local smartctl.smart[sg1,1,worst] 200 test.local smartctl.smart[sg1,1,thresh] 51 test.local smartctl.smart[sg1,1,type] "Pre-fail" test.local smartctl.smart[sg1,1,updated] "Always" test.local smartctl.smart[sg1,1,when_failed] "-" test.local smartctl.smart[sg1,1,raw_value] 0 test.local smartctl.smart[sg1,3,attribute_name] "Spin_Up_Time" test.local smartctl.smart[sg1,3,flag] "0x0027" test.local smartctl.smart[sg1,3,value] 169 test.local smartctl.smart[sg1,3,worst] 169 test.local smartctl.smart[sg1,3,thresh] 21 test.local smartctl.smart[sg1,3,type] "Pre-fail" test.local smartctl.smart[sg1,3,updated] "Always" test.local smartctl.smart[sg1,3,when_failed] "-" test.local smartctl.smart[sg1,3,raw_value] 6508 test.local smartctl.smart[sg1,4,attribute_name] "Start_Stop_Count" test.local smartctl.smart[sg1,4,flag] "0x0032" test.local smartctl.smart[sg1,4,value] 100 test.local smartctl.smart[sg1,4,worst] 100 test.local smartctl.smart[sg1,4,thresh] 0 test.local smartctl.smart[sg1,4,type] "Old_age" test.local smartctl.smart[sg1,4,updated] "Always" test.local smartctl.smart[sg1,4,when_failed] "-" test.local smartctl.smart[sg1,4,raw_value] 36 test.local smartctl.smart[sg1,5,attribute_name] "Reallocated_Sector_Ct" test.local smartctl.smart[sg1,5,flag] "0x0033" test.local smartctl.smart[sg1,5,value] 200 test.local smartctl.smart[sg1,5,worst] 200 test.local smartctl.smart[sg1,5,thresh] 140 test.local smartctl.smart[sg1,5,type] "Pre-fail" test.local smartctl.smart[sg1,5,updated] "Always" test.local smartctl.smart[sg1,5,when_failed] "-" test.local smartctl.smart[sg1,5,raw_value] 0 test.local smartctl.smart[sg1,7,attribute_name] "Seek_Error_Rate" test.local smartctl.smart[sg1,7,flag] "0x002e" test.local smartctl.smart[sg1,7,value] 200 test.local smartctl.smart[sg1,7,worst] 200 test.local smartctl.smart[sg1,7,thresh] 0 test.local smartctl.smart[sg1,7,type] "Old_age" test.local smartctl.smart[sg1,7,updated] "Always" test.local smartctl.smart[sg1,7,when_failed] "-" test.local smartctl.smart[sg1,7,raw_value] 0 test.local smartctl.smart[sg1,9,attribute_name] "Power_On_Hours" test.local smartctl.smart[sg1,9,flag] "0x0032" test.local smartctl.smart[sg1,9,value] 79 test.local smartctl.smart[sg1,9,worst] 79 test.local smartctl.smart[sg1,9,thresh] 0 test.local smartctl.smart[sg1,9,type] "Old_age" test.local smartctl.smart[sg1,9,updated] "Always" test.local smartctl.smart[sg1,9,when_failed] "-" test.local smartctl.smart[sg1,9,raw_value] 15927 test.local smartctl.smart[sg1,10,attribute_name] "Spin_Retry_Count" test.local smartctl.smart[sg1,10,flag] "0x0032" test.local smartctl.smart[sg1,10,value] 100 test.local smartctl.smart[sg1,10,worst] 253 test.local smartctl.smart[sg1,10,thresh] 0 test.local smartctl.smart[sg1,10,type] "Old_age" test.local smartctl.smart[sg1,10,updated] "Always" test.local smartctl.smart[sg1,10,when_failed] "-" test.local smartctl.smart[sg1,10,raw_value] 0 test.local smartctl.smart[sg1,11,attribute_name] "Calibration_Retry_Count" test.local smartctl.smart[sg1,11,flag] "0x0032" test.local smartctl.smart[sg1,11,value] 100 test.local smartctl.smart[sg1,11,worst] 253 test.local smartctl.smart[sg1,11,thresh] 0 test.local smartctl.smart[sg1,11,type] "Old_age" test.local smartctl.smart[sg1,11,updated] "Always" test.local smartctl.smart[sg1,11,when_failed] "-" test.local smartctl.smart[sg1,11,raw_value] 0 test.local smartctl.smart[sg1,12,attribute_name] "Power_Cycle_Count" test.local smartctl.smart[sg1,12,flag] "0x0032" test.local smartctl.smart[sg1,12,value] 100 test.local smartctl.smart[sg1,12,worst] 100 test.local smartctl.smart[sg1,12,thresh] 0 test.local smartctl.smart[sg1,12,type] "Old_age" test.local smartctl.smart[sg1,12,updated] "Always" test.local smartctl.smart[sg1,12,when_failed] "-" test.local smartctl.smart[sg1,12,raw_value] 30 test.local smartctl.smart[sg1,183,attribute_name] "Runtime_Bad_Block" test.local smartctl.smart[sg1,183,flag] "0x0032" test.local smartctl.smart[sg1,183,value] 100 test.local smartctl.smart[sg1,183,worst] 100 test.local smartctl.smart[sg1,183,thresh] 0 test.local smartctl.smart[sg1,183,type] "Old_age" test.local smartctl.smart[sg1,183,updated] "Always" test.local smartctl.smart[sg1,183,when_failed] "-" test.local smartctl.smart[sg1,183,raw_value] 0 test.local smartctl.smart[sg1,192,attribute_name] "Power-Off_Retract_Count" test.local smartctl.smart[sg1,192,flag] "0x0032" test.local smartctl.smart[sg1,192,value] 200 test.local smartctl.smart[sg1,192,worst] 200 test.local smartctl.smart[sg1,192,thresh] 0 test.local smartctl.smart[sg1,192,type] "Old_age" test.local smartctl.smart[sg1,192,updated] "Always" test.local smartctl.smart[sg1,192,when_failed] "-" test.local smartctl.smart[sg1,192,raw_value] 29 test.local smartctl.smart[sg1,193,attribute_name] "Load_Cycle_Count" test.local smartctl.smart[sg1,193,flag] "0x0032" test.local smartctl.smart[sg1,193,value] 200 test.local smartctl.smart[sg1,193,worst] 200 test.local smartctl.smart[sg1,193,thresh] 0 test.local smartctl.smart[sg1,193,type] "Old_age" test.local smartctl.smart[sg1,193,updated] "Always" test.local smartctl.smart[sg1,193,when_failed] "-" test.local smartctl.smart[sg1,193,raw_value] 6 test.local smartctl.smart[sg1,194,attribute_name] "Temperature_Celsius" test.local smartctl.smart[sg1,194,flag] "0x0022" test.local smartctl.smart[sg1,194,value] 125 test.local smartctl.smart[sg1,194,worst] 96 test.local smartctl.smart[sg1,194,thresh] 0 test.local smartctl.smart[sg1,194,type] "Old_age" test.local smartctl.smart[sg1,194,updated] "Always" test.local smartctl.smart[sg1,194,when_failed] "-" test.local smartctl.smart[sg1,194,raw_value] 25 test.local smartctl.smart[sg1,196,attribute_name] "Reallocated_Event_Count" test.local smartctl.smart[sg1,196,flag] "0x0032" test.local smartctl.smart[sg1,196,value] 200 test.local smartctl.smart[sg1,196,worst] 200 test.local smartctl.smart[sg1,196,thresh] 0 test.local smartctl.smart[sg1,196,type] "Old_age" test.local smartctl.smart[sg1,196,updated] "Always" test.local smartctl.smart[sg1,196,when_failed] "-" test.local smartctl.smart[sg1,196,raw_value] 0 test.local smartctl.smart[sg1,197,attribute_name] "Current_Pending_Sector" test.local smartctl.smart[sg1,197,flag] "0x0032" test.local smartctl.smart[sg1,197,value] 200 test.local smartctl.smart[sg1,197,worst] 200 test.local smartctl.smart[sg1,197,thresh] 0 test.local smartctl.smart[sg1,197,type] "Old_age" test.local smartctl.smart[sg1,197,updated] "Always" test.local smartctl.smart[sg1,197,when_failed] "-" test.local smartctl.smart[sg1,197,raw_value] 0 test.local smartctl.smart[sg1,198,attribute_name] "Offline_Uncorrectable" test.local smartctl.smart[sg1,198,flag] "0x0030" test.local smartctl.smart[sg1,198,value] 200 test.local smartctl.smart[sg1,198,worst] 200 test.local smartctl.smart[sg1,198,thresh] 0 test.local smartctl.smart[sg1,198,type] "Old_age" test.local smartctl.smart[sg1,198,updated] "Offline" test.local smartctl.smart[sg1,198,when_failed] "-" test.local smartctl.smart[sg1,198,raw_value] 0 test.local smartctl.smart[sg1,199,attribute_name] "UDMA_CRC_Error_Count" test.local smartctl.smart[sg1,199,flag] "0x0032" test.local smartctl.smart[sg1,199,value] 200 test.local smartctl.smart[sg1,199,worst] 200 test.local smartctl.smart[sg1,199,thresh] 0 test.local smartctl.smart[sg1,199,type] "Old_age" test.local smartctl.smart[sg1,199,updated] "Always" test.local smartctl.smart[sg1,199,when_failed] "-" test.local smartctl.smart[sg1,199,raw_value] 0 test.local smartctl.smart[sg1,200,attribute_name] "Multi_Zone_Error_Rate" test.local smartctl.smart[sg1,200,flag] "0x0008" test.local smartctl.smart[sg1,200,value] 200 test.local smartctl.smart[sg1,200,worst] 200 test.local smartctl.smart[sg1,200,thresh] 0 test.local smartctl.smart[sg1,200,type] "Old_age" test.local smartctl.smart[sg1,200,updated] "Offline" test.local smartctl.smart[sg1,200,when_failed] "-" test.local smartctl.smart[sg1,200,raw_value] 0 

А дальше просто отправим все это Zabbix Trapper:

zabbix_smartctl.sh

#!/bin/bash # Sending collected data to the zabbix server # Get device list and type from STDIN, produced by smartdiscovery.sh  PREFIX='/usr/local/bin' while IFS=' ' read -r -a attr; do 	smartctl -A -H -i -d ${attr[1]} /dev/${attr[0]} | $PREFIX/smart2zabbix.sh /dev/${attr[0]} ${attr[1]} - | zabbix_sender -c /etc/zabbix/zabbix_agentd.conf -i - done < /dev/stdin  

Дальше нужно только разрешить sudo для некоторых скриптов, поместить задание в cron и импортировать шаблон на Zabbix Server.
Готовый комплект можно получить с официального портала Zabbix Share, где это все выложено для всех желающих: S.M.A.R.T. monitoring with smartmontools (LLD,Trapper)

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

ссылка на оригинал статьи http://habrahabr.ru/post/274391/