Книга: Сценарии командной оболочки. Linux, OS X и Unix. 2-е издание
Назад: Глава 6. Системное администрирование: обслуживание системы
Дальше: Глава 8. Инструменты веб-мастера

Глава 7. Пользователи Интернета

Одна из областей, где Unix блистает особенно ярко, — это Интернет. Неважно, собираетесь ли вы запустить быстрый сервер на своем компьютере или просто с толком побродить по Сети, сценарии командной оболочки всегда придут на помощь.

Инструменты для работы с Интернетом допускают возможность управления из сценариев, даже если вы никогда не думали о таком их применении. Например, программой FTP, которая постоянно оказывается в ловушке отладочного режима, можно управлять интересными способами, как описывается в сценарии № 53 ниже. Сценарии командной оболочки часто позволяют улучшить производительность и вывод большинства утилит командной строки, выполняющих те или иные операции с Интернетом.

Первое издание этой книги уверяло читателей, что лучший инструмент для сценариев, работающих с Интернетом, — команда lynx; теперь мы рекомендуем использовать curl. Оба инструмента поддерживают исключительно текстовый интерфейс для доступа в Интернет, но если lynx предлагает механизм, напоминающий браузер, то curl специально проектировался для использования в сценариях и выводит исходный код HTML любых страниц, которые вы решите исследовать.

Например, ниже показано, как с помощью curl получить первые семь строк из главной страницы сайта Dave on Film:

$ curl -s http://www.daveonfilm.com/ | head -7

<!DOCTYPE html>

<html lang="en-US">

<head>

<meta charset="UTF-8" />

<link rel="profile" href="http://gmpg.org/xfn/11" />

<link rel="pingback" href="http://www.daveonfilm.com/xmlrpc.php" />

<title>Dave On Film: Smart Movie Reviews from Dave Taylor</title>

Тот же результат можно получить с помощью lynx, если утилита curl недоступна, но, если у вас имеются обе утилиты, мы рекомендуем использовать curl. Именно с ней мы будем работать в данной главе.

ВНИМАНИЕ

Одно из ограничений приведенных в этой главе сценариев, извлекающих информацию из веб-сайтов, состоит в том, что, если веб-сайт, от которого зависит сценарий, изменит верстку или API после выхода книги, сценарий может перестать работать. Но, имея навык чтения разметки HTML или JSON (даже если вы не понимаете их в полном объеме), вы сумеете все исправить. Проблема трассировки других сайтов является основной причиной создания расширяемого языка разметки (Extensible Markup Language, XML): он позволяет разработчикам сайтов возвращать содержимое страниц отдельно от правил его размещения.

№ 53. Загрузка файлов через FTP

Когда-то одним из самых востребованных применений Интернета была передача файлов, а одним из самых простых решений этой задачи стал протокол передачи файлов (File Transfer Protocol, FTP). На базовом уровне все взаимодействия в Интернете сводятся к передаче файлов. Например, веб-браузер запрашивает передачу HTML-документа и сопутствующих изображений, чат-сервер постоянно передает строки дискуссии взад-вперед, почтовые программы пересылают электронные письма из одного конца мира в другой.

