Книга: Сценарии командной оболочки. Linux, OS X и Unix. 2-е издание
Назад: Глава 9. Администрирование веб-сервера
Дальше: Глава 11. Сценарии для OS X

Глава 10. Администрирование интернет-сервера

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

№ 73. Исследование журнала access_log веб-сервера Apache

Если вы управляете веб-сервером Apache или похожим на него, где используется обобщенный формат журналирования (Common Log Format), вы сможете быстро выполнить статистический анализ с помощью сценария командной оболочки. В стандартной конфигурации веб-сервер ведет для сайта журналы access_log и error_log (обычно в /var/log, но точный путь зависит от системы). Если вы поддерживаете собственный сервер, вам определенно стоит архивировать эту ценную информацию.

В табл. 10.1 перечислены поля в файле access_log.

Таблица 10.1. Значения полей в файле access_log

Поле

Значение

1

IP-адрес хоста, обратившегося к серверу

2–3

Защитная информация для соединений HTTPS/SSL

4

Дата и часовой пояс данного запроса

5

Метод вызова

6

Запрошенный URL

7

Использованный протокол

8

Код результата

9

Число переданных байтов

10

Ссылающийся домен

11

Строка идентификации браузера

Типичная строка в access_log имеет следующий вид:

65.55.219.126 - - [04/Jul/2016:14:07:23 +0000] "GET /index.rdf HTTP/1.0" 301

310 "-" "msnbot-UDiscovery/2.0b (+http://search.msn.com/msnbot.htm)""

Код результата 301 (поле 8) указывает, что запрос был успешно обработан. Ссылающийся домен (поле 10) определяет URL страницы, которую пользователь просматривал непосредственно перед запросом. Десять лет тому назад в этом поле передавался URL предыдущей страницы; теперь, по соображениям безопасности, в нем обычно указывается "-", как показано в примере.

Количество обращений к сайту можно определить, подсчитав строки в файле журнала, а диапазон дат записей в файле — по первой и последней строкам.

$ wc -l access_log

   7836 access_log

$ head -1 access_log ; tail -1 access_log

69.195.124.69 - - [29/Jun/2016:03:35:37 +0000] ...

65.55.219.126 - - [04/Jul/2016:14:07:23 +0000] ...

Используя эту информацию, сценарий в листинге 10.1 выводит большой объем статистической информации из файла журнала access_log в формате веб-сервера Apache. Предполагается, что сценарии scriptbc и nicenumber, написанные нами в главе 1, находятся в одном из каталогов, перечисленных в переменной окружения PATH.

Код

