Журнал LinuxFormat - перейти на главную

LXF149:tut5

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

Команды: GNU/Linux и смекалка

Тихон Тарнавский вознамерился заделать линуксоидов еще и полиглотами, благо родная ОС и тут приходит на помощь.


Как у вас с иностранными языками? Изучение языков или просто чтение иноязычных текстов не всем дается легко; у многих оно вызывает неприязнь и скуку даже после длительного опыта. Особенно неприятна работа со словарем. Казалось бы, существующие компьютерные программы-словари могут решить эту проблему. Но на практике они решают ее редко. Почему? В больших словарях, которых хватает на все случаи жизни, переизбыток информации – он утомляет. Помните, как в школе или институте на уроках иностранного языка, или на курсах по его изучению вас заставляли составлять словарные тетрадки, записывая туда слова и их переводы? Это делается как раз для того, чтобы избежать перегрузки лишней информацией из словарей: позже вы сверяетесь со своей тетрадкой и видите в ней, во-первых, только те слова, которые лично вам не запомнились; а во-вторых, только те их значения, которые вы уже встречали в текстах. Очень полезно. Но очень скучно. А что мы делаем со скучной работой? Конечно! Перекладываем ее выполнение на компьютер.

Работа со словарем

Начнем с самого словаря. Здесь нам понадобятся: программа работы со словарем (конечно же, консольная) и собственно сам словарь. Программа эта называется dict (от слова dictionary – словарь) и состоит из двух частей – сервера и клиента. Следовательно, нужно установить два пакета: dict и dictd; первый содержит клиентскую часть, второй – серверную (буква d в конце означает daemon; так в Unix-системах называют программу, работающую в фоновом режиме, отвечая на запросы других программ). Теперь к словарю. Я буду объяснять на примере англо-русского словаря Мюллера, который распространяется под лицензией GPL. Обычно пакет с этим словарем в формате dict называется mueller7-dict или dict-mueller7.

Как и раньше, нужные команды мы будем записывать в файл-скрипт; а вызов этого скрипта вы назначите на удобную вам комбинацию клавиш. Перед вызовом скрипта нужно будет выбрать слово для перевода. А как удобнее всего выбрать слово при чтении текста? Выделить его мышью. Как известно, в X Window System существует несколько отдельных буферов обмена, и в один из них текст как раз попадает сразу после его выделения мышью. Осталось этот текст оттуда достать. Поможет нам в этом специальная программа, предназначенная для работы с иксовыми буферами обмена из командной строки. Называется она xclip (от X clipboard) и устанавливается из одноименного пакета. Опции этой команды весьма просты, а нам из них понадобится всего одна – -o (от слова output – вывод, вывести):

$ xclip -o
free$

Как видите, приглашение командной строки вывелось сразу после выделенного слова: xclip не добавляет символа новой строки после вывода. Сейчас нам это не важно – мы все равно будем использовать подстановку вывода, где конечный перевод строки не имеет значения. Если же вы захотите использовать xclip с разделением строк, просто добавьте команду echo без аргументов. Она, в отличие от xclip, по умолчанию завершает свой вывод переводом строки, даже если этот вывод пуст:

$ xclip -o; echo
free
$

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

$ dict “$(xclip -o)”
1 definition found
From Mueller English-Russian Dictionary [mueller7]:
 free
  [fri:]
  1. _a.
  1) свободный, вольный; находящийся на свободе;
  независимый; to make free use of smth. пользоваться чем-л. без ограничений; широко пользоваться чем-л. - get free - make free - set free - free choice
  2) добровольный, без принуждения
[...]
  11)_фин. free currency необратимая валюта, валюта, не имеющая обеспечения - free labour; to make free with smb. позволять себе вольности, бесцеремонность по отношению к кому-л.; free of за пределами; we’re
  not free of the suburbs yet мы еще не выбрались из пригородов; free pardon to give with a free hand раздавать щедрой рукой; to spend with a free hand швыряться деньгами; to have (to give) a free hand иметь (давать) полную свободу действий
 2. _adv.
  1) свободно; to run free бегать на свободе
  2) бесплатно
 3. _v. освобождать (from, of - от); выпускать на свободу

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

