Big Data на обычном linux, или Big Data по быстрому

Мотивация

Часто нужно подготовить данные для машинного обучения “ИИ” и

  • объём этих данных не всегда так велик, что превосходит объём дисков одной рабочей машины
  • важен не объём данных (размер выборки) а качество этих данных и наличие полезных признаков.

и удобно просто в консоли что то сделать с данными или скриптом как-то их отфильтровать итд. Однако существующие методы работы с dataset заставляют открывать среду разработки и начинать “программирование”. Это замедляет и без того не быстрый процесс и , например у меня, пропадает запал.

Я хочу проходить путь от идеи и датасета в 1-2 ТБ до обучающего набора ИИ за день и не более.

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

CSV формат в linux “CLI”

Данные в формате csv это просто текстовый файл, с которым в консоли удобно работать. Часто в этом формате данные откуда - то выгружают, чтобы передать для биг даты или анализа и обучения ИИ.

Есть “аналитические БД” которые могут статистику по данным посчитать уже при импорте, или сделать какую-то простую выборку. Их рассматривать тут не буду - не интересно и есть другие нудобства (кому надо тот знает и так почему я их не рассматриваю ;). Есть способ увеличить скорость обработки в десятки раз и я хочу тут писать о быстрой прямой потоковой обработке. Я буду писать о джадайских приёмах.

Биг дата не помещается в памяти машины (чаще всего) и с ней лучше всего работать в потоковом режиме задействуя все процессоры и скорость IO системы - параллельно.

Мапинг данных в память, чтобы “как бы можно работать в памяти” - это галиматья. Если вы мапите память, то есть соблазн отойти от потоковой обработки и не быть героем - теряете в скорости , очень теряете.

Утилиты для обработки csv “файлов”

Сравнение производителности и возможностей утилит обработки csv файлов

Удобные шаблонные скрипты для потоковой обработки файлов

Конфигурация для map - reduce

1
2
3
4
5
6
7
8

# импортируем настройки обработки, настраиваем кластер
source ./config.sh
# СИНХРОННО разбиваем входной файл на map части
BASH_ENV=.env_autobuild bash slave_map_scripts/splitInDataForMap.sh
# на slave машинах пересортируем колонки и добавляем удобные разделители, распаковываем файлы
# запустить на всех slave машинах map фазу "ПЕРЕРАЗОБРАТЬ ДАННЫЕ"
BASH_ENV=.env_autobuild bash slave_map_scripts/map_1.sh

config.sh содержит конфиграцию кластера (slave машины например). он может сохранить ее в удобный файл окружения для скриптов, например так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash -x

# входные данные
# если cpu много - лучше zcat, если диск быстрый а cpu нужен - лучше cat
export IN=data/nppro.csv.gz
export SRC=zcat\ $IN
#SRC=cat\ nppro.csv
export DEBUG=head
#DEBUG=cat
export DEBUG="pv -s $(gzip -l $IN |tail -n1|awk '{print $2}') -"

export MAP_SLAVE_N=1
# slave\slavepassword
export SLAVES=( 'sshpass -p slavepassword ssh slave@localhost' ) # ( slave@localhost slave2@localhost slave3@localhost slave@localhost )
export MASTER='sshpass -p 1 ssh mp@localhost'
export MASTER_USER=mp
export MASTER_DATA_PATH=/home/mp/WORK/JOB/v6/data

# bash bug https://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script
declare -p SLAVES MAP_SLAVE_N MASTER MASTER_USER MASTER_DATA_PATH > .env_autobuild

Тут

  1. определение полезных в работе перменных
  2. MAP_SLAVE_N число рабочих машин
  3. SLAVES список самих этих машин (ssh доступ)
  4. сохранение в файл .env_autobuild переменных.

в файле .env_autobuild получим :

1
2
3
4
5
declare -ax SLAVES=([0]="sshpass -p slavepassword ssh slave@localhost")
declare -x MAP_SLAVE_N="1"
declare -x MASTER="sshpass -p 1 ssh mp@localhost"
declare -x MASTER_USER="mp"
declare -x MASTER_DATA_PATH="/home/mp/WORK/raskraska/v6/data"