Оригинальная программа FTP все еще остается в строю, и, несмотря на довольно убогий интерфейс, она обладает достаточно мощными средствами и возможностями, чтобы иметь ее на вооружении. Существует богатое разнообразие программ с поддержкой FTP, из которых особенно примечательны FileZilla (http://filezilla-project.org/) и NcFTP (http://www.ncftp.org/), плюс масса замечательных графических интерфейсов, делающих работу с FTP более удобной. Однако FTP с успехом можно использовать для загрузки и выгрузки файлов, написав сценарии-обертки на языке командной оболочки.

Например, FTP часто используется для загрузки файлов из Интернета. Именно эту возможность реализует сценарий в листинге 7.1. Нередко файлы находятся на анонимных FTP-серверах, имеющих адреса URL следующего вида: ftp://<некоторый_сервер>/<путь>/<имя_файла>/.

Код

Листинг 7.1. Сценарий ftpget

  #!/bin/bash

  # ftpget -- получая URL в стиле ftp, разворачивает его и пытается получить

  #   файл, используя прием доступа к анонимному ftp.

  anonpass="[email protected]$(hostname)"

  if [ $# -ne 1 ] ; then

    echo "Usage: $0 ftp://..." >&2

    exit 1

  fi

  # Типичный URL: ftp://ftp.ncftp.com/unixstuff/q2getty.tar.gz

  if [ "$(echo $1 | cut -c1-6)" != "ftp://" ] ; then

    echo "$0: Malformed url. I need it to start with ftp://" >&2

    exit 1

  fi

  server="$(echo $1 | cut -d/ -f3)"

  filename="$(echo $1 | cut -d/ -f4-)"

  basefile="$(basename $filename)"

  echo ${0}: Downloading $basefile from server $server

  ftp -np << EOF

  open $server

  user ftp $anonpass

  get "$filename" "$basefile"

  quit

  EOF

  if [ $? -eq 0 ] ; then

    ls -l $basefile

  fi

  exit 0

Как это работает

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

Запуск сценария

Сценарий очень прост в использовании: достаточно указать полный адрес URL файла на FTP-сервере, и файл будет загружен в текущий каталог, как показано в листинге 7.2.

Результаты

Листинг 7.2. Запуск сценария ftpget

$ ftpget ftp://ftp.ncftp.com/unixstuff/q2getty.tar.gz

ftpget: Downloading q2getty.tar.gz from server ftp.ncftp.com

-rw-r--r--  1 taylor  staff  4817 Aug 14 1998 q2getty.tar.gz

Некоторые версии FTP более многословны, чем другие. Кроме того, нередки случаи несоответствия реализаций протокола на стороне клиента и на стороне сервера. В подобных ситуациях такие «многословные» программы FTP иногда выводят пугающие сообщения об ошибках, к примеру Unimplemented command («Нереализованная команда»). Вы можете без опаски игнорировать их. Например, в листинге 7.3 показан вывод того же сценария, запущенного в OS X.

Листинг 7.3. Запуск сценария ftpget в OS X

$ ftpget ftp://ftp.ncftp.com/ncftp/ncftp-3.1.5-src.tar.bz2

../Scripts.new/053-ftpget.sh: Downloading q2getty.tar.gz from server ftp.

ncftp.com

Connected to ncftp.com.

220 ncftpd.com NcFTPd Server (licensed copy) ready.

331 Guest login ok, send your complete e-mail address as password.

230-You are user #2 of 16 simultaneous users allowed.

230-

230 Logged in anonymously.

Remote system type is UNIX.

Using binary mode to transfer files.

local: q2getty.tar.gz remote: unixstuff/q2getty.tar.gz

227 Entering Passive Mode (209,197,102,38,194,11)

150 Data connection accepted from 97.124.161.251:57849; transfer starting for

q2getty.tar.gz (4817 bytes).

100% |*******************************************************| 4817

67.41 KiB/s 00:00 ETA

226 Transfer completed.

4817 bytes received in 00:00 (63.28 KiB/s)

221 Goodbye.

-rw-r--r--  1 taylor  staff  4817 Aug 14  1998 q2getty.tar.gz

Если ваша версия FTP чересчур многословна и вы пользуетесь OS X, программу FTP можно сделать более сдержанной, добавив в ее вызов флаг -V (то есть заменить команду ftp -n командой ftp -nV).

Усовершенствование сценария

В этот сценарий можно добавить автоматическое разархивирование загружаемых файлов (пример разархивирования вы найдете в сценарии № 33, глава 4), имеющих определенные расширения. Многие сжатые файлы, такие как .tar.gz и .tar.bz2, разархивируются с помощью системной команды tar.

В этот сценарий можно также добавить функцию выгрузки указанного файла на FTP-сервер. Если сервер поддерживает анонимные соединения (в наши дни таких серверов осталось очень немного из-за взломщиков-дилетантов и других злоумышленников, но это уже другая история), вам достаточно будет определить каталог назначения в командной строке или в самом сценарии и заменить команду get на put в последовательности команд, как показано ниже:

ftp -np << EOF

open $server

user ftp $anonpass

cd $destdir

put "$filename"

quit

EOF

Для доступа к защищенной паролем учетной записи на сервере FTP можно добавить в сценарий запрос пароля в интерактивном режиме, отключив эхо-вывод перед инструкцией read, и включить его снова после ввода:

/bin/echo -n "Password for ${user}: "

stty -echo

read password

stty echo

echo ""

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

№ 54. Извлечение адресов URL из веб-страницы

Простейшее применение lynx заключается в извлечении списка адресов URL, находящихся в данной веб-странице, что может пригодиться при поиске ссылок в Интернете. Выше мы говорили, что в этом издании книги предпочли уйти от lynx в сторону curl, но, как оказывается, lynx в сто раз удобнее для решения этой задачи (см. листинг 7.4), чем curl, потому что автоматически анализирует разметку HTML, тогда как curl вынуждает вас делать это вручную.

В вашей системе нет программы lynx? Большинство современных систем Unix снабжается диспетчерами пакетов, такими как yum в Red Hat, apt в Debian и brew в OS X (впрочем, brew не устанавливается по умолчанию), с помощью которых можно установить lynx. Если вы решите скомпилировать lynx самостоятельно или пожелаете загрузить скомпилированные двоичные файлы, вы найдете все необходимое по адресу: http://lynx.browser.org/.

Код

Листинг 7.4. Сценарий getlinks

#!/bin/bash

# getlinks -- получая URL, возвращает все относительные и абсолютные ссылки.

#   Принимает три параметра: -d генерирует первичные домены в каждой ссылке,

#   -i выводит список только внутренних ссылок на сайт (то есть на другие

#   страницы на том же сайте), и -x выводит список только внешних ссылок

#   (в противоположность -i).

if [ $# -eq 0 ] ; then

  echo "Usage: $0 [-d|-i|-x] url" >&2

  echo "-d=domains only, -i=internal refs only, -x=external only" >&2

  exit 1

fi

if [ $# -gt 1 ] ; then

  case "$1" in

    -d) lastcmd="cut -d/ -f3|sort|uniq"

         shift

         ;;

    -r) basedomain="http://$(echo $2 | cut -d/ -f3)/"

        lastcmd="grep \"^$basedomain\"|sed \"s|$basedomain||g\"|sort|uniq"

        shift

        ;;

    -a) basedomain="http://$(echo $2 | cut -d/ -f3)/"

        lastcmd="grep -v \"^$basedomain\"|sort|uniq"

        shift

        ;;

     *) echo "$0: unknown option specified: $1" >&2

        exit 1

  esac

else

  lastcmd="sort|uniq"

fi

lynx -dump "$1"|\

  sed -n '/^References$/,$p'|\

  grep -E '[[:digit:]]+\.'|\

  awk '{print $2}'|\

  cut -d\? -f1|\

  eval $lastcmd

exit 0

Как это работает

Отображая страницу, lynx отображает ее текст, стремясь сохранить форматирование как можно ближе к оригиналу, а также список всех гипертекстовых ссылок, найденных на этой странице. Данный сценарий извлекает только ссылки с использованием команды sed для вывода всего, что следует за строкой «References» (Ссылки) в тексте веб-страницы . Затем сценарий обрабатывает полученный список, как определено флагами, заданными пользователями.

Этот сценарий демонстрирует один интересный прием: настройку переменной lastcmd (, , , ) для фильтрации списка ссылок в соответствии с флагами, заданными пользователем. После настройки переменной lastcmd применяется удивительно удобная команда eval , чтобы заставить командную оболочку интерпретировать содержимое переменной как команду, а не как значение.

Запуск сценария

По умолчанию сценарий выводит список всех ссылок, найденных на указанной веб-странице, и не только тех, которые начинаются с префикса http:. Сценарию может быть передано три необязательных флага, влияющих на результат: флаг -d требует выводить только доменные имена в совпавших адресах URL, флаг -r требует оставить в списке только относительные ссылки (то есть указывающие на другие страницы на том же сервере, откуда получена текущая страница), и флаг -a требует вывести только абсолютные ссылки (то есть указывающие на другие серверы).

Результаты

