Unix way: "Гарики", они и в Linux'е "гарики".
Автор: Вадим Хохлов


Введение

Один из принципов Unix состоит в том, что программа должна выполнять только одну функцию, но очень хорошо. Программы также должны уметь взаимодействовать друг с другом[1]. Имея специальный "клей", можно объединять различные программы в более сложные конструкции. Манипулируя таким образом программами можно решать различные задачи. Один из таких примеров и рассматривается в статье. Надеюсь, она будет полезна программистам, начинающим изучать программирование для Unix.

Постановка задачи

На сайте http://nrsoft.chat.ru располагается программка, которая позволяет просматривать гарики Игоря Губермана. Программа работает под Windows, а мне хотелось иметь что-то подобное для Linux. Таким образом, задача ставится в следующем виде: необходимо разработать набор bash-скриптов для выбора различных интересных высказываний из какой-нибудь базы. Должна существовать возможность вставить выбранное высказывание, например, в качестве подписи в письмо или показать на экране (в консоле или в X Window)

Структуры данных

Высказывания будут хранится в обычных текстовых файлах [2]. Это позволит, во-первых, формировать их с помощью несложных скриптов из файлов, найденных в Internet, а, во-вторых, использовать стандартные утилиты для обработки текста: grep, tail, head.

Структура файла следующая:

Каждое высказывание начинается со строки, содержащей звездочку и его номер. Вот фрагмент файла с гариками:

                Игорь Губерман
                243
                4
                * 0
                Держа самих себя на мушке,
                в чем наша слава, честь и сила,
                Мы держим подлых у кормушки,
                А слабоумных у кормила.
                * 1
                Не на годы, а на времена
                Оскудела моя сторона,
                Своих лучших сортов семена
                В мерзлоту раскидала страна.
        

Выборка фразы из файла

Алгоритм выборки произвольной фразы из файла состоит в следующем:

  1. Прочитать из файла автора высказываний.
  2. Определить количество высказываний в файле.
  3. Определить количество строк в фразе.
  4. Сгенерировать случайный номер высказывания.
  5. Выбрать нужную фразу из файла.

Для извлечения первых трех строк из файла мы будем использовать комбинацию команд head и tail. Первая печатает несколько первых строк файла, а вторая - последних. По умолчанию печатается 10 строк, но можно указать необходимое количество. Например, следующий фрагмент выводит на экран автора высказываний (предполагается, что имя файла с фразами передается скрипту как первый параметр):

head -n 1 $1

Для сохранения результата команды в переменной используется командная подстановка: `` (обратные кавычки) и $(). Результат фрагмента скрипта `команда` заменяется на вывод команды. $(команда) является конструкцией, специфичной для bash. Например, для определения количества строк в фразе используется следующий фрагмент скрипта:

cstr=`head -n 3 $1 | tail -n 1`

Генерация случайных чисел в bash-скриптах выполняется с помощью встроенной переменной $RANDOM. (Убедитесь, что в системе активирован сервис random. Прим.ред.) При каждом обращении к ней генерируется случайное число в диапазоне от 0 до 32767. Если же нам необходим диапазон от 0 до N, то используется остаток от деления $RANDOM на N, который вычисляется с помощью оператора %. Для вычисления арифметических выражений в bash используется конструкция $[ выражение ]. Следовательно, для получения номера высказывания можно использовать следующую запись:

$[ $RANDOM % `head -n 2 $1 | tail -n 1` ]

Выборка строк по шаблону из файла осуществляется с помощью семейства команд grep. Шаблон искомой строки задается с помощью регулярных выражений. Например, следующее выражение:

^\* 34$

соответствует строке

* 34

Символ '^' означает начало строки, '$' - конец. Символ '*' имеет специальное значение, поэтому его необходимо экранировать символом '\'. Символы начала и конца строки в данном случае используются, чтобы исключить строки вида:

aa * 32 bb

Обычно grep выводит строки, совпадающие с шаблоном. Если же мы хотим получить также несколько строк после найденной, то используется параметр -A . В нашем случае интерес представляет не строка с номер высказывания, а сама фраза. Для ее извлечения из результат работы grep используется уже известный подход с tail:

grep -A $cstr "^\* $[ $RANDOM % `head -n 2 $1 | tail -n 1` ]$" $1 |\
                tail -n $cstr
        

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

Таким образом, полный текст скрипта bphrases имеет следующий вид:

#!/bin/bash
if [ $# -ge 1 ];then
    #кол-во строк в фразе
    cstr=`head -n 3 $1 | tail -n 1`
    #фраза
    grep -A $cstr "^\* $[ $RANDOM % `head -n 2 $1 | tail -n 1` ]$" $1 |\
	    tail -n $cstr
    #автор
    echo -e "\t" `head -n 1 $1`
fi    

С помощью оператора if скрипт проверяет, что указан обрабатываемый файл.

Работа с несколькими базами фраз

Для работы с несколькими базами фраз можно написать дополнительный скрипт, который будет случайным образом выбирать файл с высказываниями и передавать его скрипту bphrases. Имена файлов баз перечислены в специальном файле, первая строка которого, содержит число этих файлов. Текст скрипта bphrasesx имеет следующий вид:

#!/bin/bash
# печатает на стандартный выход произвольную фразу из одной или нескольких строк
# и ее автора из произвольного файла
# $1 - имя файла, содержащего список файлов с фразами

if [ $# -ge 1 ];then
    #номер строки с именем файла с фразами. Добавляем 2, т.к. счет с 1 
    #и надо пропустить первую строку, с числом файлов 
    nstr=$[ $RANDOM % `head -n 1 $1` + 2]
    bphrases `head -n $nstr $1 | tail -n 1`
fi        

Написанные скрипты можно использовать различным образом. Например, я настроил kmail для вставки различных фраз в качестве подписи в письма.

Вывод фраз в X Window

Теперь напишем скрипт для X Window, который будет показывать гарики Игоря Губермана и позволит их копировать в clipboard. Аналогом команды cat для X-ов является команда xmessage. Например, команда

xmessage "This is an example of using the xmessage"

выведет простое окно с сообщением (см. на Рис.1):


Рис.1. Окно команды xmessage

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

xmessage -buttons end:2,more:3,clip:4

добавляет три кнопки - end, more, clip. Число после имени кнопки - значение, которое будет возвращать xmessage при нажатии на соответствующую кнопку. Нажатие на кнопку end будет завершать работу скрипта, more - показывать следующий гарик, clip - копировать текст текущего гарика в clipboard.

Для более тонкой настройки можно использовать класс окна. Этот параметр широко используется в X Window и оконными менеджерами. Например, следующая строка:

xmms.workspace: 2

заставляет IceWM запускать xmms на третьем рабочем столе. Большинство программ X Window позволяют задавать класс окна через параметр -name. Узнать класс окна и другие его характеристики можно с помощью команды xprop. Различные параметры приложений можно указывать в файле ~/.Xresources. Для того чтобы изменения, сделанные в этом файле вступили в силу, необходимо выполнить команду:

xrdb -merge $HOME/.Xresources

Задав класс окна gariki для xmessage, укажем шрифт, цвета и тексты на кнопках в ~/.Xresources:

gariki*font: -misc-fixed-*-*-*-*-20-*-*-*-*-*-*-*
gariki*background: lightgreen
gariki*end.label: Хватит
gariki*end.background: red
gariki*more.label: Еще\ хочу...
gariki*more.background: magenta
gariki*clip.label: Копировать
gariki*clip.background: lightblue
gariki*Text.background: yellow    

С этими параметрами окно xmessage будет иметь вид, показанный на Рис.2.


Рис.2. Окно скрипта gariki

При завершении работы xmessage устанавливает код завершения, зависящий от того, какая кнопка была нажата. Переменная $? содержит код завершения последней команды. Она будет обрабатываться следующим образом:

ret=$?
[ $ret -le 2 ] && exit 0    

Строка [ $ret -le 2 ] && exit 0 является более компактным аналогом оператора if. Часть строки до && проверяет, что значение переменной ret не больше 2, а вторая часть выполнится только в том случае, если проверяемое условия истинно.

Работа с clipboard

Для работы с clipboard можно использовать утилиту xclip. Она позволяет показывать содержимое буфера обмена, помещать в него содержимое файла и выполнять ряд других функций. По умолчанию используется режим XA_PRIMARY, т.е. для вставки содержимого clipboard в кое-либо поле ввода достаточно нажать среднюю кнопку мыши[3].

Собираем все вместе

Таким образом, полный текст скрипта gariki имеет следующий вид [4]:

#!/bin/bash
msg=`bphrasesx ${1:-~/texts/lit/Gariki/files}`
while :
do
    xmessage -name gariki -buttons end:2,more:3,clip:4 -default end \
    -center -title "Гарики" "$msg"
    ret=$?
    [ $ret -le 2 ] && exit 0
    [ $ret -eq 4 ] && echo "$msg" | xclip -i 
    [ $ret -eq 3 ] && msg=`bphrasesx ${1:-~/texts/lit/Gariki/files}`
done    

Строка ${1:-~/texts/lit/Gariki/files} указывает, что необходимо взять значение переменной $1, а если она не определена, то ~/texts/lit/Gariki/files. Естественно, Вы должны использовать свое значение по умолчанию. Переменную msg необходимо брать в кавычки для правильной обработки символов перевода строки.

Для удобства я собрал все скрипты а также файлы с гариками и высказываниями Владимира Вишневского и Ежи Леца в одном архиве.

Заключение

Используя лишь стандартные "кирпичики", мы написали полнофункциональное приложение. При этом не пришлось прибегать к тяжелой артиллерии в виде C или C++.

Об авторе

Я работаю программистом и преподаю в Херсонском государственном техническом университете. С Linux знаком с 1999 года. Общаюсь с ним, в основном, дома. Кроме этого, я являюсь разработчиком IceWM Control Center - набора программ (в том числе и скриптов icerrun) для настройки различных параметров IceWM.

Мои хобби - игра в Что?Где?Когда?, аквариум, коты.

Ссылки

[1] Об этих принципах хорошо написано в [1], [2], [3].

[2] О преимуществах текстового представления информации см. в [2], [3].

[3] Правда, это не всегда работает с редакторами KDE.

[4] Не помню, кто сказал, что лучший способ надоесть - рассказать все до конца. Поэтому я намеренно не объяснял некоторые моменты подробно.

Рекомендуемая литература

  1. Unix - универсальная среда программирования. К сожалению, я не помню ее координат.
  2. Керниган Б., Пайк Р. Практика программирования. - СПб.: Невский диалект, 2001. - 381 с.
  3. The Art of Unix Programming.
  4. Соответствующие страницы man и info.
  5. Различные руководства по программированию на bash.

Copyright (c) Septemper 2003, Vadim A. Khohlov


Вернуться на главную страницу