Листинг 10.1. Сценарий webaccess

  #!/bin/bash

  # webaccess -- анализирует файл журнала access_log в формате веб-сервера

  #   Apache, извлекая полезную и интересную статистическую информацию.

  bytes_in_gb=1048576

  # Измените следующую переменную, чтобы она соответствовала имени хоста

  #   вашего веб-сервера, чтобы отфильтровать запросы, обусловленные

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

  host="intuitive.com"

  if [ $# -eq 0 ] ; then

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

    exit 1

  fi

  if [ ! -r "$1" ] ; then

    echo "Error: log file $1 not found." >&2

    exit 1

  fi

firstdate="$(head -1 "$1" | awk '{print $4}' | sed 's/\[//')"

  lastdate="$(tail -1 "$1" | awk '{print $4}' | sed 's/\[//')"

  echo "Results of analyzing log file $1"

  echo ""

  echo " Start date: $(echo $firstdate|sed 's/:/ at /')"

  echo " End date: $(echo $lastdate|sed 's/:/ at /')"

  hits="$(wc -l < "$1" | sed 's/[^[:digit:]]//g')"

  echo "         Hits: $(nicenumber $hits) (total accesses)"

  pages="$(grep -ivE '(.gif|.jpg|.png)' "$1" | wc -l | sed 's/[^[:digit:]]//g')"

  echo "   Pageviews: $(nicenumber $pages) (hits minus graphics)"

  totalbytes="$(awk '{sum+=$10} END {print sum}' "$1")"

  /bin/echo -n " Transferred: $(nicenumber $totalbytes) bytes "

  if [ $totalbytes -gt $bytes_in_gb ] ; then

    echo "($(scriptbc $totalbytes / $bytes_in_gb) GB)"

  elif [ $totalbytes -gt 1024 ] ; then

    echo "($(scriptbc $totalbytes / 1024) MB)"

  else

    echo ""

  fi

  # Теперь выберем из журнала некоторые полезные данные.

  echo ""

  echo "The 10 most popular pages were:"

  awk '{print $7}' "$1" | grep -ivE '(.gif|.jpg|.png)' | \

     sed 's/\/$//g' | sort | \

     uniq -c | sort -rn | head -10

  echo ""

  echo "The 10 most common referrer URLs were:"

  awk '{print $11}' "$1" | \

    grep -vE "(^\"-\"$|/www.$host|/$host)" | \

    sort | uniq -c | sort -rn | head -10

  echo ""

  exit 0

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

Рассмотрим каждый блок как отдельный небольшой сценарий. Например, первые несколько строк извлекают начальную и конечную дату (переменные firstdate и lastdate) , просто читая четвертое поле в первой и последней строках в файле. Количество посещений определяется подсчетом строк в файле с помощью wc , а количество просмотренных страниц — как разность посещений и запросов файлов изображений (то есть файлов с расширениями .gif, .jpg и .png). Общее количество отправленных байтов определяется как сумма значений десятого поля во всех строках, которая затем обрабатывается сценарием nicenumber для удобочитаемости.

Чтобы выяснить, какие страницы наиболее популярны, сначала из журнала извлекаются запрошенные страницы, и из их числа исключаются все файлы изображений . Далее вызывается команда uniq -c для сортировки и определения числа вхождений каждой уникальной записи. В финале выполняется еще одна сортировка, чтобы страницы с наибольшим количеством вхождений оказались в начале списка. Вся эта процедура выполняется строкой .

Обратите внимание, как попутно выполняется нормализация: команда sed отсекает завершающие символы слеша, чтобы имена, такие как /subdir/ и /subdir, воспринимались как одно и то же.

Аналогично разделу, извлекающему десяток наиболее популярных страниц, раздел извлекает информацию о ссылающихся доменах.

Этот блок извлекает из журнала значение поля 11, отфильтровывает записи, относящиеся к текущему хосту, а также содержащие "-" (значение, передаваемое веб-браузерами, в которых включена блокировка передачи ссылочной информации). Полученные результаты передаются той же последовательности команд — sort|uniq -c|sort -rn|head -10, чтобы получить десяток самых активных ссылающихся доменов.

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

Чтобы запустить этот сценарий, передайте ему единственный аргумент с именем файла журнала Apache (или другого веб-сервера, поддерживающего обобщенный формат журналирования).

Результаты

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

Листинг 10.2. Результаты обработки журнала access_log веб-сервера Apache с помощью webaccess

$ webaccess /web/logs/intuitive/access_log

Results of analyzing log file access_log

  Start date:  01/May/2016 at 07:04:49

    End date:  04/May/2016 at 01:39:04

        Hits:  7,839 (total accesses)

  Pageviews:   2,308 (hits minus graphics)

Transferred:   25,928,872,755 bytes

The 10 most popular pages were:

266

118 /CsharpVulnJson.ova

  92 /favicon.ico

  86 /robots.txt

  57 /software

  53 /css/style.css

  29 /2015/07/01/advanced-afl-usage.html

  24 /opendiagnostics/index.php/OpenDiagnostics_Live_CD

  20 /CsharpVulnSoap.ova

  15 /content/opendiagnostics-live-cd

The 10 most common referrer URLs were:

108 "https://www.vulnhub.com/entry/csharp-vulnjson,134/#"

  33 "http://volatileminds.net/2015/07/01/advanced-afl-usage.html"

  32 "http://volatileminds.net/"

  15 "http://www.volatileminds.net/"

  14 "http://volatileminds.net/2015/06/29/basic-afl-usage.html"

  13 "https://www.google.com/"

  10 "http://livecdlist.com/opendiagnostics-live-cd/"

  10 "http://keywords-monitoring.com/try.php?u=http://volatileminds.net"

   8 "http://www.volatileminds.net/index.php/OpenDiagnostics_Live_CD"

   8 "http://www.volatileminds.net/blog/"

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

Одна из проблем, возникающих при анализе файлов журналов веб-сервера Apache, обусловлена тем, что часто на одну и ту же страницу ссылаются два разных URL; например, /custer/ и /custer/index.html. Блок определения десяти наиболее популярных страниц должен учитывать это. Преобразование, выполняемое командой sed, уже гарантирует, что /custer и /custer/ не будут интерпретироваться как разные URL, но определить имя файла по умолчанию для данного каталога может оказаться сложной задачей (особенно если это имя определяется специальными настройками в конфигурации веб-сервера).

Информацию о десятке самых активных ссылающихся доменов можно сделать еще более полезной, если оставить в ссылающихся адресах URL только базовое имя домена (например, slashdot.org). Сценарий № 74 идет в этом направлении чуть дальше и анализирует дополнительную информацию, доступную в поле ссылающегося домена. В следующий раз, когда весь десяток самых активных ссылающихся доменов будет заполнен ссылками на slashdot.org, вы не сможете оправдаться незнанием!

№ 74. Трафик поисковых систем

Сценарий № 73 предлагает широкий обзор запросов некоторых поисковых систем к вашему сайту, но дальнейший анализ может показать не только какие из этих систем способствуют увеличению потока посетителей, но также какие ключевые слова они вводили в строке поиска. Полученная информация поможет определить, насколько точно ваш сайт индексируется поисковыми системами. Более того, опираясь на полученные данные, вы сможете повысить ранг и релевантность вашего сайта в поисковых системах. Однако, как упоминалось выше, эта дополнительная информация постепенно признается недопустимой разработчиками Apache и веб-браузеров. В листинге 10.3 приводится сценарий командной оболочки, извлекающий ее из журналов Apache.

Код

Листинг 10.3. Сценарий searchinfo

  #!/bin/bash

  # searchinfo -- извлекает и анализирует трафик поисковых систем, указанных

  #   в поле с информацией о ссылающихся доменах, в обобщенном формате

  #   журналирования.

  host="intuitive.com"   # Замените именем своего домена.

  maxmatches=20

  count=0

  temp="/tmp/$(basename $0).$$"

  trap "$(which rm) -f $temp" 0

  if [ $# -eq 0 ] ; then

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

    exit 1

  fi

  if [ ! -r "$1" ] ; then

    echo "Error: can't open file $1 for analysis." >&2

    exit 1

  fi

  for URL in $(awk '{ if (length($11) > 4) { print $11 } }' "$1" | \

    grep -vE "(/www.$host|/$host)" | grep '?')

  do

    searchengine="$(echo $URL | cut -d/ -f3 | rev | cut -d. -f1-2 | rev)"

    args="$(echo $URL | cut -d\? -f2 | tr '&' '\n' | \

      grep -E '(^q=|^sid=|^p=|query=|item=|ask=|name=|topic=)' | \

      sed -e 's/+/ /g' -e 's/%20/ /g' -e 's/"//g' | cut -d= -f2)"

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

      echo "${searchengine}: $args" >> $temp

    else

      # Запрос неизвестного формата, показать всю строку GET...

      echo "${searchengine} $(echo $URL | cut -d\? -f2)" >> $temp

    fi

    count="$(( $count + 1 ))"

  done

  echo "Search engine referrer info extracted from ${1}:"

  sort $temp | uniq -c | sort -rn | head -$maxmatches | sed 's/^/ /g'

  echo ""

  echo Scanned $count entries in log file out of $(wc -l < "$1") total.

  exit 0

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

Главный цикл for в этом сценарии извлекает все записи из файла журнала, имеющие допустимое значение в поле со ссылающимся доменом: строку длиной более четырех символов, не совпадающую с содержимым переменной $host и знаком вопроса (?), указывающим, что пользователь выполнял поиск.

Далее сценарий пытается идентифицировать имя ссылающегося домена и строку поиска, введенную пользователем . Исследования сотен поисковых запросов показывают, что типичные поисковые сайты используют небольшое количество переменных с известными именами. Например, в случае с Yahoo! строка поиска будет содержать переменную со строкой поиска p=шаблон. Google и MSN используют переменную с именем q. Команда grep проверяет присутствие p, q и других распространенных имен поисковых переменных.

Команда sed очищает извлеченные строки поиска, замещая + и %20 пробелами и убирая кавычки, а команда cut возвращает все, что следует за первым знаком «равно». Иными словами, код возвращает только искомую строку, которую ввел пользователь.

Условный блок, следующий сразу за этими строками, проверяет переменную args. Если она ничего не содержит (то есть если запрос имеет неизвестный формат) — использовалась неизвестная нам поисковая система, поэтому выводится весь запрос целиком, а не только искомая строка.

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

Чтобы запустить этот сценарий, просто передайте ему единственный аргумент с именем файла журнала Apache или другого веб-сервера, поддерживающего обобщенный формат журналирования (листинг 10.4).

ПРИМЕЧАНИЕ

Это один из самых медленных сценариев в данной книге, потому что он запускает много подоболочек для выполнения разных задач. Не удивляйтесь, если его работа потребует значительного времени.

Результаты

Листинг 10.4. Результаты обработки журнала access_log веб-сервера Apache с помощью searchinfo

$ searchinfo /web/logs/intuitive/access_log

Search engine referrer info extracted from access_log:

      771

        4 online reputation management akado

        4 Names Hawaiian Flowers

        3 norvegian star

        3 disneyland pirates of the caribbean

        3 disney california adventure

        3 colorado railroad

        3 Cirque Du Soleil Masks

        2 www.baskerballcamp.com

        2 o logo

        2 hawaiian flowers

        2 disneyland pictures pirates of the caribbean

        2 cirque

        2 cirqu

        2 Voil%C3%A0 le %3Cb%3Elogo du Cirque du Soleil%3C%2Fb%3E%21

        2 Tropical Flowers Pictures and Names

        2 Hawaiian Flowers

        2 Hawaii Waterfalls

        2 Downtown Disney Map Anaheim

Scanned 983 entries in log file out of 7839 total.

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

Одним из усовершенствований сценария мог бы стать пропуск URL ссылающихся доменов, которые, вероятнее всего, не являются поисковыми системами. Для этого просто закомментируйте ветку else .

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

for URL in $(awk '{ if (length($11) > 4) { print $11 } }' "$1" | \

  grep $2)

do

  args="$(echo $URL | cut -d\? -f2 | tr '&' '\n' | \

      grep -E '(^q=|^sid=|^p=|query=|item=|ask=|name=|topic=)' | \

      cut -d= -f2)"

  echo $args | sed -e 's/+/ /g' -e 's/"//g' >> $temp

  count="$(( $count + 1 ))"