Красота! Запустил скрипт и все настроено - теперь удобно переменные конфигурации использовать так:

1
BASH_ENV=.env_autobuild   	bash СКРИПТ КОМАНДА 

Разбиеное одного большого csv файла на slave части и загрузка его на slave машины

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
#https://www.baeldung.com/linux/split-file-at-line-numbers
# using head & tail method
TOTAL_COUNT=$(( $($SRC|$DEBUG -N "count lines"|wc -l) - 1 )) # first line is header
let "LINESCOUNT = $TOTAL_COUNT / $MAP_SLAVE_N"
let "LINESCOUNT_modulus = $TOTAL_COUNT % $MAP_SLAVE_N"
echo "total lines = $TOTAL_COUNT split by $LINESCOUNT + $LINESCOUNT_modulus in tail"
#COMMAND="$SRC|$DEBUG|tail -n +1"
SRCFILE='data/SOME.csv'
# надо первую строку пропустить - там заголовок
#$SRC|$DEBUG -N "unpack to data/SOME.csv" |tail -n +1 > $SRCFILE
for i in $(seq 1 $MAP_SLAVE_N); do
if [ $i -eq $MAP_SLAVE_N ]
startline=$((($i-1)*$LINESCOUNT))
endline=$(($i*$LINESCOUNT))
then
export endline=$(($endline + $LINESCOUNT_modulus + 1));
fi
linecount=$(( $endline-$startline+1 ))
#echo "tail -n +$startline $SRCFILE | head -n $linecount | pv -s $linecount | gzip -c > ./mount_map/$i/IN.csv.gz"
#tail -n +$startline $SRCFILE | head -n $linecount | pv -s $linecount -l -N "split for map number $i"| gzip -c > ./mount_map/$i/IN.csv.gz &
slave=${SLAVES[$(($i-1))]} ; #echo "slave machine= $slave"
tail -n +$startline $SRCFILE | head -n $linecount | pv -s $linecount -l -N "split for map number $i"| gzip -c | ($slave 'cat > map/IN.csv.gz') &

#COMMAND="$COMMAND | tee >( head -n $LINESCOUNT | gzip -c > ./mount_map/$i/IN.csv.gz ) | tail -n $(($LINESCOUNT*($MAP_SLAVE_N-$i) + 1)) "
# create the top of file:
#echo "$COMMAND | head -n $LINESCOUNT > ./mount_map/$i/IN.csv"
# create bottom of file:
#COMMAND="$SRC| tail -n $(($i * $LINESCOUNT))"
done
wait # jobs

Тут ключевая строчка ради которой все считается вот :

1
tail -n +$startline $SRCFILE | head -n $linecount | pv -s $linecount -l -N "split for map number $i"| gzip -c | ($slave 'cat > map/IN.csv.gz')  &

берем хвост начиная со нужной строки , берем от него начало в нужное число строк , зная общее число строк показываем прогресс (pv) , сжимаем это все для передачи по сети (меньше трафик), запскаем на рабочей машине “сохранялку в указанный файл” и передаем ей сетевой поток сжатыйх данных. и так с каждым диапазоном строк - входной файл быстро разбивается на примерно равное число кусков по строкам, по числу раобчих машин.

Запуск map операций на всех рабочих машинах

скрипт

1
2
3
4
5
6
7
8
9
#!/bin/bash 
# запустить на удаленных машинах map обработки
COMMAND='./map1.sh &;'
for i in "${!SLAVES[@]}"; do
slave=${SLAVES[$i]}
echo 'stage: Run map "reorder columns" $i/$MAP_SLAVE_N'
($slave $COMMAND )
done
wait

по списку ssh рабочих машин подключается и там запускает скрипт map обработки в фоне, мы ждем завершения их всех.

Так можно по списку слейвов запустить там любые команды - я вам тут даю рецепт администрирования кластера linux машин, по сути.

аналогично можно получать от рабоичих машин результаты , или так или scp - по всякому.

Таким образом весь map - reduce сводится к операциям обработки csv файлов на одной машине. само распределение и сбор в одном месте - дело довольно простой уже техники.

Полезные приёмы обработки csv

Используйте mawk если это возможно (это awk реализация)

