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

LXF146:tut5

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

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

Тихон Тарнавский распространяет рассказ о необычных приемах и навыках

работы с командной строкой на печать текстов.


Работа с текстами: оценка объемов

Раз уж команды, с которыми мы имеем дело, предназначены для работы с текстом, резонно будет хранить в обычных текстовых файлах всю информацию, пригодную для такого хранения. Заодно можно будет сократить использование многочисленных и достаточно неторопливых программ, вроде OpenOffice Writer, Zim, Tomboy и прочая и прочая, и заменить их одним легким и – главное – единообразным (а значит, меньше придется запоминать) средством: текстовым редактором. При этом многие функции (в том числе и такие, которые графическим программам и не снились) мы сможем выполнять все теми же универсальными средствами, которым посвящен этот цикл статей. Возможно, в наш век повсеместного засилья «интуитивных» и якобы удобных интерфейсов это предложение вам покажется диким – в таком случае я вас не тороплю. Но, возможно, когда вы почувствуете всю мощь командной строки, обратно в «окошки» уже не захочется. Меня, по крайней мере, давно не тянет.

А если вы будете работать с обычными текстами, то неплохо уметь оценить этих текстов объемы. Причем здесь du и ls -l не подойдут, т. к. они покажут размер файла в байтах, а не объем текста в символах. Ведь в кодировке utf-8, которая сейчас практически повсеместна, каждая буква кириллицы занимает два байта, а латиница, пробелы и знаки препинания – по одному. Поэтому воспользуемся командой wc (word count, подсчет слов), которая, кроме слов, умеет считать строки, байты и символы:

$ wc -m commandline1
22251 commandline1

Но это, конечно, не самое интересное. Хотелось бы как-то оценивать именно полезный объем текста, ведь в текстовых файлах пробелы и переводы строк используются не только как разделители, но и для отступов, промежутков в тексте, выравнивания и т. п. Значит, лучшим решением будет эти пробельные символы вовсе не считать. Для этого проще всего будет перед подсчетом их удалить. Поможет нам в этом команда tr (попробуйте man -k character [символ] – и вы ее найдете). Для удаления символов новой строки и табуляции нужно воспользоваться так называемыми escape-последовательностями – обозначениями спецсимволов через \буква:

$ tr -d ' \t\n' <commandline1 | wc -m
18887

Символ “<” – еще один «винтик»: он означает, что на вход команде будет подан файл, чье имя указано после этого символа.

Наконец, чтобы увидеть сразу обе цифры, вы можете ввести две команды в одну строку через точку с запятой:

$ tr -d ' \t\n' <commandline1 | wc -m; wc -m commandline1
19415
23172 commandline1

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

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

sc() {
 tr -d ' \t\n' <”$1” | wc -m
 wc -m “$1”
}

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

Всю эту запись вы можете набрать прямо в командной строке:

$ sc() {
> tr -d ' \t\n' <”$1” | wc -m
> wc -m “$1”
> }
$

Как видите, приглашение в процессе ввода изменилось. Это указывает нам, что новая строка – продолжение предыдущей. Теперь можем делать так:

$ sc commandline1
20485
24475 commandline1

Уже лучше. Но писать отдельную команду для каждого файла, который мы хотим обсчитать, тоже не слишком удобно. Значит, надо нашу функцию еще немного усовершенствовать:

sc() {
 for i in “$@”; do
  tr -d ' \t\n' <”$i” | wc -m
  wc -m “$i”
 done
}

Здесь тоже почти все прозрачно, если перевести на русский первую строку: «для i в “$@” делай:» и последнюю: «сделано». То есть будет делаться то же, что и выше, но не для одного файла, а для каждого из некой пока непонятной записи “$@”. Точно так же, как $1 означает первый параметр (а $2 – второй, и так далее), эта запись означает все параметры сразу, причем каждый из них будет браться в отдельные кавычки. Видите, как удобно – ничего дополнительно додумывать не нужно, даже (опять-таки) если в именах встретятся пробелы. Теперь можете готовую функцию дописать в упомянутый выше файл .bash_aliases – и она будет доступна в каждом сеансе bash:

$ sc *
434
518 bash-tips
21256
25414 commandline1
1329
1725 примеры


Двусторонняя печать