done

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

№ 75. Исследование журнала error_log веб-сервера Apache

Так же как сценарий № 73 извлекает интересную и полезную статистическую информацию из файла журнала access_log веб-сервера Apache или совместимого с ним, этот сценарий извлекает чрезвычайно важные сведения из файла журнала error_log.

В случае с веб-серверами, которые не разбивают автоматически свои журналы на отдельные компоненты access_log и error_log, иногда есть возможность разделить централизованный журнал на эти составляющие, выполнив фильтрацию по коду результата (содержимому поля 8):

awk '{if (substr($9,0,1) <= "3") { print $0 } }' apache.log > access_log

awk '{if (substr($9,0,1) > "3") { print $0 } }' apache.log > error_log

Коды, начинающиеся с 4 или 5, сообщают об ошибке (коды 400–499 соответствуют ошибкам на стороне клиента, а коды 500–599 — на стороне сервера). Коды, начинающиеся с 2 или 3, сообщают об успешной обработке запроса (коды 200–299 соответствуют успешной обработке запросов, а коды 300–399 — успешной переадресации).

Другие серверы, поддерживающие единый файл журнала и фиксирующие в нем одновременно отчеты об успехе и об ошибках, снабжают записи с информацией об ошибках полем [error]. В этом случае с помощью команды grep '[error]' можно создать аналог журнала error_log, а с помощью команды grep -v '[error]' — аналог журнала access_log.

Независимо от того, создает ли ваш сервер журнал error_log автоматически или вы должны выделить его вручную, отыскав записи со строкой '[error]', структура записей в error_log практически всегда отличается от структуры записей в access_log, включая способ представления даты:

$ head -1 error_log

[Mon Jun 06 08:08:35 2016] [error] [client 54.204.131.75] File does not exist:

/var/www/vhosts/default/htdocs/clientaccesspolicy.xml

В access_log даты указываются в виде компактного значения, занимающего одно поле, без пробелов; в error_log дата занимает пять полей. Кроме того, в отличие от единообразной схемы access_log, в которой позиция поля со словом/строкой в записи четко определяется пробелами, записи в error_log включают содержательные описания ошибок, различающиеся по длине. Исследование одних только описаний показывает удивительное разнообразие, как демонстрируется ниже:

$ awk '{print $9" "$10" "$11" "$12 }' error_log | sort -u

File does not exist:

Invalid error redirection directive:

Premature end of script

execution failure for parameter

premature EOF in parsed

script not found or

malformed header from script

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

Сценарий в листинге 10.5 решает только самые основные проблемы — в частности, отыскивает ошибки File does not exist («Файл не найден») — и просто выводит список всех остальных записей в error_log, которые не относятся к хорошо известным ситуациям.

Код