Простой запуск сценария возвращает список всех ссылок, найденных на указанной странице, как показано в листинге 7.5.

Листинг 7.5. Запуск сценария getlinks

$ getlinks http://www.daveonfilm.com/ | head -10

http://instagram.com/d1taylor

http://pinterest.com/d1taylor/

http://plus.google.com/110193533410016731852

https://plus.google.com/u/0/110193533410016731852

https://twitter.com/DaveTaylor

http://www.amazon.com/Doctor-Who-Shada-Adventures-Douglas/

http://www.daveonfilm.com/

http://www.daveonfilm.com/about-me/

http://www.daveonfilm.com/author/d1taylor/

http://www.daveonfilm.com/category/film-movie-reviews/

Еще одно из возможных применений сценария — получение списка доменных имен, на которые ссылается указанный сайт. На этот раз воспользуемся стандартным инструментом Unix — командой wc, чтобы подсчитать общее количество найденных ссылок:

$ getlinks http://www.amazon.com/ | wc -l

219

На домашней странице сайта Amazon найдено 219 ссылок. Внушительное количество! А сколько разных доменных имен представлено в этих ссылках? Давайте отфильтруем список, запустив сценарий с флагом -d:

$ getlinks -d http://www.amazon.com/ | head -10

amazonlocal.com

aws.amazon.com

fresh.amazon.com

kdp.amazon.com

services.amazon.com

www.6pm.com

www.abebooks.com

www.acx.com

www.afterschool.com

www.alexa.com

Сайт Amazon не стремится уводить посетителей за свои пределы, но есть ряд партнерских сайтов, ссылки на которые все же присутствуют на главной странице. Конечно, не все придерживаются такой политики.

А что, если ссылки на странице Amazon разделить на абсолютные и относительные?

$ getlinks -a http://www.amazon.com/ | wc -l

51

$ getlinks -r http://www.amazon.com/ | wc -l

222

Вполне ожидаемо, что количество относительных ссылок на странице Amazon, ссылающихся на внутренние страницы, в четыре раза превышает количество абсолютных ссылок, уводящих на другие веб-сайты. Всякий коммерческий сайт должен стремиться удержать пользователей на своих страницах!

Усовершенствование сценария

Как видите, сценарий getlinks может быть очень полезным аналитическим инструментом. Далее в книге вы найдете один из вариантов его дальнейшего усовершенствования: сценарий № 69 в главе 9 помогает быстро проверить действительность всех гипертекстовых ссылок.

№ 55. Получение информации о пользователе GitHub

Сайт GitHub создавался как серьезное подспорье для индустрии открытого программного обеспечения и открытого сотрудничества людей по всему миру. Многие системные администраторы и разработчики посещают GitHub, чтобы получить исходный код какого-нибудь открытого проекта или оставить отчет о проблеме. Так как по сути GitHub — это социальная платформа для разработчиков, возможность быстро получить основную информацию о том или ином пользователе была бы весьма кстати. Сценарий в листинге 7.6 выводит некоторые сведения о заданном пользователе GitHub и позволяет познакомиться с очень мощным GitHub API.

Код

Листинг 7.6. Сценарий githubuser

  #!/bin/bash

  # githubuser -- Получая имя пользователя GitHub, выводит информацию о нем.

  if [ $# -ne 1 ]; then

    echo "Usage: $0 <username>"

    exit 1

  fi

  # Флаг -s подавляет вывод дополнительной информации,

  #   которую обычно  выводит curl.

  curl -s "https://api.github.com/users/$1" | \

           awk -F'"' '

               /\"name\":/ {

                 print $4" is the name of the GitHub user."

               }

               /\"followers\":/{

                 split($3, a, " ")

                 sub(/,/, "", a[2])

                 print "They have "a[2]" followers."

               }

               /\"following\":/{

                 split($3, a, " ")

                 sub(/,/, "", a[2])

                 print "They are following "a[2]" other users."

               }

               /\"created_at\":/{

                 print "Their account was created on "$4"."

               }

               '

  exit 0

Как это работает

Следует признать, что это сценарий скорее на языке awk, чем на языке bash, но иногда для анализа данных приходится привлекать дополнительные возможности awk (GitHub API возвращает данные в формате JSON). С помощью curl сценарий запрашивает у сайта GitHub информацию о пользователе , заданном в аргументе, и передает данные в формате JSON команде awk. В сценарии awk определяется разделитель полей — символ двойной кавычки, чтобы упростить анализ JSON-данных. Затем выполняется сопоставление данных с несколькими регулярными выражениями в сценарии awk и выводятся результаты в удобочитаемом виде.

Запуск сценария

Сценарий принимает единственный аргумент: имя пользователя GitHub. Если указанное имя пользователя не будет найдено, сценарий ничего не выведет.

Результаты

Если сценарию передается существующее имя пользователя, он должен вывести сводную информацию об этом пользователе GitHub, как показано в листинге 7.7.

Листинг 7.7. Запуск сценария githubuser

$ githubuser brandonprry

Brandon Perry is the name of the GitHub user.

They have 67 followers.

They are following 0 other users.

Their account was created on 2010-11-16T02:06:41Z.

Усовершенствование сценария

Этот сценарий имеет большой потенциал благодаря объему информации, возвращаемому GitHub API. Он выводит только четыре значения из возвращаемых JSON-данных. Создание «резюме» на основе информации, которую API возвращает подобно многим веб-службам, лишь одна из возможностей.

№ 56. Поиск по почтовому индексу

Для демонстрации еще одного приема извлечения информации из Интернета, на этот раз с помощью curl, создадим простой инструмент поиска почтовых индексов. Передайте сценарию в листинге 7.8 почтовый индекс, и вы узнаете город и штат (в США), которому он принадлежит. Достаточно просто.

Самой очевидной была бы идея использовать официальный веб-сайт почтовой службы США (US Postal Service), но мы задействуем другой сайт, http://city-data.com/, в котором для каждого почтового индекса отводится своя веб-страница, что упрощает извлечение информации.

Код

Листинг 7.8. Сценарий zipcode

#!/bin/bash

# zipcode -- получая почтовый индекс, определяет город и штат в США.

#   Использует сайт city-data.com, в котором для каждого почтового