Информации в словаре много; выбор дальнейшего пути очень широк. Создадим самую лаконичную «тетрадку»: «слово – краткий перевод». Другие варианты останутся вам на домашнее задание: например, можно оставлять еще и транскрипцию слова. Или информацию о частях речи и спецтерминах. Или записывать отдельно переводы фраз с этим словом. Кстати, начало для всех этих вариантов будет общим: избавиться от первых пяти строк (включая две пустых), содержащих сообщение об одном найденном определении, название словаря и оригинал слова. Здесь нам поможет команда tail: если приписать к количеству строк знак плюс, она оставит не заданное число строк от конца, а все строки, начиная с заданного номера:

$ dict “$(xclip -o)” | tail -n +6
 [fri:]
  1. _a.
   1) свободный, вольный; находящийся на свободе;
   независимый; to make free use of smth. пользоваться чем-л. без ограничений; широко пользоваться чем-л. - get free - make free - set free - free choice
[...]
  3. _v. освобождать (from, of - от); выпускать на свободу
(thumbnail)
Рис. 1. Вывод команды dicht для слова с единственным значением.

Почему бы не удалить еще и шестую строку с транскрипцией? Потому что для слов с всего одним значением она же содержит и перевод (рис. 1).

Как вы, думаю, помните, большинство утилит командной строки обрабатывают текст построчно. А в словарных статьях, как мы уже убедились, переводы строки применяются для форматирования текста. Поэтому для начала разделим текст на строки так, как нам будет удобно в его дальнейшей обработке. Лучше всего будет сначала расставить во всех местах, где нужна разбивка на строки, специальные метки (в качестве метки сгодится любой символ, которого нет в словарной статье); а затем заменить переводы строки пробелами, а метки – переводами строки.

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

Но тут встает вопрос: как объяснить программе, которая будет производить замену, такие понятия, как «любая цифра» или «первый значимый символ в строке»?

Регулярно выражаясь

Для этого во многих программах обработки текста (включая, к примеру, уже знакомую нам grep) есть специальный механизм, называемый регулярными выражениями. Эти выражения позволяют очень просто задавать такие вещи, как, к примеру, «символ из заданного набора», или «любой символ», или «произвольное число повторений», или «начало строки». Но grep нам тут не подойдет, поскольку сейчас нам нужно не выбирать символы из строк или строки из потока, а заменять одни символы другими. Поэтому обратимся к более широкопрофильной программе автоматической обработки текста.

Она называется sed или “stream editor” (потоковый редактор). В странице справки (man) sed описывается как «потоковый редактор для фильтрации и преобразования текста». Часто sed называют еще «неинтерактивным редактором», подразумевая, что команды тут выполняются не в процессе их ввода человеком (как при «интерактивном» редактировании в привычном текстовом редакторе), а после, в автоматическом режиме. Язык sed очень лаконичен: каждое имя команды в нем состоит всего из одной буквы (после которой могут идти параметры команды). Сейчас нам будет нужна только одна из них – команда поиска-замены текста. Ищет она, понятно, по регулярным выражениям и выглядит так:

s/регулярное-выражение/текст-замены/

Вместо слэша разделителем может выступать любой другой символ – главное, чтобы все три разделителя в одной команде были одинаковыми. После третьего разделителя могут быть добавлены управляющие флаги. Упомяну два самых простых из них и, пожалуй, самых популярных. Флаг -g (от global replacement – глобальная замена) заставит sed заменить все совпадения с указанным выражением; по умолчанию же заменяется только первое совпадение. Флаг -i (от case insensitive) делает поиск нечувствительным к регистру букв.

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

