Как стать автором
Обновить

Комментарии 48

главное все молчат:D
Хочется верить что это потому, что нет изьянов! :)
Ну-ну, изъянов нет :) Если уж постите код — то постите в хабровых тэгах <source lang="язык"></source>. Я верю, что у вас не все так плохо с форматированием :)

По ходу текста:
#!/bin/bash

Вам абсолютно не нужен bash в этом скрипте — пишите лучше #!/bin/sh -ef — это будет работать быстрее и предсказуемее, чем bash. Опция "-e" нужна для того, чтобы падать при ошибке, а не продолжать дальнейшее выполнение при явно порушенных состояниях и структурах данных. Опция "-f" нужна для того, чтобы не тратить время на глоббинг и не нарваться на то, что название файла со "*", "?" или "[]" внезапно поведет себя совсем не так, как вы думали.

IFS="\n"
lastmod=( $( grep Last-Modified response.out ) )
unset IFS

Вот зачем здесь IFS и эти внешние скобочки? Вы точно понимаете, что они делают? Они делают вызов того, что внутри в subshell. При этом у этого subshell'а будет своя копия среды (причем «обычные» переменные туда не попадут, их надо будет экспортировать с помощью export, и все созданное в subshell'е там же и останется, можно делать всякие cd /куда/нибудь/далеко и по выходу из subshell это изменение исчезнет, т.к. текущая директория менялась только там). grep сам никогда ничего не делает со строкой, ничего он не разбивает и IFS он при всем желании увидеть вообще не может — это внутренняя переменная шелла, она никуда процессам не экспортируется. Если вы хотите просто поймать строчку, как она есть, правильнее поставить просто "", т.е. без всяких трюков с IFS:
lastmod="$(grep Last-Modified response.out)"

Далее, с файлом:
# Очистка файла для новой записи
echo "" > response.out

Во-первых, вам его не нужно очищать. Единственное, что вы с ним делаете — перезаписываете wget'ом. Опция -o (--output-file) у wget'а перезаписывает лог-файл сама, в отличие от -a (--append-output). Во-вторых, даже если вы хотите очистить файл, это делается так:
echo -n >response.out

Иначе вызов echo выдаст туда newline. В-третьих, если вы захотите выдать пустую строку, echo прекрасно работает и без всяких аргументов:
echo

Далее, собственно, сам wget. Внезапно, вполне wget умеет *сам* проверять Last-Modified и скачивать файл, только если он обновился. Дату последнего обновления проще всего хранить прямо в самом скаченном файле в качестве timestamp. В итоге хитрая конструкция с дополнительным response.out и вырезаниями заголовков превращается в совершенно банальную:
wget -N http://www.lostfilm.tv/rssdd.xml

После этого можно спокойно парсить содержимое файла rssdd.xml, не опасаясь создать лишнюю нагрузку скачиванием.

Сам парсинг гораздо проще сделать так:
cat ../rssdd.xml | grep -io 'http.*torrent' | egrep -i "$movies" | egrep "$quality" | while read link; do
    # обрабатываем ссылку
done

Опция "-e" для grep в данном случае совершенно не нужна, а вот двойные кавычки не помешают. Сами выбирающие выражения вполне можно оформить без скобочек и такой тучи эскейпов при использовании egrep:
# Фильтр сериалов
movies='House.M.D|IT.Crowd|Persons.Unknown|Legend.of.the.Seeker|Leverage|Warehouse.13|Futurama'

# Фильтр качества
quality='\.720p\.\|\.HD\.\|Persons.Unknown|Legend.of.the.Seeker|IT.Crowd'

Технически, вообще правильнее эскейпить еще и все точки, заменяя на \., но вы это и так знаете, судя по первым 2 значениям.