#   индекса отводится своя веб-страница.

baseURL="http://www.city-data.com/zips"

/bin/echo -n "ZIP code $1 is in "

curl -s -dump "$baseURL/$1.html" | \

  grep -i '<title>' | \

  cut -d\( -f2 | cut -d\) -f1

exit 0

Как это работает

Адреса URL страниц с информацией о почтовых индексах на сайте http://city-data.com/ имеют единообразную организацию: сам почтовый индекс является заключительной частью URL:

http://www.city-data.com/zips/80304.html

Такое единообразие позволяет легко сконструировать адрес URL, соответствующий заданному почтовому индексу. Возвращаемая страница содержит название города в заголовке, которое легко отличить по открывающей и закрывающей круглым скобкам, как показано ниже:

<title>80304 Zip Code (Boulder, Colorado) Profile - homes, apartments,

schools, population, income, averages, housing, demographics, location,

statistics, residents and real estate info</title>

Строка длинная, но легко поддается анализу!

Запуск сценария

Чтобы воспользоваться сценарием, достаточно просто передать ему почтовый индекс в аргументе командной строки. Если указан действительный индекс, сценарий выведет название города и штата, как показано в листинге 7.9.

Результаты

Листинг 7.9. Запуск сценария zipcode

$ zipcode 10010

ZIP code 10010 is in New York, New York

$ zipcode 30001

ZIP code 30001 is in <title>Page not found – City-Data.com</title>

$ zipcode 50111

ZIP code 50111 is in Grimes, Iowa

Так как 30001 не является действительным почтовым индексом, сценарий сгенерировал сообщение об ошибке Page not found («Страница не найдена»). Оно выглядит немного неопрятно, но мы можем улучшить его.

Усовершенствование сценария

Наиболее очевидным усовершенствованием могло бы стать выполнение каких-то действий в ответ на ошибки вместо вывода невнятной последовательности <title>Page not found – City-Data.com</title>. Еще более интересный вариант — добавить флаг -a, который сообщал бы сценарию о необходимости вывода дополнительной информации о регионе, тем более что http://city-data .com/ предлагает довольно много информации, помимо названий городов, включая площадь, сведения о населении и цены на недвижимость.

№ 57. Поиск по телефонному коду города

Сценарий поиска по телефонному коду города является разновидностью предыдущего. Как оказывается, реализовать такой сценарий действительно очень просто, благодаря существованию простых для анализа веб-страниц с кодами городов. Например, страница по адресу http://www.bennetyee.org/ucsd-pages/area.html легко поддается анализу, не только потому, что она хранит информацию в табличной форме, но и потому, что автор использовал атрибуты HTML для идентификации элементов. Например, строка с информацией о коде 207 выглядит так:

<tr><td align=center><a name="207">207</a></td><td align=center>ME</td><td

align=center>-5</td><td> Maine</td></tr>

Мы использовали этот сайт в сценарии (листинг 7.10) поиска по телефонному коду города.

Код

Листинг 7.10. Сценарий areacode

#!/bin/bash

# areacode -- получая трехзначный телефонный код, действующий в США,

#   определяет город и штат по данным в простой табличной форме, на

#   веб-сайте Беннета Йи (Bennet Yee).

source="http://www.bennetyee.org/ucsd-pages/area.html"

if [ -z "$1" ] ; then

  echo "usage: areacode <three-digit US telephone area code>"

  exit 1

fi

# wc -c вернет количество символов + символ перевода строки,

#   то есть для 3 цифр = 4 символа

if [ "$(echo $1 | wc -c)" -ne 4 ] ; then

  echo "areacode: wrong length: only works with three-digit US area codes"

  exit 1

fi

# Все символы -- цифры?

if [ ! -z "$(echo $1 | sed 's/[[:digit:]]//g')" ] ; then

  echo "areacode: not-digits: area codes can only be made up of digits"

  exit 1

fi

# Теперь можно выполнить поиск по телефонному коду...

result="$(curl -s -dump $source | grep "name=\"$1" | \

  sed 's/<[^>]*>//g;s/^ //g' | \

  cut -f2- -d\ | cut -f1 -d\( )"

echo "Area code $1 =$result"

exit 0

Как это работает

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

Запуск сценария

Этот сценарий принимает единственный аргумент — телефонный код города для поиска. Примеры использования сценария демонстрируются листинге 7.11.

Результаты

Листинг 7.11. Тестирование сценария areacode

$ areacode 817

Area code 817 = N Cent. Texas: Fort Worth area

$ areacode 512

Area code 512 = S Texas: Austin

$ areacode 903

Area code 903 = NE Texas: Tyler

Усовершенствование сценария

Самое простое усовершенствование, которое можно предложить, — реализовать обратный поиск, когда по названию города и штата сценарий находит и выводит все телефонные коды, соответствующие заданному городу.

№ 58. Слежение за погодой

Если вы проводите весь день в кабинете или в серверном зале, уткнувшись носом в терминал, вам наверняка иногда очень хочется выйти на улицу, прогуляться, особенно в хорошую погоду. Weather Underground (http://www.wunderground.com/) — отличный веб-сайт, который предлагает прикладной интерфейс (API) с бесплатным доступом для разработчиков. Вам нужно только зарегистрировать API-ключ. Имея API-ключ, можно написать короткий сценарий командной оболочки (показан в листинге 7.12), сообщающий, насколько хороша (или плоха) погода. Знание погоды поможет нам решить, стоит ли выходить на короткую прогулку.

Код

Листинг 7.12. Сценарий weather

  #!/bin/bash

  # weather -- использует Wunderground API для получения информации

  #   о погоде по почтовому индексу (США).

  if [ $# -ne 1 ]; then

    echo "Usage: $0 <zipcode>"

    exit 1

  fi

  apikey="b03fdsaf3b2e7cd23" # Это недействительный API-ключ -- вы

                             #   должны получить свой.

  weather=`curl -s \

      "https://api.wunderground.com/api/$apikey/conditions/q/$1.xml"`

  state=`xmllint --xpath \

      //response/current_observation/display_location/full/text\(\) \

      <(echo $weather)`

  zip=`xmllint --xpath \

      //response/current_observation/display_location/zip/text\(\) \

      <(echo $weather)`

  current=`xmllint --xpath \

      //response/current_observation/temp_f/text\(\) \

      <(echo $weather)`

  condition=`xmllint --xpath \

      //response/current_observation/weather/text\(\) \

      <(echo $weather)`

  echo $state" ("$zip") : Current temp "$current"F and "$condition" outside."

  exit 0

Как это работает

Сценарий вызывает команду curl, чтобы отправить запрос к Wunderground API и сохранить HTTP-ответ в переменной weather . Затем он использует утилиту xmllint (ее легко установить с помощью диспетчера пакетов, такого как apt, yum или brew) для выполнения XPath-запроса к полученным данным , причем в конце каждого вызова xmllint применяется интересный синтаксис <(echo $weather), поддерживаемый языком bash. Эта конструкция принимает вывод команды внутри скобок и передает его указанной программе в виде дескриптора файла, то есть программа думает, что читает настоящий файл. После выборки необходимой информации из полученных данных в формате XML она выводится в виде удобочитаемого сообщения с краткими сведениями о погоде.

Запуск сценария

Запуская сценарий, достаточно передать ему почтовый индекс, как показано в листинге 7.13. Очень просто!

Результаты

Листинг 7.13. Тестирование сценария weather

$ weather 78727

Austin, TX (78727) : Current temp 59.0F and Clear outside.

$ weather 80304

Boulder, CO (80304) : Current temp 59.2F and Clear outside.

$ weather 10010

New York, NY (10010) : Current temp 68.7F and Clear outside.

Усовершенствование сценария

Откроем небольшой секрет. В действительности этот сценарий принимает не только почтовые индексы. Службе Wunderground API можно также передать название региона, например CA/San_Francisco (попробуйте передать эту строку сценарию weather!). Однако такой формат не очень удобен: он требует использовать символы подчеркивания вместо пробелов и символ слеша (/) в середине. В качестве одного из усовершенствований можно было бы добавить в сценарий запрос на ввод аббревиатуры штата и названия города и автоматически заменять пробелы символами подчеркивания, если сценарий запущен без аргумента. Как обычно, можно также добавить дополнительную проверку ошибок. Например, что получится, если передать сценарию четырехзначный или недействительный почтовый индекс?

№ 59. Поиск информации о кинофильме в базе IMDb

Сценарий в листинге 7.14 демонстрирует более сложный пример доступа к Интернету с помощью lynx для поиска в базе данных Internet Movie Database (http://www.imdb.com/) сведений о кинофильмах по указанному шаблону. База данных IMDb назначает уникальный числовой код каждому фильму, каждому телевизионному сериалу и даже каждой отдельной серии; если пользователь укажет такой код, данный сценарий вернет краткое описание фильма. В противном случае он вернет список фильмов, частично или полностью соответствующих указанному названию.

В зависимости от типа запроса (числовой код или название) сценарий обращается по разным адресам URL и сохраняет результаты в кэше, чтобы многократно обойти содержимое страницы для извлечения разных фрагментов информации. Для этого используется много — очень много! — вызовов команд sed и grep, в чем вы можете убедиться лично.

Код

Листинг 7.14. Сценарий moviedata

  #!/bin/bash

  # moviedata -- получая название фильма или сериала, возвращает список

  #   совпадений. Если пользователь укажет числовой код IMDb, вернет

  #   краткое описание фильма. Использует базу данных Internet Movie Database.

  titleurl="http://www.imdb.com/title/tt"

  imdburl="http://www.imdb.com/find?s=tt&exact=true&ref_=fn_tt_ex&q="

  tempout="/tmp/moviedata.$$"

  summarize_film()

  {

    # Форматирует описания фильма.

    grep "<title>" $tempout | sed 's/<[^>]*>//g;s/(more)//'

    grep --color=never -A2 '<h5>Plot:' $tempout | tail -1 | \

      cut -d\< -f1 | fmt | sed 's/^/ /'

    exit 0

  }

  trap "rm -f $tempout" 0 1 15

  if [ $# -eq 0 ] ; then

    echo "Usage: $0 {movie title | movie ID}" >&2

    exit 1

  fi

  #########

  # Выяснить тип запроса: по названию или по коду IMDb

  nodigits="$(echo $1 | sed 's/[[:digit:]]*//g')"

  if [ $# -eq 1 -a -z "$nodigits" ] ; then

    lynx -source "$titleurl$1/combined" > $tempout

    summarize_film

    exit 0

  fi

  ##########

  # Это не код IMDb, поэтому нужно выполнить поиск...

  fixedname="$(echo [email protected] | tr ' ' '+')" # для формирования URL

  url="$imdburl$fixedname"

  lynx -source $imdburl$fixedname > $tempout

  # Нет результатов?

  fail="$(grep --color=never '<h1 class="findHeader">No ' $tempout)"

  # Если найдено несколько похожих названий...

  if [ ! -z "$fail" ] ; then

    echo "Failed: no results found for $1"

    exit 1

  elif [ ! -z "$(grep '<h1 class="findHeader">Displaying' $tempout)" ] ; then

    grep --color=never '/title/tt' $tempout | \

    sed 's/</\

</g' | \

    grep -vE '(.png|.jpg|>[ ]*$)' | \

    grep -A 1 "a href=" | \

    grep -v '^--$' | \

    sed 's/<a href="\/title\/tt//g;s/<\/a> //' | \

    awk '(NR % 2 == 1) { title=$0 } (NR % 2 == 0) { print title " " $0 }' | \

    sed 's/\/.*>/: /' | \

    sort

  fi

  exit 0

Как это работает

Этот сценарий конструирует разные адреса URL, в зависимости от содержимого аргумента. Если пользователь указал числовой код, сценарий конструирует соответствующий URL, загружает с помощью lynx сведения о фильме, сохраняет их в файле $tempout и затем вызывает функцию summarize_film() . Ничего сложного.

Но если пользователь указал название, тогда сценарий конструирует URL с запросом поиска к базе данных IMDb и сохраняет полученную страницу во временном файле. Если базе данных IMDb не удалось найти совпадений, она возвращает в HTML-странице тег <h1> с атрибутом class="findHeader" и текстом No results («Нет результатов»). Именно эту ситуацию проверяет команда в строке . Далее следует простая проверка: если содержимое $fail имеет ненулевую длину, сценарий сообщает об отсутствии результатов.

Однако если $fail ничего не содержит, это означает, что поиск по заданному шаблону удался и в файле хранятся некоторые результаты. Далее в результатах выполняется поиск шаблона /title/tt, но здесь есть одна сложность: разобрать результаты, возвращаемые базой данных IMDb, очень непросто, потому что для каждой заданной ссылки в результатах имеется несколько совпадений. Остальная последовательность замысловатых команд sed|grep|sed пытается идентифицировать и удалить повторяющиеся совпадения и оставить только то, что имеет значение.

Кроме того, когда IMDb находит совпадение, такое как "Lawrence of Arabia (1962)", она возвращает название и год в двух разных элементах HTML, в двух разных строках. М-да. Однако год нам определенно необходим, чтобы различать фильмы с одинаковыми названиями. Этим занимается команда awk в строке , ,используя весьма хитроумный способ.

Для тех, кто не знаком с awk, отметим, что в общем случае awk-сценарий имеет следующую организацию: (условие) { действие }. Эта строка сохраняет нечетные строки в $title, и затем, когда очередь доходит до четной строки (с годом и данными о соответствии), она выводит предыдущую и текущую строки в одну строку.

Запуск сценария

Хотя этот сценарий невелик, он обладает большой гибкостью в отношении формата входных данных, как видно из листинга 7.15. Вы можете указать название фильма в кавычках или как набор отдельных слов, а можете ввести восьмизначный числовой код IMDb, чтобы выбрать конкретный фильм.

Результаты

Листинг 7.15. Запуск сценария moviedata

$ moviedata lawrence of arabia

0056172: Lawrence of Arabia (1962)

0245226: Lawrence of Arabia (1935)

0390742: Mighty Moments from World History (1985) (TV Series)

1471868: Mystery Files (2010) (TV Series)

1471868: Mystery Files (2010) (TV Series)

1478071: Lawrence of Arabia (1985) (TV Episode)

1942509: Lawrence of Arabia (TV Episode)

1952822: Lawrence of Arabia (2011) (TV Episode)

$ moviedata 0056172

Lawrence of Arabia (1962)

    A flamboyant and controversial British military figure and his

    conflicted loyalties during his World War I service in the Middle East.

Усовершенствование сценария

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

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

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

№ 60. Пересчет валют по курсу

В первом издании этой книги задача пересчета денежных сумм из одной валюты в другую оказалась довольно сложной, и для ее решения потребовалось написать два сценария: один извлекал сведения о курсах валют из финансового веб-сайта и сохранял их в особом формате, а другой использовал эти данные для фактического пересчета, например, из долларов США в евро. В минувшие годы, однако, Всемирная паутина продолжала развиваться, и сейчас мы не видим причин перелопачивать горы информации, когда имеются такие сайты, как Google, предлагающие простые и дружественные для использования из сценариев калькуляторы.

Представленный в листинге 7.16 сценарий пересчета валют по курсу просто использует валютный калькулятор, доступный по адресу: http://www.google.com/finance/converter.

Код

Листинг 7.16. Сценарий convertcurrency

#!/bin/bash

# convertcurrency -- принимая сумму и базовую валюту, пересчитывает эту

#   сумму в другой валюте. Для обозначения валют используются идентификаторы

#   ISO. Для фактических вычислений использует валютный калькулятор Google:

#   http://www.google.com/finance/converter

if [ $# -eq 0 ]; then

  echo "Usage: $(basename $0) amount currency to currency"

  echo "Most common currencies are CAD, CNY, EUR, USD, INR, JPY, and MXN"

  echo "Use \"$(basename $0) list\" for a list of supported currencies."

fi

if [ $(uname) = "Darwin" ]; then

  LANG=C # Для решения проблемы в OS X с ошибочными последовательностями

         #   байтов и lynx

fi

url="https://www.google.com/finance/converter"

tempfile="/tmp/converter.$$"

lynx=$(which lynx)

# Так как эти данные используются многократно, извлечем их,

#   а потом займемся всем остальным.

currencies=$($lynx -source "$url" | grep "option value=" | \

  cut -d\" -f2- | sed 's/">/ /' | cut -d\( -f1 | sort | uniq)

########### Выполнить все запросы, не связанные с пересчетом.

if [ $# -ne 4 ] ; then

  if [ "$1" = "list" ] ; then

    # Вывести список всех символов валют, известных калькулятору.

    echo "List of supported currencies:"

    echo "$currencies"

  fi

  exit 0

fi

########### Теперь выполним пересчет.

if [ $3 != "to" ] ; then

  echo "Usage: $(basename $0) value currency TO currency"

  echo "(use \"$(basename $0) list\" to get a list of all currency values)"

  exit 0

fi

amount=$1

basecurrency="$(echo $2 | tr '[:lower:]' '[:upper:]')"

targetcurrency="$(echo $4 | tr '[:lower:]' '[:upper:]')"

# Наконец, фактический вызов калькулятора!

$lynx -source "$url?a=$amount&from=$basecurrency&to=$targetcurrency" | \

  grep 'id=currency_converter_result' | sed 's/<[^>]*>//g'

exit 0

Как это работает

Валютный калькулятор Google принимает три параметра непосредственно в URL: сумму, исходную валюту и конечную валюту. Как выглядит такой URL, можно видеть в следующем примере, запрашивающем пересчет 100 долларов США в мексиканские песо:

https://www.google.com/finance/converter?a=100&from=USD&to=MXN

Сценарий ожидает, что пользователь определит все три поля в аргументах, и затем передает их сайту Google в URL.

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

Запуск сценария

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

Результаты

Листинг 7.17. Запуск сценария convertcurrency

$ convertcurrency

Usage: convert amount currency to currency

Most common currencies are CAD, CNY, EUR, USD, INR, JPY, and MXN

Use "convertcurrency list" for a list of supported currencies.

$ convertcurrency list | head -10

List of supported currencies:

AED United Arab Emirates Dirham

AFN Afghan Afghani

ALL Albanian Lek

AMD Armenian Dram

ANG Netherlands Antillean Guilder

AOA Angolan Kwanza

ARS Argentine Peso

AUD Australian Dollar

AWG Aruban Florin

$ convertcurrency 75 eur to usd

75 EUR = 84.5132 USD

Усовершенствование сценария

Несмотря на строгость и простоту веб-калькулятора, в вывод результатов все же можно добавить немного порядка. Например, вывод результатов пересчета в листинге 7.17 лишен смысла, поскольку сумма в долларах США в нем выражена числом с четырьмя знаками после запятой, даже при том, что для отображения количества центов достаточно двух знаков. Правильнее было бы вывести 84,51 или округлить до 84,52. Эту ошибку в сценарии желательно исправить.

И еще, пока вы не отвлеклись, хорошо бы добавить в сценарий проверку сокращенных обозначений валют. Пригодилось бы и преобразование кодов валют в полные названия, например, чтобы можно было выяснить, что AWG — это арубанские флорины или что BTC — это Bitcoin (Биткоин).

№ 61. Извлечение информации об адресе Биткоин

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

Код

Листинг 7.18. Сценарий getbtcaddr

#!/bin/bash

# getbtcaddr -- получая адрес Биткоин, возвращает полезную информацию.

if [ $# -ne 1 ]; then

  echo "Usage: $0 <address>"

  exit 1

fi

base_url="https://blockchain.info/q/"

balance=$(curl -s $base_url"addressbalance/"$1)

recv=$(curl -s $base_url"getreceivedbyaddress/"$1)

sent=$(curl -s $base_url"getsentbyaddress/"$1)

first_made=$(curl -s $base_url"addressfirstseen/"$1)

echo "Details for address $1"

echo -e "\tFirst seen: "$(date -d @$first_made)

echo -e "\tCurrent balance: "$balance

echo -e "\tSatoshis sent: "$sent

echo -e "\tSatoshis recv: "$recv

Как это работает

Сценарий несколько раз вызывает команду curl, чтобы извлечь ценные сведения из заданного адреса Биткоин. Соответствующая служба, доступная по адресу: http://blockchain.info/, дает простую возможность получить полную информацию об адресе Биткоин и цепочке блоков. Фактически, нам даже не потребовалось анализировать информацию, получаемую от службы, потому что она возвращает простые одиночные значения. Получив баланс для заданного адреса, сведения о количестве полученных и потраченных монет и о том, когда осуществлялись платежи, сценарий выводит эту информацию на экран.

Запуск сценария

Сценарий принимает единственный аргумент — адрес Биткоин, информацию о котором требуется получить. Следует отметить, что, если передать сценарию строку, не являющуюся действительным адресом Биткоин, он выведет нули в строках, сообщающих о балансе и полученных и потраченных суммах, а в качестве даты создания будет указан 1969 год. Любые ненулевые суммы указываются в сатоши (satoshi) — минимальных единицах обозначения сумм в Биткоин (как, например, пенни, но с намного большим количеством знаков после запятой).

Результаты

Пользоваться сценарием getbtcaddr очень просто, как показано в листинге 7.19, так как он принимает единственный аргумент, адрес Биткоин, информацию о котором требуется получить.

Листинг 7.19. Запуск сценария getbtcaddr

$ getbtcaddr 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

Details for address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

    First seen: Sat Jan 3 12:15:05 CST 2009

    Current balance: 6554034549

    Satoshis sent: 0

    Satoshis recv: 6554034549

$ getbtcaddr 1EzwoHtiXB4iFwedPr49iywjZn2nnekhoj

Details for address 1EzwoHtiXB4iFwedPr49iywjZn2nnekhoj

    First seen: Sun Mar 11 11:11:41 CDT 2012

    Current balance: 2000000

    Satoshis sent: 716369585974

    Satoshis recv: 716371585974

Усовершенствование сценария

Сценарий по умолчанию выводит очень большие числа, которые трудно прочитать. Чтобы отобразить данные в единицах, более простых для восприятия (например, в целых Биткоинах), можно использовать сценарий scriptbc (сценарий № 9 в главе 1). Поддержка аргумента точности позволила бы выводить данные в удобочитаемом формате.

№ 62. Определение изменений в веб-страницах

Иногда, просматривая существующие решения, мы с воодушевлением говорим себе: «Оказывается, это совсем несложно». Слежение за изменениями на веб-сайтах — удивительно простой способ собирать такие воодушевляющие образцы. Сценарий в листинге 7.20, changetrack, автоматизирует эту задачу. Данный сценарий имеет одну интересную особенность: обнаружив изменения на сайте, он не просто выводит уведомление в командной строке, а посылает пользователю новую веб-страницу по электронной почте.

Код

Листинг 7.20. Сценарий changetrack

#!/bin/bash

# changetrack -- проверяет страницу по указанному URL и, если она

#   изменилась с момента последнего посещения, посылает новую страницу

#    по указанному адресу электронной почты.

sendmail=$(which sendmail)

sitearchive="/tmp/changetrack"

tmpchanges="$sitearchive/changes.$$" # Временный файл

fromaddr="[email protected]"

dirperm=755       # чтение+запись+выполнение для владельца каталога

fileperm=644      # чтение+запись для владельца, только чтение для других

trap "$(which rm) -f $tmpchanges" 0 1 15 # Удалить временный файл при выходе.

if [ $# -ne 2 ] ; then

  echo "Usage: $(basename $0) url email" >&2

  echo " tip: to have changes displayed on screen, use email addr '-'" >&2

  exit 1

fi

if [ ! -d $sitearchive ] ; then

  if ! mkdir $sitearchive ; then

    echo "$(basename $0) failed: couldn't create $sitearchive." >&2

    exit 1

  fi

  chmod $dirperm $sitearchive

fi

if [ "$(echo $1 | cut -c1-5)" != "http:" ] ; then

  echo "Please use fully qualified URLs (e.g. start with 'http://')" >&2

  exit 1

fi

fname="$(echo $1 | sed 's/http:\/\///g' | tr '/?&' '...')"

baseurl="$(echo $1 | cut -d/ -f1-3)/"

# Загрузить копию веб-страницы и поместить в файл архива. Обратите

#   внимание, что изменения определяются по чистому содержимому

#   (используется флаг -dump, а не -source), поэтому можно не заниматься

#   парсингом разметки HTML....

lynx -dump "$1" | uniq > $sitearchive/${fname}.new

if [ -f "$sitearchive/$fname" ] ; then

  # Этот сайт просматривался прежде, так что сравним старую и новую

  #   копии с помощью diff.

  diff $sitearchive/$fname $sitearchive/${fname}.new > $tmpchanges

  if [ -s $tmpchanges ] ; then

    echo "Status: Site $1 has changed since our last check."

  else

    echo "Status: No changes for site $1 since last check."

    rm -f $sitearchive/${fname}.new  # Ничего нового...

    exit 0                           # Изменений нет, выйти.

  fi

else

  echo "Status: first visit to $1. Copy archived for future analysis."

  mv $sitearchive/${fname}.new $sitearchive/$fname

  chmod $fileperm $sitearchive/$fname

  exit 0

fi

# Сюда сценарий попадает, когда обнаружены изменения и нужно послать

#   пользователю содержимое файла .new и заменить им старую копию

#   для следующего вызова сценария.

if [ "$2" != "-" ] ; then

  ( echo "Content-type: text/html"

    echo "From: $fromaddr (Web Site Change Tracker)"

    echo "Subject: Web Site $1 Has Changed"

    echo "To: $2"

    echo ""

    lynx -s -dump $1 | \

    sed -e "s|src=\"|SRC=\"$baseurl|gi" \

        -e "s|href=\"|HREF=\"$baseurl|gi" \

        -e "s|$baseurl\/http:|http:|g"

  ) | $sendmail -t

else

  # Вывод различий на экран не кажется хорошим решением.

  #   Сможете предложить что-то получше?

  diff $sitearchive/$fname $sitearchive/${fname}.new

fi

# Обновить сохраненную копию веб-сайта.

mv $sitearchive/${fname}.new $sitearchive/$fname

chmod 755 $sitearchive/$fname

exit 0

Как это работает

Получив URL и адрес электронной почты, этот сценарий извлекает содержимое веб-страницы и сравнивает его с содержимым сайта, сохраненным при предыдущей проверке. Если сайт изменился, новая страница отправляется по электронной почте указанному адресату после небольших изменений, цель которых — обеспечить работоспособность ссылок на изображения и в атрибутах href. Остановимся подробнее на этих изменениях, начиная со строки .

Команда lynx извлекает исходный код веб-страницы , после чего команда sed вносит в него три разных изменения. Во-первых, все фрагменты SRC=" замещаются фрагментами SRC="baseurl/ , чтобы заменить все относительные пути вида SRC="logo.gif" абсолютными путями, включающими доменное имя, и тем самым обеспечить их работоспособность. Для сайта с доменным именем http://www.intuitive.com/ упомянутая выше ссылка примет вид SRC="http://www.intuitive.com/logo.gif". Аналогично изменяются атрибуты href . Затем, чтобы гарантировать целостность всех ссылок, измененных на предыдущих этапах, выполняется третье изменение, в рамках которого из исходного кода HTML удаляются строки baseurl, если они были добавлены по ошибке . Например, ссылка HREF="http://www.intuitive.com/http://www.somewhereelse.com/link" явно недействительная, и ее следует исправить.

Обратите также внимание, что адрес получателя указан в команде echo (echo "To: $2"), а не передается команде sendmail как аргумент. Это простая предохранительная мера: передавая адрес команде sendmail во входном потоке (которая знает, что должна извлечь адрес получателя из потока благодаря флагу -t), мы избавляем себя от необходимости беспокоиться о пользователях, любящих поиграть с такими адресами, как "joe;cat /etc/passwd|mail larry". Этот прием демонстрирует безопасный способ вызова sendmail из сценариев командной оболочки.

Запуск сценария

Данный сценарий требует два параметра: URL сайта (для правильной работы сценария должны использоваться полные адреса URL, начинающиеся с http://) и адрес электронной почты (или список адресов, разделенных запятыми), куда следует послать измененную веб-страницу. Или, если хотите, вместо адреса электронной почты можно просто использовать - (дефис), чтобы только вывести на экран результаты сравнения командой diff.

Результаты

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

Листинг 7.21. Первый запуск сценария changetrack

$ changetrack http://www.intuitive.com/ [email protected]

Status: first visit to http://www.intuitive.com/. Copy archived for future

analysis.

Все последующие проверки сайта http://www.intuitive.com/ будут заканчиваться отправкой копии по электронной почте, только если страница изменится после предыдущего вызова сценария. Это может быть результатом простого исправления единственной опечатки или сложного переоформления всей страницы. С помощью сценария можно следить за изменениями на любых веб-сайтах, но лучше всего, пожалуй, он будет работать с теми, которые обновляются нечасто: если выбрать целью главную страницу BBC News, проверка потребует значительного объема процессорного времени, потому что этот сайт постоянно обновляется.

Если после предыдущего вызова сценария сайт не изменился, при повторном запуске сценарий ничего не выведет и ничего не пошлет указанному адресату:

$ changetrack http://www.intuitive.com/ [email protected]

$

Усовершенствование сценария

Очевидный недостаток текущей версии сценария — он поддерживает только ссылки с префиксом http://. То есть он будет отвергать любые веб-страницы, обслуживаемые по протоколу HTTPS. Чтобы добавить поддержку обоих протоколов, необходимо применить несколько не самых простых регулярных выражений, но в целом это возможно!

Другое усовершенствование, которое сделает сценарий более полезным: добавить аргумент, определяющий степень изменений, чтобы пользователи могли указать, что, если изменилась только одна строка, сценарий не должен считать сайт обновившимся. Подсчет изменившихся строк реализуется передачей вывода diff команде wc -l. (Имейте в виду, что для каждой измененной строки diff обычно выводит три строки.)

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

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

Биткоин = 100 000 000 сатоши. — Примеч. пер.

Назад: Глава 6. Системное администрирование: обслуживание системы
Дальше: Глава 8. Инструменты веб-мастера