Раз уж мы заговорили о текстах, то тексты эти часто хочется видеть не только на экране, но и на бумаге. Любые тексты – не только те, о которых речь выше: и web-страницы из браузера, и документацию или книги в PDF, да и не одни тексты, а еще и картинки. Часто удобно иметь возможность распечатки на две стороны листа, да еще и поля при этом сделать разными для четных и нечетных страниц, чтобы можно было подшить распечатку. К сожалению, далеко не все программы поддерживают автоматическую разбивку документа на четные и нечетные страницы для двусторонней печати на обычном (одностороннем) принтере. К счастью, мы можем и здесь подключить утилиты командной строки, которые позволят нам самостоятельно создать такую разбивку. Практически все графические программы для GNU/Linux, имеющие функцию печати, умеют производить так называемую «печать в файл». На выходе получается файл в формате PostScript, который можно напрямую передать стандартной консольной утилите печати – а можно и предварительно обработать. Для обработки таких файлов существует весьма многофункциональный и гибкий пакет утилит командной строки под названием psutils, которым мы и воспользуемся.

Для начала нужно создать входной файл. Для простоты задайте один и тот же путь и имя файла во всех программах, где вы будете использовать «печать в файл». Например, создайте в домашнем каталоге подкаталог tmp (если его еще там нет), а файл в нем назовите print.ps. Если в диалоге печати есть выбор формата файла, выберите PostScript.

Теперь приступим. Для начала нам нужно выбрать нужные страницы. Пакет psutils состоит из примерно двух десятков небольших программ, каждая из которых выполняет свой небольшой набор функций – в точном соответствии с одним из основных принципов Unix: делай одно дело, но делай его хорошо. То есть в первую очередь нужно выбрать подходящую программу. Для этого выведем список файлов в пакете. В Debian и основанных на нем дистрибутивах для работы с отдельными пакетами предназначена команда dpkg (от фразы Debian package), а список файлов пакета выводится ключом L. Но список этот достаточно длинный; хорошо бы его отфильтровать. Фильтровать тексты можно командой grep. Она очень гибко настраивается, но сейчас нам нужен простейший вариант – фильтрация по заданной строке. В GNU/Linux все исполняемые файлы из системных пакетов устанавливаются в один из четырех каталогов: /bin/, /sbin/, /usr/bin/, /usr/sbin/. Значит, добавим фильтр по строке, входящей во все четыре пути: grep bin/ (рис. 1). Из этого списка видно, что сейчас нам нужна команда psselect (select – выбирать). Начнем с нечетных страниц – ключ o (odd – нечетный):

psselect -o ~/tmp/print.ps

Тильда (~) – краткое обозначение домашнего каталога: каково бы ни было ваше имя пользователя, bash автоматически подставит путь к домашнему каталогу вместо этого символа.

Если вы поторопились ввести эту команду, в ответ наверняка увидели длинное полотно непонятного текста. Все правильно: как и большинство стандартных команд Unix, команды из пакета psutils выводят результаты на стандартный вывод, т. е. по умолчанию в терминал. По идее, теперь нужно отправить этот вывод на принтер. Но чтобы зря не расходовать бумагу, давайте пока будем тестировать все через файл, а вывод на принтер добавим уже когда закончим и будем удовлетворены результатом. Для вывода в файл и ввода из файла в shell тоже существуют специальные винтики, как | для передачи вывода другой команде: > и < (второй мы уже использовали в предыдущем примере). После них ставится имя файла, а сами символы похожи на стрелки, обозначающие направление движения данных: в файл (>имя-файла) или из файла (<имя-файла). Сразу же добавим и команду для просмотра этого файла. Простейший способ записать две команды в одной строке тоже был упомянут выше – разделить их точкой с запятой. Но есть и вариант похитрее – & & (два символа подряд без пробела): в этом случае вторая команда будет выполнена, только если первая завершится без ошибок.

psselect -o ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps

Если вы предпочитаете другую программу для просмотра файлов PostScript, замените evince на нее.

Аналогично можно вывести и четные страницы, заменив ключ o на e [even – четный]. Но чтобы не перекладывать всю пачку вручную в обратном порядке, лучше изменить порядок печати на обратный. Для этого у psselect тоже есть специальная опция – r (reverse):

psselect -er ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps

По идее, уже неплохо, но неудобно же для печати каждого файла вводить две команды. Поэтому давайте оформим их в отдельный скрипт, вызов которого можно будет повесить в вашем оконном менеджере, скажем, на кнопку на панели или пункт в меню. Такие скрипты принято хранить в каталоге ~/bin/, а назвать его можно print. В bash-скрипте добавьте в начале служебную строку:

#!/bin/bash

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

#!/bin/bash
psselect -o ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps
psselect -er ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps

После сохранения файла нужно разрешить доступ к нему на исполнение командой chmod (change mode – изменить режим [доступа]):

chmod a+x ~/bin/print

Запись a+x расшифровывается достаточно просто. Плюс означает, что некие права нужно добавить (если их нужно убрать, то тут будет минус). Слева от плюса пишется, кому эти права дать (all – всем), а справа – на что именно (eXecute – исполнение); если хотите сделать файл исполняемым только для себя, замените a на u (user – пользователь, т. е. владелец файла).

В тестовом режиме уже все хорошо. В частности, вторая команда выполнится только после того, как завершится первая, т. е. когда мы закроем просмотр ее результата. Но при печати задача сразу же будет добавлена в очередь принтера, и вторая команда выполнится немедленно. Тогда печать второго задания начнется тут же после первого – вы даже бумагу перевернуть не успеете. Поэтому хорошо будет вставить между этими командами некую задержку, ожидание какого-то действия от человека. Поскольку скрипт вы будете вызывать нажатием графической кнопки, то и запрос лучше сделать в графическом интерфейсе. Да, существуют две программы, позволяющие прямо из командной строки, безо всякого программирования, создавать разнообразные графические окошки. Называются они kdialog и zenity. Первая зависит почти от половины KDE, а вторая – только от библиотеки GTK, которая еще и уже установлена в любой системе, где используется, скажем, GIMP или Firefox. Поэтому мы выбираем zenity. Если вы работаете в KDE, то переработка скрипта под kdialog останется вам в качестве домашнего упражнения, благо ничего сложного там нет.

Во время печати может что-то пойти не так, поэтому на всякий случай создадим окно с вопросом: печатать ли вторую сторону. Это делается с помощью опции question (вопрос), для которой есть всего две дополнительных опции: title (заголовок) и text (текст). Полностью команда будет выглядеть так:

zenity --question --title=Печать --text=”Печатать вторую сторону?”

Окно, которое она создаст, изображено на рис. 2. А результат работы команды зависит от сделанного выбора. При нажатии кнопки «да» программа завершится корректно, при нажатии «нет» – с ошибкой. А запускать следующую команду только при успешном завершении предыдущей мы уже умеем. Теперь наш скрипт будет выглядеть так:

#!/bin/bash
psselect -o ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps
zenity --question --title=Печать --text=”Печатать вторую сторону?” &&
 psselect -er ~/tmp/print.ps >~/tmp/tmp.ps && evince ~/tmp/tmp.ps

Теперь изменим поля для четных и для нечетных страниц. Редактирование документов PostScript делается программой pstops (PS to PS) из того же пакета psutils. Нам нужно обработать каждую отдельную страницу, поэтому из man-страницы pstops нас интересуетвот эта строчка:

spec = [-]pageno[L][R][U][H][V][@scale][(xoff,yoff)]

Здесь pageno – номер начальной страницы; а квадратные скобки традиционно для man-страниц обозначают необязательные параметры, из которых нам нужен только последний – смещение. К примеру, для увеличения одного из полей на сантиметр параметр для команды pstops будет выглядеть так: “0(1cm,0)”. Конечно, четные страницы нужно двигать в другую сторону, т. е. на отрицательную величину. Если хотите изменить это число, можете задать сдвиг и в миллиметрах, с суффиксом mm. Кроме этого, зададим размер страницы (-p a4) и добавим полученную команду в скрипт:

psselect -o ~/tmp/print.ps | pstops -p a4 “0(1cm,0)” >~/tmp/tmp.ps && evince ~/tmp/tmp.ps
zenity --question --title=Печать --text=”Печатать вторую сторону?” &&
 psselect -er ~/tmp/print.ps | pstops -p a4 “0(-1cm,0)” >~/tmp/tmp.ps && evince ~/tmp/tmp.ps