Начнем с расстановки будущих разрывов строк. Обозначим их, скажем, обратным апострофом. Каждая строка должна содержать одно значение слова. В исходной статье значения либо отмечаются, как уже говорилось, числом в начале строки с точкой или скобкой, либо разделяются точкой с запятой. Точку с запятой можно будет заменить на перевод строки одновременно с апострофом командой tr. А вот числами нам сейчас и нужно заняться. Начало строки обозначается в регулярных выражениях символом '^'. Затем идет какое-то количество пробелов. Какое, не столь важно, да и не стоит привязываться к конкретному количеству – вдруг в других словарных статьях оно будет другим. Поэтому воспользуемся шаблоном произвольного количества повторений. Звездочка означает, что идущий перед ней символ может повторяться любое количество раз (начиная от нуля, то есть его может и вообще не быть). Значит, «любое количество пробелов в начале строки» обозначится так: '^ *'.

Далее идет число, то есть последовательность цифр. «Последовательность» можно обозначить той же звездочкой; осталось понять, как записать «цифру». Воспользуемся записью «символ из заданного набора»: набор символов задается в квадратных скобках, причем можно указывать диапазоны, записывая их начало и конец через дефис. Значит, «любая цифра» будет выглядеть как '[0-9]', а все вместе запишется так: '^ *[0-9]*'. После цифры идет точка либо скобка; это снова «символы из набора», только набор на сей раз состоит всего из двух значений: '[.)]'. Итак:

dict “$(xclip -o)” | tail -n +6 | sed 's/^ *[0-9]*[.)]/`/'

Но цифра, в отличие от пробела, хотя бы одна там быть обязана. Можно предварительно вставить шаблон '[0-9]' еще раз. А можно воспользоваться так называемыми расширенными регулярными выражениями: в них, в отличие от базовых, есть символ '+', который отличается от звездочки только тем, что хотя бы один раз указанный перед ним шаблон должен встречаться обязательно. Расширенные регулярные выражения в sed включаются опцией -r:

dict “$(xclip -o)” | tail -n +6 | sed -r 's/^ *[0-9]+[.)]/`/'

«Продолжаем разговор»

Переходим к разбиению на строки. Заменяем существующие переводы строки пробелами, а обратные апострофы и точки с запятой – переводами строки:

tr '\n' ' ' | tr '`;' '\n\n'

Поскольку отступы в тексте статей сделаны пробелами, нужно еще удалить лишние, то есть заменить каждую последовательность из нескольких пробелов одним. Сделать это можно все той же программой tr. У нее есть опция -s, длинный вариант которой звучит как “squeeze-repeats” (сжать повторы):

tr -s ' '

Убираем транскрипцию. Для этого возвращаемся к sed. Транскрипция заключена в квадратные скобки и расположена в начале строки (после какого-то количества пробелов). Начало строки и любое количество пробелов мы искать уже умеем: '^ *'. Квадратные скобки используются для обозначения набора символов. Как же обозначить сами эти скобки? В регулярных выражениях есть на этот счет общее правило: чтобы специальный символ потерял свое специальное значение и читался просто как символ, перед ним нужно поставить обратный слэш: '\['. Интересно, что перед закрывающей скобкой слэш не нужен: она ведь имеет специальное значение только в паре с открывающей. Впрочем, если вы его поставите, тоже не будет никакой беды – обратный слэш перед обычным символом игнорируется; одним словом, его можно использовать и в тех случаях, когда вы не уверены в том, должен ли следующий символ трактоваться как специальный. Хотя в общем это правило верно только для расширенных регулярных выражений: в базовые с помощью слэша можно вводить «расширенные» элементы. Например, sed 's/[0-9]\+//' – это то же самое, что и sed -r 's/[0-9]+//'.

Между скобок могут встретиться не только буквы латинского алфавита, но и многочисленные символы, используемые только в транскрипции. С другой стороны – кроме как в транскрипции, квадратные скобки в словарных статьях не встречаются. Поэтому можно смело искать там последовательность любых символов. А «любой символ» в регулярных выражениях обозначается точкой. Все вместе получается '^ *\[.+]'. Такую последовательность нам нужно удалить, то есть заменить пустой строкой:

sed -r 's/^ *\[.+]//'