Листинг 10.5. Сценарий weberrors

  #!/bin/bash

  # weberrors -- Сканирует файл error_log журнала сервера Apache, сообщает

  #   о наиболее важных ошибках и выводит все остальные, неопознанные записи.

  temp="/tmp/$(basename $0).$$"

  # Для надежной работы этого сценария настройте следующие три переменные

  #   в соответствии с вашей конфигурацией.

  htdocs="/usr/local/etc/httpd/htdocs/"

  myhome="/usr/home/taylor/"

  cgibin="/usr/local/etc/httpd/cgi-bin/"

  sedstr="s/^/ /g;s|$htdocs|[htdocs] |;s|$myhome|[homedir] "

  sedstr=$sedstr"|;s|$cgibin|[cgi-bin] |"

  screen="(File does not exist|Invalid error redirect|premature EOF"

  screen=$screen"|Premature end of script|script not found)"

  length=5 # Количество отображаемых записей в каждой категории

  checkfor()

  {

    grep "${2}:" "$1" | awk '{print $NF}' \

      | sort | uniq -c | sort -rn | head -$length | sed "$sedstr" > $temp

    if [ $(wc -l < $temp) -gt 0 ] ; then

      echo ""

      echo "$2 errors:"

      cat $temp

    fi

  }

  trap "$(which rm) -f $temp" 0

  if [ "$1" = "-l" ] ; then

    length=$2; shift 2

  fi

  if [ $# -ne 1 -o ! -r "$1" ] ; then

    echo "Usage: $(basename $0) [-l len] error_log" >&2

    exit 1

  fi

  echo Input file $1 has $(wc -l < "$1") entries.

  start="$(grep -E '\[.*:.*:.*\]' "$1" | head -1 \

    | awk '{print $1" "$2" "$3" "$4" "$5 }')"

  end="$(grep -E '\[.*:.*:.*\]' "$1" | tail -1 \

    | awk '{print $1" "$2" "$3" "$4" "$5 }')"

  /bin/echo -n "Entries from $start to $end"

  echo ""

  ### Проверить типичные и хорошо известные ошибки:

  checkfor "$1" "File does not exist"

  checkfor "$1" "Invalid error redirection directive"

  checkfor "$1" "Premature EOF"

  checkfor "$1" "Script not found or unable to stat"

  checkfor "$1" "Premature end of script headers"

  grep -vE "$screen" "$1" | grep "\[error\]" | grep "\[client " \

    | sed 's/\[error\]/\`/' | cut -d\` -f2 | cut -d\ -f4- \

    | sort | uniq -c | sort -rn | sed 's/^/ /' | head -$length > $temp

  if [ $(wc -l < $temp) -gt 0 ] ; then

    echo ""

    echo "Additional error messages in log file:"

    cat $temp

  fi

  echo ""

  echo "And non-error messages occurring in the log file:"

  grep -vE "$screen" "$1" | grep -v "\[error\]" \

    | sort | uniq -c | sort -rn \

    | sed 's/^/ /' | head -$length

  exit 0

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

Этот сценарий сканирует файл журнала error_log на наличие пяти ошибок, указанных в вызовах функции checkfor, с помощью awk извлекая из каждой записи последнее поле, то есть поле с номером в переменной $NF (которая представляет количество полей в данной записи). Затем передает результат последовательности команд sort | uniq -c | sort -rn , чтобы проще было определить источник ошибок данной категории.

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

Последние несколько строк сценария находят наиболее распространенные ошибки, не относящиеся к предопределенным категориям, но являющиеся стандартными для формата журнала error_log веб-сервера Apache. Команда grep представляет собой часть длинного конвейера.

Затем сценарий находит не обнаруженные ранее наиболее распространенные ошибки, которые не являются стандартными для формата журнала error_log веб-сервера Apache. И снова команда grep составляет часть длинного конвейера.

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

Чтобы запустить этот сценарий, просто передайте ему в единственном аргументе полный путь к файлу журнала error_log в стандартном формате веб-сервера Apache, как показано в листинге 10.6. Если передать ему дополнительный аргумент -l length, он выведет указанное количество совпадений в каждой категории вместо пяти по умолчанию.

Результаты

Листинг 10.6. Результаты обработки журнала error_log веб-сервера Apache с помощью weberrors

$ weberrors error_log

Input file error_log has 768 entries.

Entries from [Mon Jun 05 03:35:34 2017] to [Fri Jun 09 13:22:58 2017]

File does not exist errors:

       94 /var/www/vhosts/default/htdocs/mnews.htm

       36 /var/www/vhosts/default/htdocs/robots.txt

       15 /var/www/vhosts/default/htdocs/index.rdf

       10 /var/www/vhosts/default/htdocs/clientaccesspolicy.xml

        5 /var/www/vhosts/default/htdocs/phpMyAdmin

Script not found or unable to stat errors:

        1 /var/www/vhosts/default/cgi-binphp5

        1 /var/www/vhosts/default/cgi-binphp4

        1 /var/www/vhosts/default/cgi-binphp.cgi

        1 /var/www/vhosts/default/cgi-binphp-cgi

        1 /var/www/vhosts/default/cgi-binphp

Additional error messages in log file:

        1 script '/var/www/vhosts/default/htdocs/wp-trackback.php' not found

or unable to stat

        1 script '/var/www/vhosts/default/htdocs/sprawdza.php' not found or

unable to stat

        1 script '/var/www/vhosts/default/htdocs/phpmyadmintting.php' not

found or unable to stat

And non-error messages occurring in the log file:

        6 /usr/lib64/python2.6/site-packages/mod_python/importer.py:32:

DeprecationWarning: the md5 module is deprecated; use hashlib instead

        6 import md5

        3 [Sun Jun 25 03:35:34 2017] [warn] RSA server certificate CommonName

(CN) `Parallels Panel’ does NOT match server name!?

        1 sh: /usr/local/bin/zip: No such file or directory

        1 sh: /usr/local/bin/unzip: No such file or directory

№ 76. Предотвращение катастрофических последствий с использованием удаленного архива

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

Решение задачи выглядит сложнее, чем есть на самом деле, потому что, как показано в листинге 10.7, «архив» — это всего лишь файл, посылаемый по электронной почте в удаленный почтовый ящик, который может находиться на серверах Yahoo! или Gmail. Список архивируемых файлов хранится в отдельном файле данных и допускает использование шаблонных символов, поддерживаемых командной оболочкой. Имена файлов могут содержать пробелы, что никак не усложняет сценарий, как вы увидите сами.

Код