есть по идее bin/csvawk н вроде бы на С , но mawk хоть и не для csv но он очень быстрый, выберите ему один столбец, оно того стоит.

Mawk быстрее gawk с++ решения в 6 раз. доказательство , быстрее perl скрипта в 3 раза, это один из моих серетных инструментов.

использую примерно так, например нужно из url вытащить только домен

1
2
3
4
5
6
# делаем из него домен а не url с мусором
#1m14s
#bin/csvawk '{match($1, /:\/\/([^\/]+)\//); print( "\"" substr($1, RSTART+3, RLENGTH-4) "\"" ); }' | \
#tail -n+2 |bin/csvunpipe -p "domain" >map/URL.csv
# 22sec !
mawk -F, '{match($1, /:\/\/([^\/]+)/); print( "\"" substr($1, RSTART+3, RLENGTH-3) "\"" ); }' | \

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

Есть разные способы работы с регулярками , mawk - быстрее всего, что я пробовал.

берем один столбец или несколько столбцов csvtool (C) csvcut (python)

1
2
# берём только столбец "url"
csvtool -t ';' namedcol url - | \

утилита csvtool (C) - одна из самых быстрых чтобы выбрать нужные столбцы из входного потока (она их читает по имени столбца по номеру).

Однако если нужно поменять столбцы менятами местами , то есть вариант на много быстрее , для этого она не подходит.

Полезные возможности:

  • выбрать указанные столбцы. (менять порядок столцов не советую - тормозит)

  • умеет менять разделители

    1
    csvtool -t TAB -u COMMA cat input.tsv > output.csv
  • объединение нескольких csv файлов в один , НО есть paste - который быстрее

    1
    csvtool paste input1.csv input2.csv > output.csv
  • умеет примитивный join (по точному значению), но для этого есть тоже получше средства

удачные примеры использования

1
2
3
4
5
6
7
8
9
# два нужны столбца по номеру
csvtool col 1,11 - >map/user_url.csv

# обработать поток - тут идет пересортировка столбцов в строках
# оптимизация csvcut -c user_id,cat1,cat2,cat3,cat4,cat5,cat6,cat7,cat8,cat9,url | \
# убрал url, чтобы сортировка шла быстрее. для ЭТОЙ задачи url не нужен
csvtool -t ';' -u ',' col 1,3-11 - | \
# тут была смена разделителей

csvtool (C) быстрее чем csvcut (python). замеры скорости.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#=========тесты скорости разбора csv=========
#slave@mp-company:~$ time pv map/IN.csv | csvcut > /dev/null
#1,43GiB 0:02:54 [8,44MiB/s] [========================================================================================================================================================>] 100%
#real 2m54,070s
#user 2m53,384s
#sys 0m5,936s
#slave@mp-company:~$
# !!сишный инструмент - гораздо быстрее (gnu csvtools)
#slave@mp-company:~$ time pv map/IN.csv | csvtool col 1,3-11,2 - > /dev/null
#1,43GiB 0:00:41 [35,5MiB/s] [========================================================================================================================================================>] 100%
#real 0m41,390s
#user 0m39,750s
#sys 0m1,296s
# среднее значение 20МБ/с но тоже быстро (csvawk из https://github.com/DavyLandman/csvtools)
# но тут awk конкатенация съедает много, csvcut из этого пакета ооочень быстр. см ниже
#slave@mp-company:~$ time pv map/IN.csv | /home/mp/WORK/JOB/v6/csvtools/bin/csvawk '{print $1","$3","$4","$5","$6","$7","$8","$9","$10","$11","$2}' > /dev/null
#D: Done parsing config params
#1,43GiB 0:01:13 [20,0MiB/s] [========================================================================================================================================================>] 100%
#real 1m13,542s
#user 1m15,441s
#sys 0m2,310s
# скорость бешеная вообще
#slave@mp-company:~$ time pv map/IN.csv | /home/mp/WORK/JOB/v6/csvtools/bin/csvcut -D 2 -- 2>/dev/null >/dev/null
#1,43GiB 0:00:07 [ 185MiB/s] [========================================================================================================================================================>] 100%
#real 0m7,902s
#user 0m7,203s
#sys 0m0,845s