Далее уберем служебные слова, обозначающие части речи, разделы терминологии и прочие пояснения. Они начинаются с подчеркивания и заканчиваются точкой. Буквы, как и цифры, можно задать началом и концом последовательности. А остальное нам уже знакомо. Только не забудем о пробелах вокруг слова; и раз удалять слово будем вместе с пробелами, то и заменим его не пустой строкой, а пробелом: 's/ *_[a-zа-я]+\. */ /'. Естественно, одним вызовом sed можно выполнить сразу несколько команд. Для этого их нужно оформить точно так же, как и команды shell: либо написать каждую в своей строке, либо разделить точкой с запятой:

sed -r 's/^ *\[.+]//; s/ *_[a-zа-я]+\. */ /’

После разбиения на строки могли остаться пробелы в начале и в конце строки. Хорошо бы их удалить. Шаблон для начала строки нам уже известен, а конец строки обозначается символом ‘$’: ‘s/^ *//; s/ *$//’. Объединяем с предыдущими двумя командами:

sed -r ‘s/^ *\[.+]//; s/ *_[a-zа-я]+\. */ /; s/^ *//; s/ *$//’

И все вместе, чтобы не забыть, что у нас уже накопилось:

dict “$(xclip -o)” | tail -n +6 | sed -r 's/^ *[0-9]+[.)]/`/' | \
 tr '\n' ' ' | tr '`;' '\n\n' | tr -s ' ' | \
 sed -r 's/^ *\[.+]//; s/ *_[a-zа-я]+\. */ /; s/^ *//; s/ *$//’

Наконец, удаляем лишние строки с помощью grep -v. Во-первых, после всех предыдущих замен могли остаться пустые строки ('^$'). Во-вторых, нужно удалить строки с примерами фраз, то есть те, которые начинаются с английского слова, а не с русского; для отсеивания достаточно будет одной латинской буквы ('^[a-Z]'). И, наконец, в-третьих, различные значения одной фразы могут быть записаны списком, промаркированным строчными русскими буквами со скобкой ('^[а-я]) '; см. к примеру слово look). Можно это записать тремя отдельными командами:

grep -v '^$' | grep -v '^[a-Z]' | grep -v '^[а-я]) '

Но команда grep, как и sed, понимает и расширенные регулярные выражения. А в них есть запись для перечисления нескольких вариантов на выбор, обозначаемая вертикальной чертой, и включаются расширенные регулярные выражения опцией -E. Опция эта настолько популярна, что для grep -E есть даже специальный синоним – egrep. Так что объединим наши три команды в одну:

egrep -v '^$|^[a-Z]|^[а-я]\) '

Здесь, правда, пришлось «экранировать» слэшем круглую скобку, поскольку такие скобки в расширенных выражениях используются для группировки. Кстати, благодаря группировке мы можем вынести за скобки начало строки в получившемся выражении:

(thumbnail)
Рис. 2. Отфильтрованные строки значений слова.
egrep -v '^($|[a-Z]|[а-я]\) )'

Эти три варианта равнозначны. Выбирайте из них тот, который вам кажется наиболее читабельным, добавляйте в конвейер – и можно проверять, что получилось (рис. 2).

«А затем еще немного, и еще немного...»

Уже неплохо, но местами длинновато. Давайте уберем английские фразы в конце строки после двоеточий или тире, а также пояснения в скобках (которые тоже начинаются с английских слов).

Начнем с первого. Это двоеточие либо дефис, после чего следует строка из латинских букв, пробелов и, возможно, дополнительных двоеточий или дефисов. Очевидно, нужен шаблон «набор символов». Но тут есть одна тонкость: ведь дефисом в наборе символов обозначается диапазон; как же обозначить сам дефис? Запросто: поставим его там, где он не может обозначать диапазон. То есть не между двумя другими символами, а либо после открывающей скобки, либо перед закрывающей: 's/ *[-:][a-Z:-]*$//'.

(thumbnail)
Рис. 3. Отфильтрованные строки значений без пояснений в круглых скобках.
(thumbnail)
Рис. 4. Так будут выглядят статьи слов-омонимов.