В самой обработке потеряны двойные кавычки:
link=${link/&аmp;/&}
name=${link#*&}

Если вдруг в link встретится пробел или, еще хуже, какая-нибудь конструкция вида $что-нибудь — это все будет интерпретировано совсем не так, как можно было бы подумать.

В строке с «cp» совершенно не обязательно писать ./$name, гораздо лучше написать "$name" — а вообще, на практике, это такая довольно опасная операция, в зависимости от степени интерактивности скрипта уж лучше либо «cp -i» использоваться, либо сделать хоть какую-то обработку ошибок.

Кажется, я почти все строчки затронул? :)
НЛО прилетело и опубликовало эту надпись здесь
Вам абсолютно не нужен bash в этом скрипте — пишите лучше #!/bin/sh -ef — это будет работать быстрее и предсказуемее, чем bash.
Почему будет работать быстрее? Разве в современных система sh не линк на bash?
Не всегда. В убунту, если ничего не поменялось, это линк на dash.
wiki.debian.org/Shell

Ага, раньше был bash, а с Squeeze стал dash.
Зачастую нет. Во многих системах это ksh либо dash (в Debian и его derivatives).
В Debian и Ubuntu — /bin/sh — это dash.

На *BSD — там свой sh:

This version of sh was rewritten in 1989 under the BSD license after the
Bourne shell from AT&T System V Release 4 UNIX.

Даже там, где /bin/sh => /bin/bash, как правило, это запускает bash в режиме POSIX-совместимости, что делает его радикально более другим.
cat ../rssdd.xml | grep -io 'http.*torrent'

grep 'http.*torrent' ../rssdd.xml</source.
oops… перед отправкой умудрился стереть закрывающую скобку у source
-io, конечно же, выкидывать не надо.
>лучше #!/bin/sh -ef — это будет работать быстрее
Будьте добры результаты тестов.
Вы меня так заинтриговали, что наверно даже не усну сегодня, пока не узнаю на сколько наносекунд этот конкретный скрипт из 10 строчек выполнится быстрее под sh, чем под bash. А так хоть узнаю сколько наносекунд я смогу выиграть поменяв свои скрипты. Будет больше времени на семью и спорт… красота…
НЛО прилетело и опубликовало эту надпись здесь
Если вы серьезно и хотите бенчмарков — то, пожалуйста, первое, что находится в гугле по запросу «dash bash benchmark» показывает, что один и тот же скрипт выполняется в bash примерно в 2.3 раза медленнее, чем в dash:
bash 10000 0m19.689s
dash 10000 0m8.577s

На самом деле спор из разряда «писать на C или C++», в том случае, когда ничего из C++ объективно не нужно и использоваться не будет.
Ну передёргивать, так на все деньги, что уж там. Первый тест по вашей ссылке:
bash 1 0m0.002s
dash 1 0m0.001s

Т.е. баш медленнее на 1000000 нс.
С учётом того, что использовать скрипт предполагается по крону (каждые 15 минут), а всё время по сути съест io wget'а и порождение грепов, то я бы подумал… мм… что… вы наверное космические корабли запускаете, раз считаете это за оптимизацию.
При том, что там в день добавляется всего два-три сериала, то дёргать сервер каждые 15 минут нет вообще никакого смысла.
пишите лучше #!/bin/sh -ef

Не совсем согласен. Синтакс различается в зависимости от интерпретатора. Например мой вариант обработки ссылок не сработает на dash (Ubuntu 10.10). Можно конечно изменить сам алгоритм обработки на более универсальный, например на предложенный Вами. Но такое обобщение может привести к непредсказуемым результатам на разных платформах!
Вот как раз POSIX shell — это сколько-нибудь гарантия того, что оно будет плюс-минус одинаково на разных платформах. А использование #!/bin/bash — гарантия того, что на *BSD и некоторых source-based Linux оно не будет работать вообще (хотя бы потому, что там это будет #!/usr/local/bin/bash), на некоторых Windows-реализациях тоже не будет, на стандартном Android — тоже не будет и т.д.
wget -N www.lostfilm.tv/rssdd.xml
cat ../rssdd.xml | grep -io 'http.*torrent' | egrep -i "$movies" | egrep "$quality" | while read link; do
# обрабатываем ссылку
done