Листинг 10.7. Сценарий remotebackup

  #!/bin/bash

  # remotebackup -- принимает список файлов и каталогов, создает единый

  #   сжатый архив и отправляет его по электронной почте на удаленный сайт

  #   для сохранения. Может запускаться по ночам для сохранения важных

  #   пользовательских файлов, но не может служить заменой более строгой

  #   системы резервного копирования.

  outfile="/tmp/rb.$$.tgz"

  outfname="backup.$(date +%y%m%d).tgz"

  infile="/tmp/rb.$$.in"

  trap "$(which rm) -f $outfile $infile" 0

  if [ $# -ne 2 -a $# -ne 3 ] ; then

    echo "Usage: $(basename $0) backup-file-list remoteaddr {targetdir}" >&2

    exit 1

  fi

  if [ ! -s "$1" ] ; then

    echo "Error: backup list $1 is empty or missing" >&2

    exit 1

  fi

  # Сканировать записи и создать фиксированный список в файле infile.

  #   В ходе этой операции выполняются экранирование пробелов и подстановка

  #   шаблонных символов в именах файлов, то есть имя файла "this file"

  #   превращается в this\ file, что избавляет от необходимости использовать

  #   кавычки.

  while read entry; do

    echo "$entry" | sed -e 's/ /\\ /g' >> $infile

  done < "$1"

  # Фактическое создание архива, его кодирование и отправка.

  tar czf - $(cat $infile) | \

    uuencode $outfname | \

    mail -s "${3:-Backup archive for $(date)}" "$2"

  echo "Done. $(basename $0) backed up the following files:"

  sed 's/^/ /' $infile

  /bin/echo -n "and mailed them to $2 "

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

    echo "with requested target directory $3"

  else

    echo ""

  fi

  exit 0

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

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

Архиватор tar автоматически сжимает архив, а следующая за ним команда uuencode гарантирует возможность отправки полученного архива по электронной почте без повреждений. Конечный результат заключается в получении электронного письма с закодированным tar-архивом в удаленной системе.

ПРИМЕЧАНИЕ

Программа uuencode кодирует двоичные данные так, что они могут передаваться без повреждений через систему электронной почты. Дополнительную информацию смотрите в странице справочного руководства man uuencode.

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

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

$ cat filelist

*.sh

*.html

Результаты

В листинге 10.8 демонстрируется запуск сценария remotebackup для копирования всех HTML-файлов и файлов сценариев, имеющихся в текущем каталоге, и вывод результатов.

Листинг 10.8. Запуск сценария remotebackup для копирования HTML-файлов и файлов сценариев

$ remotebackup filelist [email protected]

Done. remotebackup backed up the following files:

   *.sh

   *.html

and mailed them to [email protected]

$ cd /web

$ remotebackup backuplist [email protected] mirror

Done. remotebackup backed up the following files:

   ourecopass

and mailed them to [email protected] with requested target directory mirror

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

Прежде всего, если у вас установлена современная версия tar, возможно, она поддерживает чтение списка файлов со стандартного ввода stdin (например, GNU-версия tar поддерживает флаг -T, при наличии которого программа читает список файлов со стандартного ввода). В этом случае сценарий можно сократить, убрав команду cat, передающую список файлов через аргументы командной строки.

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

Листинг 10.9. Сценарий trimmailbox для использования в комплексе со сценарием remotebackup

#!/bin/bash

# trimmailbox -- простой сценарий, гарантирующий сохранность только четырех

#   последних сообщений в почтовом ящике пользователя. Предполагает

#   использование реализации Berkeley Mail (Mailx bkb mail) -- требует

#   модификации для других почтовых систем!

keep=4 # По умолчанию сохраняет только четыре последних сообщения.

totalmsgs="$(echo 'x' | mail | sed -n '2p' | awk '{print $2}')"

if [ $totalmsgs -lt $keep ] ; then

  exit 0    # Ничего делать не надо.

fi

topmsg="$(( $totalmsgs - $keep ))"

mail > /dev/null << EOF

d1-$topmsg

q

EOF

exit 0

Этот короткий сценарий удаляет из почтового ящика все письма, кроме нескольких самых последних ($keep). Очевидно, что, если в роли архивного хранилища используется почтовый ящик Hotmail или Yahoo! Mail, этот сценарий не будет работать и вам придется периодически чистить его вручную.

№ 77. Мониторинг состояния сети

Программа netstat считается одной из самых запутанных утилит администрирования Unix, что очень плохо, потому что в действительности она позволяет получить много полезной информации о пропускной способности и производительности сети. При вызове с флагом -s программа netstat выводит массу информации о каждом сетевом протоколе, поддерживаемом системой, включая TCP, UDP, IPv4/v6, ICMP, IPsec и другие. Большинство из них не поддерживаются в типичной конфигурации. Особый интерес, как правило, вызывает протокол TCP. Этот сценарий анализирует трафик, пересылаемый по протоколу TCP, определяет процент пакетов, потерянных при передаче и выводит предупреждение, если какие-то из значений выходят за рамки допустимого.

Анализ функционирования сети по значениям, накопленным за продолжительный период, определенно полезен, но намного лучше иметь возможность анализировать данные с тенденциями их изменения. Если, к примеру, система регулярно теряет 1,5% пакетов, а в последние три дня этот показатель подскочил до 7,8%, похоже, назревает проблема, которую требуется изучить более детально.

Вот почему сценарий состоит из двух частей. Первая часть, представленная в листинге 10.10, — это короткий сценарий, который, как предполагается, должен запускаться каждые 10–30 минут для записи ключевых статистик в файл журнала. Второй сценарий (листинг 10.11) выполняет анализ файла журнала, сообщает о нормальных параметрах функционирования сети и любых аномалиях или других значениях, постоянно увеличивающихся с течением времени.

ВНИМАНИЕ

Некоторые диалекты Unix не могут выполнять этот код в том виде, в каком он приводится здесь (но мы подтверждаем, что он работает в OS X)! Как оказывается, вывод команды netstat в разных версиях Unix и Linux имеет множество мелких различий (где-то изменены пробелы или пунктуация). Нормализация вывода netstat могла бы сама по себе стать прекрасным сценарием.

Код

Листинг 10.10. Сценарий getstats

  #!/bin/bash

  # getstats -- каждые 'n' минут сохраняет значения, получаемые

  #   с помощью netstat (из crontab).

  logfile="/Users/taylor/.netstatlog" # Измените в соответствии с вашей конфигурацией.

  temp="/tmp/getstats.$$.tmp"

  trap "$(which rm) -f $temp" 0

  if [ ! -e $logfile ] ; then   # Первый запуск?

    touch $logfile

  fi

  ( netstat -s -p tcp > $temp

  # Проверьте свой файл журнала после первого запуска: некоторые версии netstat

  #   выводят несколько строк вместо одной, именно поэтому здесь используется

  #   последовательность "| head -1".

  sent="$(grep 'packets sent' $temp | cut -d\ -f1 | sed \

  's/[^[:digit:]]//g' | head -1)"

  resent="$(grep 'retransmitted' $temp | cut -d\ -f1 | sed \

  's/[^[:digit:]]//g')"

  received="$(grep 'packets received$' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  dupacks="$(grep 'duplicate acks' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  outoforder="$(grep 'out-of-order packets' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  connectreq="$(grep 'connection requests' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  connectacc="$(grep 'connection accepts' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  retmout="$(grep 'retransmit timeouts' $temp | cut -d\ -f1 | \

    sed 's/[^[:digit:]]//g')"

  /bin/echo -n "time=$(date +%s);"

  /bin/echo -n "snt=$sent;re=$resent;rec=$received;dup=$dupacks;"

  /bin/echo -n "oo=$outoforder;creq=$connectreq;cacc=$connectacc;"

  echo "reto=$retmout"

  ) >> $logfile

  exit 0

Второй сценарий (в листинге 10.11) анализирует журнал с накопленными данными netstat.

Листинг 10.11. Сценарий netperf для использования в паре со сценарием getstats

  #!/bin/bash

  # netperf -- анализирует файл журнала с данными о функционировании сети,

  #   полученными с помощью netstat, выявляет важные значения и тенденции.

  log="/Users/taylor/.netstatlog" # Измените в соответствии с вашей конфигурацией.

  stats="/tmp/netperf.stats.$$"

  awktmp="/tmp/netperf.awk.$$"

  trap "$(which rm) -f $awktmp $stats" 0

  if [ ! -r $log ] ; then

    echo "Error: can't read netstat log file $log" >&2

    exit 1

  fi

  # Сначала сообщить основные статистики из последней записи в файле журнала...

  eval $(tail -1 $log) # Превратит все значения в переменные командной оболочки.

  rep="$(scriptbc -p 3 $re/$snt\*100)"

  repn="$(scriptbc -p 4 $re/$snt\*10000 | cut -d. -f1)"

  repn="$(( $repn / 100 ))"

  retop="$(scriptbc -p 3 $reto/$snt\*100)";

  retopn="$(scriptbc -p 4 $reto/$snt\*10000 | cut -d. -f1)"

  retopn="$(( $retopn / 100 ))"

  dupp="$(scriptbc -p 3 $dup/$rec\*100)";

  duppn="$(scriptbc -p 4 $dup/$rec\*10000 | cut -d. -f1)"

  duppn="$(( $duppn / 100 ))"

  oop="$(scriptbc -p 3 $oo/$rec\*100)";

  oopn="$(scriptbc -p 4 $oo/$rec\*10000 | cut -d. -f1)"

  oopn="$(( $oopn / 100 ))"

  echo "Netstat is currently reporting the following:"

  /bin/echo -n " $snt packets sent, with $re retransmits ($rep%) "

  echo "and $reto retransmit timeouts ($retop%)"

  /bin/echo -n " $rec packets received, with $dup dupes ($dupp%)"

  echo " and $oo out of order ($oop%)"

  echo " $creq total connection requests, of which $cacc were accepted"

  echo ""

  ## Теперь определить присутствие существенных проблем,

  ##   о которых следует сообщить.

  if [ $repn -ge 5 ] ; then

    echo "*** Warning: Retransmits of >= 5% indicates a problem "

    echo "(gateway or router flooded?)"

  fi

  if [ $retopn -ge 5 ] ; then

    echo "*** Warning: Transmit timeouts of >= 5% indicates a problem "

    echo "(gateway or router flooded?)"

  fi

  if [ $duppn -ge 5 ] ; then

    echo "*** Warning: Duplicate receives of >= 5% indicates a problem "

    echo "(probably on the other end)"

  fi

  if [ $oopn -ge 5 ] ; then

    echo "*** Warning: Out of orders of >= 5% indicates a problem "

    echo "(busy network or router/gateway flood)"

  fi

  # Теперь проанализировать некоторые тенденции...

  echo "Analyzing trends..."

  while read logline ; do

      eval "$logline"

      rep2="$(scriptbc -p 4 $re / $snt \* 10000 | cut -d. -f1)"

      retop2="$(scriptbc -p 4 $reto / $snt \* 10000 | cut -d. -f1)"

      dupp2="$(scriptbc -p 4 $dup / $rec \* 10000 | cut -d. -f1)"

      oop2="$(scriptbc -p 4 $oo / $rec \* 10000 | cut -d. -f1)"

      echo "$rep2 $retop2 $dupp2 $oop2" >> $stats

  done < $log

  echo ""

  # Теперь вычислить некоторые статистики и сравнить их с текущими значениями.

  cat << "EOF" > $awktmp

      { rep += $1; retop += $2; dupp += $3; oop += $4 }

  END { rep /= 100; retop /= 100; dupp /= 100; oop /= 100;

        print "reps="int(rep/NR) ";retops=" int(retop/NR) \

          ";dupps=" int(dupp/NR) ";oops="int(oop/NR) }

  EOF

  eval $(awk -f $awktmp < $stats)

  if [ $repn -gt $reps ] ; then

    echo "*** Warning: Retransmit rate is currently higher than average."

    echo " (average is $reps% and current is $repn%)"

  fi

  if [ $retopn -gt $retops ] ; then

    echo "*** Warning: Transmit timeouts are currently higher than average."

    echo " (average is $retops% and current is $retopn%)"

  fi

  if [ $duppn -gt $dupps ] ; then

    echo "*** Warning: Duplicate receives are currently higher than average."

    echo " (average is $dupps% and current is $duppn%)"

  fi

  if [ $oopn -gt $oops ] ; then

    echo "*** Warning: Out of orders are currently higher than average."

    echo " (average is $oops% and current is $oopn%)"

  fi

  echo \(Analyzed $(wc -l < $stats) netstat log entries for calculations\)

  exit 0

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

Программа netstat чрезвычайно полезна, но ее вывод может показаться пугающим. В листинге 10.12 показаны только первые его десять строк.

Листинг 10.12. Запуск netstat для получения информации о TCP

$ netstat -s -p tcp | head

tcp:

    51848278 packets sent

        46007627 data packets (3984696233 bytes)

        16916 data packets (21095873 bytes) retransmitted

        0 resends initiated by MTU discovery

        5539099 ack-only packets (2343 delayed)

        0 URG only packets

        0 window probe packets

        210727 window update packets

        74107 control packets

Первый шаг — извлечь только записи с интересной информацией и сведениями о функционировании сети. В этом заключается главная задача сценария getstats, и он решает ее, сохраняя вывод команды netstat во временном файле $temp и извлекая из него ключевые параметры, такие как общее количество отправленных и полученных пакетов. Строка , например, извлекает количество отправленных пакетов.

Команда sed удаляет любые нечисловые значения, чтобы гарантировать отсутствие любых символов пробелов и табуляции в результатах. Затем все извлеченные значения записываются в файл журнала .netstatlog, в формате var1Name=var1Value; var2Name=var2Value; и так далее. Этот формат позволит потом использовать eval для интерпретации каждой строки в .netstatlog и сохранить все прочитанные значения в переменных командной оболочки:

time=1063984800;snt=3872;re=24;rec=5065;dup=306;oo=215;creq=46;cacc=17;reto=170

Сценарий выполняет анализ содержимого файла .netstatlog, выводит последние значения параметров функционирования сети и сообщает о любых аномалиях и других значениях, неуклонно увеличивающихся с течением времени. Сценарий netperf вычисляет текущий процент повторно отправляемых пакетов, деля их количество на общее число отправленных пакетов и умножая результат на 100. Целочисленная версия процента повторно отправляемых пакетов получается делением количества повторно отправленных пакетов на общее количество отправленных пакетов, умножением на 10 000 и делением на 100 .

Как видите, имена переменных в сценарии начинаются с сокращений, полученных из наименований значений, возвращаемых программой netstat и сохраняемых в .netstatlog в конце сценария getstats . К таким сокращениям относятся: snt, re, rec, dup, oo, creq, cacc и reto. В сценарии netperf к этим сокращениям добавляется окончание p, чтобы получить имена переменных, представляющих вещественные значения процентов от общего числа отправленных и полученных пакетов. Окончание pn добавляется к сокращениям, чтобы получить имена переменных, представляющих целочисленные версии процентов от общего числа отправленных и полученных пакетов. В сценарии netperf окончание ps обозначает переменную, представляющую усредненный процент, которая используется на финальной стадии вычислений.

Цикл while перебирает записи в файле .netstatlog, вычисляет четыре ключевых перцентиля (re, retr, dup и oo, которые представляют количество повторно отправленных пакетов, превышений таймаута при отправке, дубликатов и внеочередных (срочных) пакетов соответственно). Все это записывается во временный файл $stats, затем сценарий awk суммирует каждую колонку в $stats и вычисляет средние значения, деля суммы на количество записей в файле (NR).

Команда eval в строке связывает все вместе. Комплект статистик ($stats), полученных циклом while, передается команде awk, которая использует сценарий в файле $awktmp для вывода последовательностей variable=value. Эти последовательности variable=value затем внедряются в командную оболочку инструкцией eval, в результате чего создаются переменные reps, retops, dupps и oops, представляющие среднее количество повторно отправленных пакетов, среднее количество таймаутов при повторной передаче, среднее количество пакетов-дубликатов и среднее количество внеочередных (срочных) пакетов соответственно. Затем текущие процентные значения можно сравнивать с этими усредненными величинами, чтобы выявлять настораживающие тенденции.

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

Для успешной работы сценарию netperf необходима информация в файле .netstatlog. Эта информация генерируется заданием crontab, автоматически вызывающим getstats с некоторой частотой. В современной системе OS X, Unix или Linux можно добавить в crontab следующую запись, изменив путь к сценарию, чтобы он соответствовал его местонахождению в вашей системе, естественно:

*/15 * * * * /home/taylor/bin/getstats

Она создает новую запись в файле журнала каждые 15 минут. Чтобы гарантировать определенные права доступа к файлу, лучше всего создать пустой файл вручную перед первым запуском getstats:

$ sudo touch /Users/taylor/.netstatlog

$ sudo chmod a+rw /Users/taylor/.netstatlog

Теперь программа getstats будет благополучно пыхтеть над исторической картиной работы сети в вашей системе. Для анализа файла журнала запустите сценарий netperf без аргументов.

Результаты

Для начала давайте рассмотрим содержимое файла .netstatlog, показанное в листинге 10.13.

Листинг 10.13. Последние три строки в файле .netstatlog, записанные заданием crontab, вызывающим сценарий getstats через регулярные интервалы

$ tail -3 /Users/taylor/.netstatlog

time=1063981801;snt=14386;re=24;rec=15700;dup=444;oo=555;creq=563;cacc=17;reto=158

time=1063982400;snt=17236;re=24;rec=20008;dup=454;oo=848;creq=570;cacc=17;reto=158

time=1063983000;snt=20364;re=24;rec=25022;dup=589;oo=1181;creq=582;cacc=17;reto=158

Выглядит хорошо. В листинге 10.14 приводятся результаты запуска сценария netperf.

Листинг 10.14. Запуск сценария netperf для анализа файла .netstatlog

$ netperf

Netstat is currently reporting the following:

  52170128 packets sent, with 16927 retransmits (0%) and 2722 retransmit timeouts (0%)

  20290926 packets received, with 129910 dupes (.600%) and 18064 out of order (0%)

   39841 total connection requests, of which 123 were accepted

Analyzing trends...

(Analyzed 6 netstat log entries for calculations)

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

Вы наверняка заметили, что вместо удобочитаемого формата представления дат сценарий getstats сохраняет даты в файле .netstatlog в виде количества секунд, истекших с начала эпохи, то есть с 1 января 1970 года. Например, 1 063 983 000 секунд означают день в конце сентября 2003 года. Использование такого формата упрощает расширение этого сценария, давая возможность вычислять время, прошедшее между записями.

№ 78. Изменение приоритета процесса по его имени

В практике администрирования часто возникают ситуации, когда полезно изменить приоритет задачи, например: отдать серверу чата только «холостые» циклы системы, понизить приоритет MP3-плеера, не являющегося важным приложением, или процесса, выполняющего загрузку файла, острая необходимость в котором отпала, или, напротив, увеличить приоритет монитора CPU. Изменить приоритет процесса можно командой renice; однако она требует передать ей числовой идентификатор процесса, что вызывает дополнительные трудности. Намного более удобный подход реализован в сценарии (листинг 10.15), который по имени процесса определяет его числовой идентификатор и автоматически изменяет приоритет указанного приложения.

Код

Листинг 10.15. Сценарий renicename

#!/bin/bash

# renicename -- изменяет приоритет задания по указанному имени.

user=""; tty=""; showpid=0; niceval="+1"   # Инициализация

while getopts "n:u:t:p" opt; do

  case $opt in

    n ) niceval="$OPTARG"; ;;

    u ) if [ ! -z "$tty" ] ; then

          echo "$0: error: -u and -t are mutually exclusive." >&2

          exit 1

        fi

        user=$OPTARG ;;

    t ) if [ ! -z "$user" ] ; then

          echo "$0: error: -u and -t are mutually exclusive." >&2

          exit 1

        fi

        tty=$OPTARG ;;

    p ) showpid=1; ;;

    ? ) echo "Usage: $0 [-n niceval] [-u user|-t tty] [-p] pattern" >&2

        echo "Default niceval change is \"$niceval\" (plus is lower" >&2

        echo "priority, minus is higher, but only root can go below 0)" >&2

        exit 1

  esac