Питоновские утилиты не для BigData берите лучшее для своей задачи.

Важное замечание, совет

используйте нормальные утилиты, меньше городите самодеятельность - csv формат коварный, там экранирование строк и разделители … можете все сгубить. надо вам поменять столбцы местами ? - делаёте так:

1
2
# меняем порядок с domain,,,,   на ,,,domain
csvtool -t ';' -u ';' col 2-12,1 - > map/IN_with_domain.csv

Это хотя бы контролируемо, не пишите отсебятину, оптимизировать потом будуте, если воообще будет надо.

Импортируем данные в SQLITE

Да, sqlite - вещь, для большинства этапов обработки csv с ним можно сделать очень много полезного полностью утилизировав процессоры.

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

Вот кусок скрипта в котором выполняется импорт и операции

1
2
3
4
5
6
7
8
9
10
11
12
13
DB='sqlite3 map/index1.db'
....
time=$(time (cat <<EEE
.mode csv
.separator ";"
.import map/IN_with_domain.csv raw
EEE
) | $DB)
...
# какая то операция
echo "3/5 create domain index... (~0,5m/1,5GB)" > $STATUS_INDEX
time echo "create index if not exists i_url_raw on raw(domain);" |\
$DB;

Фокус в том, что вы не покидаете bash окружения и делаете реально полезные вещи, можно параллельно в разных базах… удобно

и он быстрый и очень гибкий. там есть свой prce reg exp загружаемых модулей, но mawk быстрее и удобнее.

пример sqite reg exp неудобного, но он есть

1
2
3
4
5
6
7
8
9
10
sudo apt-get install sqlite3-pcre

.load /usr/lib/sqlite3/pcre.so
# 1,6
select substr(url,si+2,length(url)) as p1, * from (select INSTR(url,'http://') as hi, INSTR(url,'/') as si , * from raw1) limit 3 ;

case
hi!=0 and INSTR(p1,'/')!=0
отсекает сначала http:// а потом далее ищет / и берет ДО него
select substr(p1,1,INSTR(p1,'/')-1) from (select substr(url,si+2,length(url)) as p1, * from (select INSTR(url,'http://') as hi, INSTR(url,'/') as si , * from raw1)) limit 3;

Исключтить какие то строки grep -v

1
2
# исключить нулевые идентификаторы
grep -v '^"0" ' | \

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

Сортировка csv по указанным столбцам

Встраиваем в csv конвеййер сортировку:

1
2
3
4
# сортируем по первому столбцу
#sort -S512M -t',' -k 1n | \
# параллельная сортировка
parallel --pipe --files sort -S512M -t, -k1.1 | parallel -Xj1 sort -S1024M -t, -k 1.1 -m {} ';' rm {} | \

linux команду sort и его параметры знают многие, а вот параллельная сортировка со слиянием частей - дело посложнее. тут пример параллельной сортировки со слиянием.

Передача потока на питон скрипт

если надо что то сделать с накоплением (reduce?) то часто надо и ручками что-то написать, скрипт на питон - хороший вариант для прототипа. в контексте bash его можно быстро и накидать:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# отсортировали или просто передали питон скрипту поток csv 
parallel --pipe --files sort -S512M -t, -k1.1 | parallel -Xj1 sort -S1024M -t, -k 1.1 -m {} ';' rm {} | \

# передаём на питон, который "собирает переходы юзера"
python3 -c 'import sys, ujson; first=True
.... всякие ваши переменные словари , функции итд
try:
for line in sys.stdin:
a = line.split(",");
.... обрабатывайте что то
except BrokenPipeError:
pass
.... завершение обработки
' 2>err.log >map/python_out.csv ;

ну или дальше можно канал передать по конвейеру…

Питон удобно использовать еще и для того, чтобы заполнить и сохранить данные в pickle формате или подготовить для ИИ в pandas

paste - соединить два солбца через разделитель

реально быстро работает обычная linux команда paste. Пример использования