Казалось бы, можно приступать к печати. Но есть еще одна тонкость. Вторые стороны листов у нас печатаются в обратном порядке, а значит, при нечетном количестве страниц в документе первую страницу второго прохода нужно оставить пустой. И чтобы не приходилось вручную проверять четность страниц и в одном из случаев откладывать одну страницу из пачки, хорошо будет добавить эту пустую страницу автоматически в начало второго документа. Для этого сначала нужно проверить четность страниц. Как это сделать? В пакете psutils нет утилиты для получения сведений о документе. Но нет ее там неспроста. Если вы ввели первую команду из этой части в терминале, еще без перенаправления в файл, то могли увидеть, что на выходе получился текст, хотя и не очень понятный. PostScript можно назвать не только форматом документов, но и языком программирования. А значит, вся информация о документе хранится там в текстовом виде и вполне может быть извлечена уже знакомой нам командой grep. Как нетрудно догадаться (или найти в Интернете), количество страниц стоит попробовать поискать по слову Pages. Скорее всего, даже поиск по одному только этому слову даст единственный результат:

$ grep Pages ~/tmp/print.ps
%%Pages: 41

Но для верности лучше введем заголовок полностью: “grep %%Pages: ~/tmp/print.ps”. Чтобы вырезать из этой строки число, воспользуемся командой cut (cut – разрезать):

$ grep Pages ~/tmp/print.ps | cut -d ' ' -f 2
41

Опция d задает разделитель полей (delimiter) – в данном случае это пробел. А опция f – номера полей, которые нужно оставить. Задачу «проверить четность» разделим на две еще меньшие подзадачки: вычислить остаток от деления на два и сравнить его с нулем (или единицей). Для первой вполне подойдет оператор bash для арифметических действий с целыми числами: $((выражение)). В нем есть специальная операция вычисления остатка, обозначаемая знаком процента (если вы изучали какой-нибудь язык программирования, то такое обозначение может быть вам знакомо). Но внутри этих скобок должны стоять уже готовые числа, а не команды – как быть? Применим еще один «винтик» – оператор подстановки: `команда` или $(команда). Он выполняет команду, а ее результат подставляет в «то место, где это написано». Итак:

$(( $(grep Pages ~/tmp/print.ps | cut -d ' ' -f 2) % 2 ))

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

$ echo $(( $(grep Pages ~/tmp/print.ps | cut -d ' ' -f 2) % 2 ))
1

Для проверки условий есть специальная команда ‘[' или test. Да, квадратная скобка – это отдельная команда; отличается она от записи словом test только тем, что в конце нужно ставить закрывающую скобку. Мы могли бы и здесь воспользоваться конструкцией & & , но поскольку сейчас нам нужно обработать оба варианта, то нагляднее будет записать это через оператор if:

if [ “$(( $(grep Pages ~/tmp/print.ps | cut -d ' ' -f 2) % 2 ))” = 0 ]; then
...
else
...
fi

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

А чтобы добавить пустую страницу, обратимся к той же программе psselect – ей можно указать не только номера страниц или диапазоны, но и знак подчеркивания, который обозначает пустую страницу:

psselect -er ~/tmp/print.ps | psselect _,1- >~/tmp/tmp.ps && evince /home/t/tmp/tmp.ps

Здесь дефис обозначает диапазон, а отсутствие конца этого диапазона – что страницы нужно брать до конца документа. Объединяем все вместе (рис. 3). Последний раз убеждаемся на экране в правильности результата – и добавляем печать вместо просмотра. Формат PostScript хорош тем, что его можно передавать напрямую драйверу принтера. Для этого существуют две команды: lp из пакета cups или cupsys; и lpr из пакета lpr, lprng или cups-bsd. Если у вас настроен принтер, то одна из этих команд, скорее всего, уже установлена. Наличие команды lp в современных дистрибутивах более вероятно, поэтому заменяем создание временного файла на нее:

psselect -o ~/tmp/print.ps | pstops -p a4 “0(1cm,0)” | lp -
zenity --question --title=Печать --text=”Печатать вторую сторону?” &&
 if [ “$(( $(grep Pages ~/tmp/print.ps | cut -d ' ' -f 2) % 2 ))” = 0 ]; then
  psselect -er ~/tmp/print.ps | psselect _,1- | pstops -p a4 “0(-1cm,0)” | lp -
 else
  psselect -er ~/tmp/print.ps | pstops -p a4 “0(-1cm,0)” | lp -
 fi

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

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

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