done

shift $(($OPTIND - 1)) # Употребить все проанализированные аргументы.

if [ $# -eq 0 ] ; then

  echo "Usage: $0 [-n niceval] [-u user|-t tty] [-p] pattern" >&2

  exit 1

fi

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

  pid=$(ps cu -t $tty | awk "/ $1/ { print \\$2 }")

elif [ ! -z "$user" ] ; then

  pid=$(ps cu -U $user | awk "/ $1/ { print \\$2 }")

else

  pid=$(ps cu -U ${USER:-LOGNAME} | awk "/ $1/ { print \$2 }")

fi

if [ -z "$pid" ] ; then

  echo "$0: no processes match pattern $1" >&2

  exit 1

elif [ ! -z "$(echo $pid | grep ' ')" ] ; then

  echo "$0: more than one process matches pattern ${1}:"

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

    runme="ps cu -t $tty"

  elif [ ! -z "$user" ] ; then

    runme="ps cu -U $user"

  else

    runme="ps cu -U ${USER:-LOGNAME}"

  fi

  eval $runme | \

      awk "/ $1/ { printf \" user %-8.8s pid %-6.6s job %s\n\", \

      \$1,\$2,\$11 }"

  echo "Use -u user or -t tty to narrow down your selection criteria."

elif [ $showpid -eq 1 ] ; then

  echo $pid

