Сторінки

Показ дописів із міткою bash. Показати всі дописи
Показ дописів із міткою bash. Показати всі дописи

четвер, 6 жовтня 2011 р.

Cкрипт-пожирач файла

Дуже часто в мене виникає потреба обробляти якийсь файл по рядково. Звичайно тут нема ніякої проблеми, ось код:

#!/bin/bash

cat file.txt | while read line; do
 echo $line
done

Але що робити коли файл меговеликий і його обробка триває декілька хвилин, годин або днів (мається на увазі що кожен рядок з файлу піддається довготривалій обробці)???


Коли що небудь або хто небудь перериває цей процес то це мене дуже бісить. Тому виникла потреба в скрипті який би міг вирізати з файлу той рядок який йому потрібно, тобто з’їдати файл.
А якщо добавити прапорець блокування під час зчитування рядка з файла то цей скрипт набуває зовсім нових властивостей
Переваги такого скрипту:

  • можна легко відновити роботу після переривання
  • можна запустити n-кількість копій, які швидше "з’їдять" файл
  • не треба змінювати коди копій скрипту, всі коди однакові - а кожна копія робить своє

Ось детальний код з коментуванням:

#!/bin/bash


FILE="file-e" # файл-жертва

SIZE=`cat $FILE | wc -l`

# поки розмір файлу не дорівнює 0
while [ "$SIZE" != "0" ]; do 

  LOCK=`head -n 1 lock`; # зчитуєм блокування
  
  if [ "$LOCK" == "0" ]; then 
    echo 1 > lock # якщо розблоковано, блокувати і...
    
     
    SIZE=`cat $FILE | wc -l`
    { IFS= read -r line; cat 
    } < "$FILE" > "tmp_$SIZE" && mv "tmp_$SIZE" "$FILE"
    # ось ці три рядки вище роблять наступне:
    # - зчитують верхній рядок в змінну line
    # - решту файла заноситься в тимчасовий файл
    # - потім тимчасовий файл переміщується в оригінал

   echo 0 > lock # розблоковуєм
   DATE=`date  +%T`
   echo "${DATE} ${line}"
   sleep 1
  else 
    echo "Waiting for UNLOCKING..." 
    # якщо заблоковано, очікування 3 сек
    sleep 3
  fi
  
done;
# і знову на початок циклу, поки файл не закінчиться

А тепер треба перевірити роботу цього пожирача. В якості піддослідного файла-жертви буде файл ось такої структури:

1 string
2 string
3 stirng
...
Який створюється скриптом rst.sh. Менше слів, більше коду:
flashbag@laptop:~/vnc-new$ cat rst.sh
#!/bin/bash
rm -rf file-e
touch file-e
chmod 666 file-e

for i in `seq 1 50`; do 
  echo "${i} string" >> file-e
 done
flashbag@laptop:~/vnc-new$ ./rst.sh
flashbag@laptop:~/vnc-new$ cat file-e
1 string
2 string
3 string
4 string
5 string
6 string

Тепер запускаю 4 копії цього мегопожирача файлів...

Як видно з скріншота кожна копія цього мегопожирача з’їдає один унікальний рядок. Дві копії не можу з’їсти один і той самий рядок. Ще один експеримент на рахунок продовження трапези скрипта після переривання.

Ця стаття не принесе нічого нового в досвід затятих лінуксоїдів, тому що вона розрахована на аматорів-початківців у bash програмуванні. Буду радий почути відгуки :)

Прочитати пост

субота, 2 липня 2011 р.

Граббинг сайту nashe.com.ua

В пошуках нової музики для себе мені в голову прийшла ідейка проаналізувати сайт http://nashe.com.ua ( тексти пісень українськиї виконавців ). Аналізовувати та граббити різні сайти - це моє так сказати хоббі. Це дуже цікаво для мене - оскільки в цьому процесі я вивчаю програмування на bash більш детально і в потребі якогось унікального програмного рішення завжди дізнаюсь щось нове для себе.

Так ось: першим ділом треба було зясувати загальну кількість виконавців викладених на сайті. Відкривши вкладку виконавці на сайті я побачив 4 сторінки виконавців. Передивившись декілька посилань на сторінки виконавців було ясно що виконавців десь в районі 1000. На сайті вони розміщені по алфавітному зростанню. В випадку парсингу ID виконавців були б розміщені хаотично. Тому я вирішив грабити по зростанні ID кожного виконавця.Написав простий скрипт:

#!/bin/bash

for id in `seq 1 1200`; do 
  GET "http://nashe.com.ua/artist.htm?id=$id" > ./artists/$id.html
  check=`cat "./artists/$id.html" | grep arttitle | cut -d"'" -f4`
  if [ "$check" = "arttitle" ]; then
    echo $id >> artist-list
    echo "id=$id is ok!"
  else
    rm -rf ./artists/$id.html
    echo "id=$id error..."
  fi
done

який перевіряє ID від 1 до 1200 на наявність виконавця. Коли перевірка дійшла до 1000 скрипт почав видавати постійні "error..". Стало ясно що виконавців 953, а не 722 як пише на сайті.

Коли в параметрі id викликано неправильне значення то скрипт повертає усіх виконавців. За допомогою простого парсингу можна відрізнити яка сторінка видана: з існуючим ID чи неправильним.

flashbag@laptop:~/nashe.com.ua$ GET http://nashe.com.ua/artist2.htm?page=2 | grep arttitle
flashbag@laptop:~/nashe.com.ua$ GET http://nashe.com.ua/artist.htm?id=492 | grep arttitle
<div align='left' class='arttitle' style='width: 45%;'>K402</div>
flashbag@laptop:~/nashe.com.ua$ GET http://nashe.com.ua/artist.htm?id=492 | grep arttitle | cut -d"'" -f4
arttitle

Для виконавця в коді присутній блок div з класом arttitle. Якщо в команду ще добавити контейнер cut можна чітко виділити ключове слово

Звідси можна записати назву кожного виконавця в файл з такою послідовністю як вони розміщені у базі даних: від id=1 до 953. Ось скрипт get-artists.sh:


#!/bin/bash
rm -rf artist-names
touch artist-names

for id in `seq 1 953`; do
 cat ./artists/${id}.html | grep arttitle | cut -d'>' -f2 | cut -d'<' -f1 >> artist-names
done

Це навіть смішно скриптом назвати, можна цей код в одну команду втиснути )))

Тепер я вирішив дістати усі стилі виконавців, код:

#!/bin/bash
rm -rf styles.list
touch styles.list

for id in `seq 1 953`; do 
  a=`cat "./artists/$id.html" | grep "<br><span class='title'>"`
  b=`echo ${a//*<span} | cut -d'>' -f3 | cut -d'<' -f1` 
  echo $id $b
  echo $b >> styles.list 
done
після цього рядка
a=`cat "./artists/$id.html" | grep "<br><span class='title'>"`
змінна а набувала різного значення, у виконавців про яких була повна інформація змінна а приймала цю інформацію, а у тих в кого був лише стиль - приймала значення лише стилю.