1
2
3
4
5
6
7
8
9
10
time paste -d ';' 	<(
size=$(ls -l map/IN.csv|awk '{print $5}');
cat map/IN.csv | \
($PROGRESSDD ) 2> >(stdbuf -i 0 -o 0 tr '\r' '\n' |./mypv '1/5 compute domain column' 3 $size $STATUS_INDEX) | \
# берём только столбец "url"
csvtool -t ';' namedcol url - | \
mawk -F, '{match($1, /:\/\/([^\/]+)/); print( "\"" substr($1, RSTART+3, RLENGTH-3) "\"" ); }' | \
tail -n+2 |bin/csvunpipe -p "domain"
) map/IN.csv | \
# погнали дальше обрабтатывать ... столбцов стало больше, появился рассчитанный скпиптом столбец

Тут используется process substitution для paste - это очень удобный bash приём!

т.е. первый столбец мы формируем скриптом из входного файла (расчётное значение), потом разделитель “;” потом столбцы из входного файла

paste прост как валенок и если надо делать столбцы с экранированием символов - это уже не подходит… используйте csvtool например

Как показать прогресс операции на консоль

PV

есть для консоли утилита pv вместо cat - она выводит на терминал прогресс,

Минусы :

  • не в файл сохраняет прогресс - тадо иметь конект и она долна знать pty чтобы выводить, если вы отключались от сервера и снова подключились - вы ничего не увидите + процесс от этого может прерваться.
  • И она работает с файлами известного размера, ей можно подсунуть “общее число строк”, для подсчета прогресса, но токуда она выводит прогресс - все равноне удобно.

примеры использования утилиты pv

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

1
2
3
export DEBUG=head
#DEBUG=cat
export DEBUG="pv -s $(gzip -l $IN |tail -n1|awk '{print $2}') -"

если $IN это csv.gz файл то надо узнать для pv число строк в файле, тут для этого отдельный подсчёт… он кстати довольно быстрый, и выигруш от такого способа есть и смысл от gzip тоже т.к. процессоров много а IO не быстрый.

DD –progress & mypv

другой способ получить прогресс такой

1
2
3
4
5
PROGRESSDD="dd if=/dev/stdin of=/dev/stdout bs=4096 status=progress" 
....
cat map/IN.csv | \
($PROGRESSDD ) 2> >(stdbuf -i 0 -o 0 tr '\r' '\n' |./mypv '1/5 compute domain column' 3 $size $STATUS_INDEX) | \
какие то другие команды в этом конвеййере

вот что тут происходит :

  1. входной файл кидается на канал некоторой команды,
  2. команда в первой сдвинутой строке тупо передаёт данные со входа на выход блоками 4096 байт и при этом считает число блоков
  3. информация о прогрессе передаётся моему скрипту mypv который расчитывает (зная общий размер данных) прогресс и нормально выводит данные о прогрессе в файл который задан в переменной $STATUS_INDEX
  4. далее можно cat нуть этот файл в нужное место на терминале (хоть по расписанию) и увидеть и текстовое сообщение и сам прогресс и сколько времени осталось до конца.

Чем это хорошо? - тем, что каждый прогресс каждого процесса в котором перекидываются данные известного размера через pipe можно таким способом вывести в отдельные файлы и потом собрать их где надо и выести на терминал красиво в нужном порядке с нужной периордичностью. Это полностью контролируемая информация о прогрессе всех процессов

Вначале инициализируйте эти файлы например так

1
echo 		"1/5 compute domain column  (~3m/1.5GB)" > $STATUS_INDEX

чтобы там хоть что то было до первого их обновления

mypv - мой скрипт подсчитывает прогресс, предсказывает время до конца, сохраняет в строку

dd --status выдает что то типа

1
2
3
4
1539135987 bytes (1,5 GB, 1,4 GiB) copied, 8,61819 s, 179 MB/s\n
1371473920 bytes (1,4 GB, 1,3 GiB) copied, 10 s, 137 MB/s\n
594622464 bytes (595 MB, 567 MiB) copied, 4 s, 149 MB/s
154531840 bytes (155 MB, 147 MiB) copied, 1 s, 155 MB/s