Переходим ко второму. Тут все достаточно просто: фраза в круглых скобках в конце строки, начинающаяся с латинской буквы; то есть 's/([a-Z].*)$//'. Добавляем в наш конвейер и еще раз пробуем (рис. 3). Уже то, что нужно; на одном слове “free”. Теперь слегка доведем под другие слова. К примеру, посмотрите, как выглядят статьи слов-омонимов и что из них получится в нынешнем варианте (рис. 4). Совсем не то, что хотелось бы. А исправляется все нехитро: в самом начале, при разбиении на строки нужно добавить вариант для римских цифр. Вот такой: 's/^ *_[IVX]+/`/'.

Заодно уберем новые виды скобок. Для этого немного доработаем предыдущий вариант, чтобы удалялись любые скобки, в которых есть английские слова – то есть латинские буквы. Но тут снова тонкость. Вариант 's/(.*[a-Z].*) *//' нам не подойдет. Например, представьте такую строку: «значение (с пояснением), сходное значение (а тут example)». Надо бы удалить только последнюю пару скобок. А по такому шаблону удалится все от первой открывающей скобки до последней закрывающей. Поэтому запишем, что внутри не должно быть закрывающих скобок: 's/([^)]*[a-Z][^)]*) *//'. Теперь получится то, что нужно:

$ dict “$(xclip -o)” | tail -n +6 |
 sed -r 's/^ *[0-9]+[.)]/`/; s/^ *_[IVX]+/`/' |
 tr '\n' ' ' | tr '`;' '\n\n' | tr -s ' ' |
 sed -r 's/^ *\[.+]//; s/ *_[a-zа-я]+\. */ /; s/^ *//; s/ *$//' |
 sed 's/ *[-:][a-Z :-]*$//; s/([^)]*[a-Z][^)]*) *//' |
 egrep -v ‘^$|^[a-Z]|^[а-я]\) ‘
от see I
пословица, афоризм
пила
пилить(ся)
распиливать
сильно жестикулировать


Точнее, почти то, что нужно: в последнюю строку попал перевод одной из фраз, а не слова. Он был отделен точкой с запятой от предыдущего перевода той же фразы. Логично предположить, что коли пошли фразы, перевода самого слова уже не будет. То есть, начиная от латинской буквы после точки с запятой, нужно удалить все до конца строки. Но делать это нужно раньше: после расстановки переносов строк на месте цифр, но до их расстановки на месте точек с запятой. Другими словами, команду tr '`;' '\n\n' следует разбить на две и нужную замену вставить между ними:

$ dict “$(xclip -o)” | tail -n +6 |
 sed -r 's/^ *[0-9]+[.)]/`/; s/^ *_[IVX]+/`/' | tr '\n' ' ' | tr '`' '\n' |
 sed -r 's/; *[a-Z]+.*$//' | tr ';' '\n' | tr -s ' ' |
 sed -r 's/^ *\[.+]//; s/ *_[a-zа-я]+\. */ /; s/^ *//; s/ *$//' |
 sed 's/ *[-:][a-Z :-]*$//; s/([^)]*[a-Z][^)]*) *//' |
 egrep -v ‘^$|^[a-Z]|^[а-я]\) ‘
от see I
пословица, афоризм
пила
пилить(ся)
распиливать

В целом, полученное может выглядеть для вас немного страшновато. Но попробуйте «прочитать про себя», в уме проговаривая действие и назначение каждой команды по очереди – и все станет понятнее. Если где-то не станет, подглядывайте в текст статьи.

Все это лишь подготовительная часть работ, хотя и самая сложная. В следующий раз мы продолжим начатое и займемся «графическим интерфейсом»: отобразим в окне zenity полученный список с «галочками» для выбора; запишем выбранные варианты в «тетрадку»; а также поищем в «тетрадке» заданное слово и покажем в другом окне варианты переводов.

Персональные инструменты
купить
подписаться
Яндекс.Метрика