Подготовлено по материалам рассылки «программирование на shell и awk»
http://win.subscribe.ru/catalog/comp.soft.prog.shellandawk
Big Shadow
Киев, 2003
В этой заметке будут обсуждаться:
Однако, в 70-е годы прошлого века не все было так одназначно. Существовал один филосовский вопрос. Суть его была такова: что должна делать команда echo, если ей не передали аргументов, в частности, следует ли ей выдавать пустую строку или вообще ничего не предпринимать? По этому поводу велись большие дебаты. Такие, что вдохновили Дуга МакИлроя /Doug McIlroy/ сочинить притчу, которая приведена ниже.
UNIX и эхо.
Сама природа покровительствовала UNIX и вторила ей более охотно, чем кому-либо из смертных.
Простые люди поражались ее эхом, таким оно было точным и кристально чистым. Они не могли поверить,
что ей отвечают те же леса и скалы, которые так искажают их собственные голоса. Когда один
нетерпеливый пастушок попросил UNIX: "Пусть эхо ответит ничего", и она послушно открыла рот,
эхо промолчало. "Зачем ты открываешь рот? спросил пастушок. Отныне никогда не открывай его,
если эхо должно ответить ничего!". и UNIX подчинилась.
"Но я хочу совершенного исполнения, даже если эхо отвечает ничего, потребовал другой,
обидчивый, юноша, а никакого совершенного эха не получится при закрытом рте".
Не желая обидеть никого из них, UNIX согласилась говорить разные "ничего" для нетерпеливого
и обидчивого юношей. Она называла "ничего" для обидчивого как '\n'. Однако теперь, когда она
говорила '\n', на самом деле она не произносила ничего, поэтому ей приходилось открывать
рот дважды: один раз, чтобы сказать '\n', и второй раз, чтобы не сказать ничего.
Это не понравилось обидчивому юноше, который тотчас сказал: "Для меня '\n' звучит,
как настоящее "ничего", но когда ты открываешь рот второй раз, то все портишь. Возьми второе
"ничего" назад". Услужливая UNIX согласилась отказаться от некоторых эхо и обозначила это
как '\с'. С тех пор обидчивый юноша мог услышать совершенное эхо "ничего", если он задавал
'\n' и '\с' вместе, но говорят, что он так и не услышал его, поскольку умер от излишеств
в обозначениях. [1]Жила-была в стране Нью-Джерси, прекрасная
девушка UNIX, к которой приезжали издалека, чтобы полюбоваться ею. Ослепленные чистотой UNIX,
все искали ее руки и сердца: одни за изящество, другие за изысканную вежливость,
третьи за проворность при выполнении самых изнурительных заданий. Была она от рождения
столь великодушна и услужлива, что все женихи остались довольны ею, а ее многочисленное потомство
распространилось во все концы земли.
Хотя все настоящие реализации этой команды выдают пустую строку, это еще не значит, что проблемы закончились. Давайте их рассмотрим. Рекомендую все, о чем я буду рассказывать, проверить на практике, сидя за консолью. Так будет легче разобраться.
Уже в 7-ой редакции UNIX echo распознавала флаг -n, который
подавлял вывод завершающего символа перевода строки:
$ echo -n Seven Edition
Seven Edition$
В System V, где команда echo умеет интерпретировать упраляющие последовательности
вида \Х, для подавления вывода завершающего символа перевода строки используют обозначение
\с. Поэтому вызвав echo с аргументом -n вы можете
получить не совсем то, что ожидали:
$ echo -n System V
-n System V
$
В Linux же, echo не желает обрабатывать последовательности вида \Х:
$ echo Linux\\с
Linux \с
$
до тех пор, пока вы не зададите ей флаг -e:
$ echo -e Linux\\с
Linux$
Можно воспользоваться и флагом -n, echo из Linux его понимает.
Добавляет путаницы и то, что почти всегда shell имеет встроенную команду echo, поведение которой может отличаться от внешней /bin/echo. Кроме того, в системе может оказаться не одна echo, а парочка. В таком случае, второе echo обычно находится где-нибудь в районе /usr/ucb/. Из-за таких различий в реализации echo возникают определенные трудности при переносе сценариев из системы в систему. Есть ли способ их избежать? Конечно, но это уже другая история...
Перед тем как перейти к детальному рассмотрению esc-последовательностей, давайте вначале научимся вводить символ esc с клавиатуры (если вы умеете это делать, то можете переходить к выполению примеров из следующего абзаца). Чтобы ввести с клавиатуры код любой "непечатной", т. е. управляющей клавиши (функциональной, стрелки и т. п.), необходимо нажать Ctrl+V, а затем интересующую вас "кнопку". Этот прием будет работать не только в командной строке, но и в текстовом редакторе vi.
Проверим на практике. Выполним команду echo "Ctrl+V,Esc[30;40m";clear
(на экране символ esc будет показан как ^[):
$ echo "^[[30;40m";clear
Если вы все сделали правильно, то
приобретайте прибор ночного видения :)! Пожалуй я не ошибусь, предположив, что у вас возникли
вопросы:
А что это за числа? К чему они ведут? "А вдруг они не курят, а вдруг они не пьют?"
[2]
Числа 30 и 40 это аргументы команды esc[
n
m.
При необходимости указать несколько аргументов, их перечисляют через точку с запятой:
esc[
n1
;
n2
;...m.
В зависимости от значения n результаты будут следующими:
==================================================================
k | n=k - установка | n=k+30 - установка | цвет символов
| режима | цвета символов | в режиме
| отображения | n=k+40 - установка | повышенной
| символов | цвета фона | яркости
--|----------------------|-------------------------|--------------
0 | сброс всех атрибутов | черный | серый
1 | яркий (утолщенный) | красный | розовый
2 | тусклый | зеленый | салатовый
3 | | коричневый | желтый
4 | подчеркнутый | синий | светло-синий
5 | мигающий | фиолетовый | лиловый
6 | | бирюзовый | голубой
7 | реверсный | белый | ярко-белый
==================================================================
Теперича, имея в своем распоряжении такую табличку, можете приступать к получению
"синих экранов", "красных квадратов" и прочей творческой работе. В которой желаю
вам всяческих успехов (тем кто предпочитает другие слова удачи)!!!
Проведем испытания. Начнем с подготовки рабочего пространства. Выполните команду clear (или просто нажмите Ctrl+L). Затем более хитрую (напомню, что символы ^[ появляются в результате нажатия клавиш Ctrl+L,Esc):
|
Наведение порядка в этом "зоосаде" обеспечивают два компонента UNIX: база данных и библиотека подпрограмм. В первом описаны поддерживаемые терминалом функции, а второй используется для выполнения запросов к базе данных. Каждый тип терминала в этой базе имеет свое название и перечень его свойств. Изначально эта информация хранилась в файле termcap (от terminal capabilities возможности терминала). C годами termcap очень вырос и в семействе AT&T юниксов превратился в базу данных terminfo. В ней, чтобы не хранить всю информацию в одном большом файле, описание каждого терминала компилируется и сохраняется в отдельном файле.
Свойствами терминала могут быть:
Названия свойств стандартизированы и неизменны (почти:), а их значения наоборот изменяются в зависимости от типа терминала. В качестве примера ниже приведено несколько стандартных обозначений характеристик терминалов:
termcap | terminfo | |
число символов в строке | co | cols |
число строк на экране | li | lines |
очистить экран | сl | clear |
включить повышенную яркость | md | bold |
включить мигание | mb | blink |
включить реверсное отображение | mr | rev |
установить цвет символов | AF | setaf |
установить цвет фона | AB | setab |
переместить курсор | cm | cup |
подать звуковой сигнал | bl | bel |
Более подробную информацию можно получить в манах по termcap и terminfo.
Таким образом, когда программе нужна информация о возможностях терминала, она определяет тип используемого терминала по значению переменной окружения TERM, затем обращается к базе данных и получает соответствующее типу терминала описание, которое и использует в дальнейшем для управления вводом/выводом.
Что же делать, если у вас возникло желание написать сценарий, который работал бы с любым, поддерживаемым операцинной системой, терминалом с одинаковой (или уменьшенной) функциональностью? Попробуйте воспользоваться утилитой tput. Синтаксис у нее следующий:
tput [-T тип_терминала] название_свойства [параметр(ы) ...]
В качестве значения аргумента название_свойства может быть задано любое имя из списка свойств базы данных termcap и/или terminfo для терминала указанного в тип_терминала. Если этот аргумент опущен, то tput будет использовать значение переменной TERM.
Для кодирования цветов используются
следующие числа (в скобках указываются цвета для режима повышенной яркости):
0 - черный (серый)
1 - красный (розовый)
2 - зеленый (салатовый)
3 - коричневый (желтый)
4 - синий (светло-синий)
5 - фиолетовый (лиловый)
6 - бирюзовый (голубой)
7 - белый (ярко-белый)
Однако, не все так просто в нашем королевстве. Будьте готовы к тому, что в одних системах
(BSD) tput будет ожидать название_свойства в стиле termcap,
а в других (System V) в стиле terminfo. Примеры:
$ tput AF 2 # теперь буковки будут зелененькие
$ tput setaf 2 # если сразу не сработало, то теперь уж точно
$ tput cm 24 35 # переместить курсор в 24-ю строку,
# а про позицию догадайтесь сами
$ tput cup 24 35 # аналогично
$ tput li # сколько у меня строк на эране?
30
$ tput lines # а у вас?
Всем, кто хочет изучить материальную часть более предметно, рекомендую поэкспериментировать
с утилитами, которые конвертируют записи базы terminfo в termcap и наоборот.
В BSD-системах это tconv, а в Linux и юниксах ветви AT&T infocmp.
Вот вам еще примерчики:
$ infocmp -C|grep cl #вывод в формате termcap
:cl=\E[H\E[J:cm=\E[%i%d;%dH:cr=^M:cs=\E[%i%d;%dr:\
$ infocmp -I|grep cl #вывод в формате terminfo
clear=\E[H\E[J, el1=\E[1K, el=\E[K, ed=\E[J,
Конечно же, существует много случаев, когда использование esc-последовательностей напрямую оправдано.
Приведу в качестве примера фрагмент файла /etc/rc.status дистрибутива SuSE Linux (комментарии мои):
if test "$TERM" != "raw" && stty size > /dev/null 2>&1 ; then
# если наш терминал понимает, что такое ширина и высота,
# то назначаем кодам esc-последовательностей понятные названия
esc=`echo -en "\033"`
extd="${esc}[1m"
warn="${esc}[1;31m"
done="${esc}[1;32m"
attn="${esc}[1;33m"
norm=`echo -en "${esc}[m\017"`
stat=`echo -en "\015${esc}[${COLUMNS}C${esc}[10D"`
# ... неинтересное пропускаем
rc_done_up="${esc}[1A${rc_done}"
rc_failed_up="${esc}[1A${rc_failed}"
rc_reset="${norm}"
rc_save="${esc}7"
rc_restore="${esc}8"
function rc_cuu () { echo -en "\033[${1}A"; }
# и даже определяем функцию
# для перемещения по экрану вверх на заданное число строк
else
# ну а если терминал не знает где у него верх, а где низ,
# то ведем себя проще и выдаем информацию построчно без выкрутасов,
# чтобы люди к нам тянулись :) ...
И небольшое замечание относительно ДОСа. Помните такую ОС? Так вот, если к ней прикрутить стандартный драйвер ANSI.SYS, то все вышеперечисленные esc-последовательности будут работать и там.
2. Слова из песни, которые как известно не выбросишь...