В данном случае экономим на создании header, но при этом всегда парсим rssdd.xml.
В моём алгоритме обработка идёт почти на лету ( надо вместо массива сделать как Вы посоветовали «while read link», чем и займусь ) и только если лента обновилась. Но в любом случае мне было интересно читать Ваш отзыв, почерпнул много полезной информации!
В вашем случае вы, во-первых, не делаете массив. *Массивы* в bash действительно есть, но их надо объявлять через declare -a имя, и, по большому счету, они нужны совершенно не для таких задач.

Во-вторых, если очень хочется избежать парсинга файла, то можно делать довольно тривиальную вещь: до закачивания запоминать md5 файла, сравнивать с md5 после — если контрольная сумма изменилась, значит файл нужно парсить, т.е. что-то типа:

LAST_MD5=$(md5sum rssdd.xml)
wget ...
if [ "$LAST_MD5" != "$(md5sum rssdd.xml)" ]; then
    парсить файл
fi
НЛО прилетело и опубликовало эту надпись здесь
После того как несколько недель в RSS лостфильма были битые ссылки, начал парсить главную, уже 2й месяц полет нормальный.
(Так как парсер сьедает &аmp;, буква а здесь и далее по тексту была заменена на русскую. При копировании заменить!)
Парсер ничего не съедает. Пишите просто &amp;amp;, это же HTML обычный.
после написания здесь статья о «щадящим режимомо» можно забыть, сервак ляжет от хабра эффекра
используйте xmllint для парсинга самого rss
>quality='\.720p\.|\.HD\.|Persons.Unknown|Legend.of.the.Seeker|IT.Crowd'
Прозреваю копипасту в этом месте. Автор хотел ограничиться первыми двумя значениями, но уснул на клавиатуре
movies содержит список скачиваемых сериалов. Но на Лостфильме они обычно выходят в двух версиях качества, а я лично стараюсь качать все в высоком качестве. Значит нужен дополнительный фильтр — quality. Но не все сериалы выходили в 720p или HD, поэтому добавим их в конец фильтра что-бы они могли тоже пройти проверку на качество!
Парсить XML с помощью grep — то еще извращение. Что будет если элемент разнесет на 2 строки? Или наоборот не вставят перенос строки?
Попробуйте xmlstalet — шикарный инструмент для обработки XML и вытаскивания различных данных.
А можно пример итерации по ссылкам?
Там нет понятия «итерации», там XSLT + XPath. Для вытаскивания всех ссылок будет что-то типа:

xmlstarlet sel -T -t -m /rss/channel/item/link -c . -n rssdd.xml

У меня это выдает что-то типа:

http://lostfilm.tv/download.php?id=3597&Sons.of.Anarchy.S03E06.HD.rus.LostFilm.TV.mkv.torrent
http://lostfilm.tv/download.php?id=3596&Sons.of.Anarchy.S03E06.rus.LostFilm.TV.avi.torrent
http://lostfilm.tv/download.php?id=3595&Eureka.s04e18.rus.720p.LostFilm.TV.mkv.torrent
http://lostfilm.tv/download.php?id=3594&Eureka.s04e18.rus.LostFilm.TV.avi.torrent
...
Вот ведь — чуть выше написал про xmllint — никто не заметил. Этот вроде почаще встречается и работать с ним не сложнее — те же XPath
curl http://www.lostfilm.tv/rssdd.xml | xmllint --xpath '/rss/channel/item/link/text()' -
А как сделать что-бы он разбивал на строки?
xmlstarlet только %)

И у него есть ровно 2 проблемы:
1) его обычно нифига нигде не стоит %)
2) там, где он стоит — ни разу не угадаешь, как он называется; встречал «xml», «xmlstar», «xmlstarlet»
Точно. Опечатался :)
1) Зато много где есть в репозиториях — можно поставить руками.
2) Это не так критично, если не собираетесь распространять скрипт. Для себя всегда можно подправить.
У него есть еще одна багофича — совершенно долбанутый синтаксис и перманентная необходимость знать, уметь писать и уметь отлаживать XSLT, причем не обычный XSLT, а набранный с помощью «облегченного» набора фигнюшек типа -c, -v, -m, -n и т.п. ;) Пример выше как бы демонстрирует.