это разбирает скрипт на языке lua (LuaJit) , имя файла mypv , chmod +x mypv :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/luajit
--[[ читает записи от `dd status=progress` и пишет в указанный файл прогресс бар
запуск (bash):

dd if=<SRC> of=/dev/stdout bs=2048 status=progress 2> >(tr '\r' '\n' |./mypv <name> <period> <size> <outfile>)
# or
time $SRC|($PROGRESSDD ) 2> >(stdbuf -i 0 -o 0 tr '\r' '\n' |./mypv columnReorder 3 $size map/columnReorder.status)| \

в итоге строка с показателями прогресса в указанном файле
]]
local Name = 'taskName'
local period=3
local Size = 1539135987*2
local OutFile='_pv.status'
--
if true then
Name=arg[1]
period=tonumber(arg[2])
Size=tonumber(arg[3])
OutFile=arg[4]
end
local lastmoment=0
local fh, err = io.open( "/dev/stdin" )
--if err then print("OOps"); return; end
-- Open a file for write
--fho,err = io.open("requests.new","w")
--if err then print("OOps"..err); return; end

-- line by line
while true do
--local line = '1539135987 bytes (1,5 GB, 1,4 GiB) copied, 8,61819 s, 179 MB/s\n '-- test
--local line = '1371473920 bytes (1,4 GB, 1,3 GiB) copied, 10 s, 137 MB/s\n '-- test
--local line = '594622464 bytes (595 MB, 567 MiB) copied, 4 s, 149 MB/s '-- test
--local line = '154531840 bytes (155 MB, 147 MiB) copied, 1 s, 155 MB/s'-- test
local line = fh:read(80)
if line == nil then break end
--print (line)
--local fh_out, err = io.open( OutFile, 'a' ); fh_out:write(line..'\n'); fh_out:close() -- debug
for bytes, count, seconds,speed in line:gmatch "[\n\r](%d+)%s.+%(([%d,%w%s]+),%s.+copied, ([%d,]+) s, (.*)[\r\n]" do
--print('bytes '..bytes); print(count);print(seconds);print(speed);
local _seconds_n = tonumber(seconds:gsub(',','.'), 10)
if _seconds_n-lastmoment > period then
--if true then
lastmoment=_seconds_n
--
local percent=math.floor(100*tonumber(bytes)/Size + .5)
-- max progress len = 25 + braces[] = 27
local _length=math.floor((percent/4)+.5)-1
local progress='['..string.rep('=',_length)..'>'..string.rep(' ',25-_length)..']'
local eta='?';
if progress~=0 then eta=math.floor(tonumber(_seconds_n)*(100-percent)/percent+.5) end
local result=Name..': '..math.floor(tonumber(seconds:gsub(',','.'),10))..'s '..percent..'%'..progress..' ETA '..eta..'s'..' cur:'..count..' speed: '..speed
--
local fh_out, err = io.open( OutFile, 'w' )
fh_out:write(result..'\n')
fh_out:close()
end
break
end
end
-- Following are good form
fh:close()
--fho:close()
Шаблон скрипта как показать прогресс процессов
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
start=`date +%s`
clear;
tput sc ;
#tput civis;
while true
do
i=0
_WINDOW_X=`tput cols`
f="%-${_WINDOW_X}s\n"
for var in $(ls map/*.status)
do
#echo $var
data=$(cat $var|head -n1);
#tput cup $(($i )) 0 ;
printf $f "$data"
i=$(( $i + 1 ))
done
end=`date +%s`
runtime=$((end-start))
printf $f "${runtime} s"
sleep 1
tput rc;
done

итак, есть файлы в папке map с расширением .status х и будем показывать , там читаем про одной строке

ея была: выводить в разных местах экрана, но сделано проще:
экран чистим и встаём на начало , каждую строку печатаем шириной с кол-вом символов на экране с переносом строки и печатаем их одну за другой, всё. Этого достаточно, чтобы каждую секунду видеть актуальный прогресс.

Заходим на машину запускаем скрипт мониторинга и радуемся тому что данные в консоли меняются и при этом процессы идут фоном.

Полезные bash приемы (типа бонус)

Запуск в параллели любых кусков скрипта

1
2
3
4
5
6
7
8
(
тут пишите любой скрипт sh - будет выполнен в фоне
) &

(
второй запущенный параллельно скрипт...
) &
wait # ждем их завершения