Іншими словами змінна а приймала значеня html-кода блока про виконавця. Треба було якось виділити її і я побачив що усі пункти ( Дискографія, Оф.сайт, Email ) крім стилю були розміщені в тегах div. За допомогою цієї команди
echo ${a//*<span}
можна виділити всю інфу після тега span. А далі cut-ом виділяється тільки потрібне значення стилю/стилів

Файл styles.list має відповідно 953 рядка і у кожному є стиль/стилі виконавця або нема стилю. Є і такі виконавці як 98,100,109,153,154,155 та інші.



Тепер в мене виникла ідейка виділити кожний унікальний стиль. Алгоритм скрипту зразу проскочив в голові: порожні рядки видаляються, в того виконавця в кого два або більше стилів, стилі розділяються, кожному стилю - окремий рядок, далі записуються в окремий файл один унікальний стиль тільки один раз. Для перевірки схожості стилів треба видаляти лишні пробіли в рядках.

Розписувати створення скрипту не буду, бо це буде довго і нудно, ось готовий скрипт:

#!/bin/bash -x

file="style-by-line";

rm -rf $file
touch $file

cat styles.list | while read line; do
 if [ "$line" != "" ]; then 
   x=${line//[!,]/}
   if [ "${#x}" = "0" ]; then
    echo $line | tr -s " " | sed 's/^[ ]//g' >> $file
   fi 
   if [ "${#x}" = "1" ]; then
    echo $line | cut -d',' -f1 | tr -s " " | sed 's/^[ ]//g' >> $file
    echo $line | cut -d',' -f2 | tr -s " " | sed 's/^[ ]//g' >> $file
   fi
   if [ "${#x}" = "2" ]; then
    echo $line | cut -d',' -f1 | tr -s " " | sed 's/^[ ]//g'>> $file
    echo $line | cut -d',' -f2 | tr -s " " | sed 's/^[ ]//g'>> $file
    echo $line | cut -d',' -f3 | tr -s " " | sed 's/^[ ]//g'>> $file
   fi
   if [ "${#x}" = "3" ]; then
    echo $line | cut -d',' -f1 | tr -s " " | sed 's/^[ ]//g'>> $file
    echo $line | cut -d',' -f2 | tr -s " " | sed 's/^[ ]//g'>> $file
    echo $line | cut -d',' -f3 | tr -s " " | sed 's/^[ ]//g'>> $file
    echo $line | cut -d',' -f4 | tr -s " " | sed 's/^[ ]//g'>> $file
   fi   
 fi
done
sort -k 1,1 -u style-by-line > uniq-styles

Зразу в очі кидається ондоманітні умови. Так це лишнє, але з циклами в цьому напрямку в мене поки що не дуже. Змінна x - це кількість ком в одному рядку. Експерементальний шляхом визначено - максимальне значення змінної x=3, три коми-розділювачі, отже максимально можливо 4 стилі для виконавця.

Остання команда мене просто вражає. Я стільки гуглив щоб зробити унікальне сортування кожного рядка в файлі. Вже надумав написате це сортування на PHP, але в останній момент найшов найоптимальніше рішення - унікальне сортування однією командою.

Так ось вийшло 37 унікальних стилів. Відсортовані по алфавіту в OpenOffice. Ось вони:

А капелла
Альтернатива
Альтернативний рок
Брутал-поп
Вільний український метал
Готик-фольк метал
Гранж
Джерело:
Ейсід-хоп
Електро
Електро-рок
Електро-фолк
Етно-арт-рок
Етно-ска-панк
Етно-хаус
Інді-ретро-поп
Латино
Ліричний рок
Нео-психоделія
Нью-ейдж
Нью-метал
Подільський фундаментал
Поп-рок
Ритм-н-блюз
Ритуальний фолк
Рок
Рок-н-рол
Рокабіллі
Симфо-арт-метал
Техно-денс
Фолк
Фолк-дарк-метал
Фолк-метал
Фолк-рок
Фолк-фанк-рок
Хіп-хоп
Християнський рок

Тут і найшовся перший баг: стиль "Джерело:" ( виділений жирним ). Коли я подивився в усі стилі то цей стиль належав виконавцю з id=452. Сторінка цього виконавця справді не така як усі, в середині тегу span там знаходиться посилання на джерело інформації. Значить скрипт без багів, це мене порадувало =)

Щоб це все не здавалсь маячнею, я написав прості скрипти index.php та show.php.

Лістинг index.php:

<form action=show.php method=get>
<select name=style>
<?php

$uniq_styles=file('uniq-styles');

for ($i=0; $i<count($uniq_styles); $i++ )   
{ echo '<option name='.$i.'>'.$uniq_styles[$i].'</option><br>'; }

?>
</select>
<input type='submit' value="OK">
</form>


Лістинг show.php:

<?php

$style=$_GET['style']; 
echo '<b>'.$style.'</b><br>';

$styles=file("styles.list");
$artists=file("artist-names");

for ($i=1; $i<count($styles); $i++ )
 { 
   if (substr_count($styles[$i-1], $style)>0) 
  echo '<a target=_blank href="http://nashe.com.ua/artist.htm?id='.$i.'">'.$artists[$i-1].'<br>';
 } 

?>

Тут можна їх потестувати http://kepnung.host-ua.org.ua/nashe.com.ua

Цим скриптам потрібні файли uniq-styles, styles.list, artists-names.


Тепер все спочатку, коротко і ясно:

  • Щоб не закачувати кожен раз для парсингу всі сторінки виконавців є скрипт download-artists.sh. Він закачує усі 953 сторінки у папку artists, у вигляді ${id}.html.
  • Щоб записати в файл artist-names назву кожного виконавця є скрипт get-artists.sh
  • Щоб дістати стиль/стилі кожного виконавця в файл styles.list є скрипт get-styles.sh
  • І нарешті щоб виділи тільки унікальні стилі в файл uniq-styles є скрипт get-uniq-styles.sh

Наочний приклад цього грабингу це посилання вище. Іншими словами - це фільтр виконавців по стилю, на сайті такої штучки нема. Треба підказати їм таку ідейку.

Прочитати пост