else

  # Все готово. Изменить приоритет!

  /bin/echo -n "Renicing job \""

  /bin/echo -n $(ps cp $pid | sed 's/ [ ]*/ /g' | tail -1 | cut -d\ -f6-)

  echo "\" ($pid)"

  renice $niceval $pid

fi

exit 0

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

Часть кода заимствована из сценария № 47 в главе 6, который аналогично выполняет преобразование имени процесса в его числовой идентификатор, но тот сценарий останавливает задания, а не изменяет их приоритет.

В данной ситуации было бы нежелательно по ошибке изменить приоритет сразу нескольких процессов, соответствующих указанному имени (представьте, например, команду renicename -n 10 "*"), поэтому сценарий завершается с сообщением об ошибке, если обнаруживает несколько совпадений. В противном случае он выполняет указанные изменения, предоставляя программе renice самой сообщить о любых возможных ошибках.

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

Этот сценарий поддерживает несколько параметров: -n val позволяет указать желаемое значение приоритета. По умолчанию оно задается как niceval=1. Флаг -u user позволяет ограничить совпадения только процессами, принадлежащими определенному пользователю, а флаг -t tty обеспечивает аналогичную фильтрацию по имени терминала. Чтобы только увидеть идентификатор найденного процесса без фактического изменения приоритета приложения, используйте флаг -p. В дополнение к одному или нескольким флагам, сценарий renicename требует указать шаблон поиска, с которым будут сравниваться имена процессов, действующих в системе.

Результаты

В листинге 10.16 показано, что получается, если обнаруживается совпадение с несколькими процессами.

Листинг 10.16. Запуск сценария renicename с именем процесса, которому соответствует несколько идентификаторов

$ renicename "vi"

renicename: more than one process matches pattern vi:

user taylor pid 6584 job vi

user taylor pid 10949 job vi

Use -u user or -t tty to narrow down your selection criteria.

Мы остановили один из этих процессов и снова запустили ту же команду.

$ renicename "vi"

Renicing job "vi" (6584)

Мы можем убедиться, что сценарий выполнил свою задачу и приоритет процесса vi изменился, если вызвать команду ps с флагом -l и идентификатором процесса, как показано в листинге 10.17.

Листинг 10.17. Подтверждение изменения приоритета процесса

$ ps –l 6584

UID  PID PPID    F CPU PRI NI       SZ  RSS WCHAN  S  ADDR TTY             TIME CMD

501 6584 1193 4006   0  30 1 2453832 1732  - SN+ 0 ttys000 0:00.01 vi wasting.time

Этот чрезмерно широкий формат вывода команды ps читать очень неудобно, но обратите внимание на поле 7 — NI — в котором для данного процесса указано значение 1 . Проверьте любые другие запущенные процессы, и вы увидите, что они имеют стандартный приоритет 0.

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

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

Листинг 10.18. Сценарий watch_and_nice

#!/bin/bash

# watch_and_nice -- проверяет указанный процесс по имени и уменьшает

#   его приоритет до желаемого уровня, если необходимо.

if [ $# -ne 2 ] ; then

  echo "Usage: $(basename $0) desirednice jobname" >&2

  exit 1

fi

pid="$(renicename -p "$2")"

if [ "$pid" == "" ] ; then

  echo "No process found for $2"

  exit 1

fi

if [ ! -z "$(echo $pid | sed 's/[0-9]*//g')" ] ; then

  echo "Failed to make a unique match in the process table for $2" >&2

  exit 1

fi

currentnice="$(ps -lp $pid | tail -1 | awk '{print $6}')"

if [ $1 -gt $currentnice ] ; then

  echo "Adjusting priority of $2 to $1"

  renice $1 $pid

fi

exit 0

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

Назад: Глава 9. Администрирование веб-сервера
Дальше: Глава 11. Сценарии для OS X