Вообще, на самом деле как-то грустно даже, что нет какого-то простого инструмента, распространяемого вместе с той же libxml, скажем, который бы везде ставился и мог элементарные штуки делать типа «вытащить элемент(ы) по XPath из заданного XML», «добавить еще один элемент такого-то вида на к элементу, заданному таким-то XPath» и т.д.
Можно использовать классический вариант XSLT. Но я скажу честно, он очень избыточен — писать долго. На самом деле, если им регулярно пользоваться, то быстро привыкаешь. Я в свое время вытаскивал им инфу из тысяч небольших XML-файликов. Например, есть лог загрузки данных от поставщика информации. И вот нужно проследить как меняласля какой-то блок во времени. Такие задачи удобно решаются с помощью xmlstalet + grep + uniq + awk.
А потом такой синтексис становится совершенно нормальным.
На самом деле все очень просто:
sel — сделать select — выборку
-t — использовать шаблон по упрощенному синтаксису
-m — match — только те элементы, которые соответсвуют XPath
-c — copy — скопировать в вывод
-n — new line — новая строка
Да это всё как раз более-менее понятно, просто когда этим регулярно пользуешься — разумеется, помнишь — а когда раз в год… XPath еще реально запомнить и использовать, а вот всю эту тучу буковок — no way.

Действительно очень жалко, что нет адекватно простого xmlgrep / xmlsed, чтобы можно было делать что-нибудь типа «xmlgrep /a/b/c» и оно бы взяло их и выдало.

В оффтопике, который начинается на букву «P» и заканчивается на «L», есть конструкция xeval для этого…
> что-нибудь типа «xmlgrep /a/b/c» и оно бы взяло их и выдало.
главный вопрос в том, что имеено оно должно выдать — ведь вы имеете дело с элементами, которые ближе к объектам, чем протым строкам.

Если взять самый простой случай — выбрать значения по критерию, то можно использовать xeval.

Кстати, его очень легко сделать самому:

$ cat ./xeval
#!/bin/sh
xmlstarlet sel -T -t -m "$1" -v . -n "$2"

Использование:

$ ./xeval /rss/channel/item/link rss.xml
http://lostfilm.tv/download.php?id=3597&Sons.of.Anarchy.S03E06.HD.rus.LostFilm.TV.mkv.torrent
http://lostfilm.tv/download.php?id=3596&Sons.of.Anarchy.S03E06.rus.LostFilm.TV.avi.torrent
… — здесь все остальное, что было выше

можно даже фильтровать:
$ ./xeval "/rss/channel/item/link[contains(., 'Haven')]" r.xml
http://lostfilm.tv/download.php?id=3589&Haven.s01e09.rus.720p.LostFilm.TV.mkv.torrent
http://lostfilm.tv/download.php?id=3588&Haven.s01e09.rus.LostFilm.TV.avi.torrent
http://lostfilm.tv/download.php?id=3583&Haven.s01e08.rus.720p.LostFilm.TV.mkv.torrent
НЛО прилетело и опубликовало эту надпись здесь
Отлично! :)
Расскажите, как сделали?
НЛО прилетело и опубликовало эту надпись здесь
Вы извлекаете значения всех элементов, потом отправляете на почту.
А как избежать дублирования писем? Запоминат на серверной стороне, что было обработано?
НЛО прилетело и опубликовало эту надпись здесь
Я имел ввиду дуюлирование писем при следующем запуске скрипта. Когда он захочет тоже самое прислать.
НЛО прилетело и опубликовало эту надпись здесь
Теперь понятно. Спасибо.
Отличная идея, тоже прикрутил в свой скрипт отчет на почту!
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории