Все, чем мы пользуемся - видеомагнитофоны, компьютеры, телефоны и даже книги, - имеет свой пользовательский интерфейс. Интерфейс есть и у наших программ. Какие аргументы должны передаваться в командной строке? Можно ли перетаскивать мышью файлы? Должны ли
В этой главе мы не будем обсуждать проектирование пользовательского ин-герфейса - на эту тему и так написано множество книг. Вместо этого мы сосредоточим внимание на реализации интерфейсов - передаче аргументов в командной строке, посимвольному чтению с к
Простейшим пользовательским интерфейсом обычно считается так называемый консольный интерфейс. Программы с консольным интерфейсом читают целые строки и выводят данные также в виде целых строк. Примером консольного интерфейса являются фильтры (например,
Более сложный вариант - так называемый полноэкранный интерфейс. Им обладают такие программы, как elm или lynux. Они читают по одному символу и могут выводить данные в любой позиции экрана. Этот тип интерфейса рассматривается в рецептах 15.4, 15.6, 15.
Последнюю категорию интерфейсов составляют графические пользователь-кие интерфейсы (GUI, Graphic User Interface). Программы с графическим интерфейсом работают не только с отдельными символами, но и с отдельными шкселями. В графических интерфейсах часто ис
Окна заполняются элементами (widgets) - например, полосами прокрутки или кнопками. Netscape Navigator, как и ваш менеджер окон, обладает полноценным графическим интерфейсом. Perl позволяет работать со многими инструментальными пакетами GUI, однако мы
Не путайте пользовательский интерфейс программы со средой, в которой она работает. Среда определяет тип запускаемых программ. Скажем, при регистрации па терминале с полноэкранным вводом/выводом вы сможете работать с консо.';
пыми приложениями, но не с графическими программами. Давайте кратко р;г смотрим различные среды.
Некоторые из них позволяют работать лишь с программами, обладающими чисто консольным интерфейсом. Упрощенный интерфейс позволяет объединять их в качестве многократно используемых компонентов больших сценариев; такп" объединение открывает чрезвычайно ш
Типичный рабочий сеанс, в котором участвует терминал с экраном и клавиатурой, позволяет работать как с консольными, так и полноэкранными интерфейсами. Программа с полноэкранным интерфейсом взаимодействует с драйвером терминала и хорошо знает, как выве
Наконец, некоторые оконные системы позволяют выполнять как консольные и полноэкранные, так и графические программы. Например, можно запустит. grep (консольная программа) из vi (полноэкранная программа) в OKHex'term (графическая программа, работающая в
Существуют специальные инструментальные пакеты для программирования в полноэкранных и графических средах. Такие пакеты (curses для полноэкранных программ; Tk - для графических) улучшают переносимость, поскольку nporpav ма не зависит от особенностей конкре
Существуют и другие варианты взаимодействия с пользователем, в пс|'чую очередь - через Web. Программирование для Web подробно рассматривается в главах 19 и 20, поэтому в этой главе мы не будем задерживаться на этой теме.
Вы хотите, чтобы пользователь могу повлиять на поведение вашей программы, передавая аргументы в командной строке. Например, параметр -v часто управляет степенью детализации вывода.
Многие классические программы (такие, как Is и пп) получают односимвольные параметры (также называемые флагами или ключами командной строки) - например, -1 или -г. В командных строках Is -I и гт -г аргумент является логической величиной: он либо присутств
% rm -r -f /tmp/testdir эквивалентна следующей:
% rm -rf /tmp/testdir Модуль Getopt::Std, входящий в стандартную поставку Perl, анализирует эти традиционные типы параметров. Его функция getopt получает одну строку, где каждый символ соответствует некоторому параметру, анализирует аргументы командной строки в массиве @AR
Модуль Getopt::Std также содержит функцию getopts, которая позволяет указать, является ли параметр логическим или принимает значение. Параметры со значениями (такие, как параметр -о программы gcc) обозначаются двоеточием, как это сделано в следующем ф
use Getopt::Std;
getopts("o:");
if ($opt_o) {
print "Writing output to $opt_o"; Обе функции, getopt и getopts, могут получать второй аргумент - ссылку на хэш. При наличии второго аргумента значения вместо переменных $opt_X сохраняются в $hash{X}:
use Getopt::Std;
%option =();
getopts("Do:", \%option):
if ($option{D}) {
print "Debugging mode enabled.\n";
}
# Если параметр -о не задан, направить результаты в "-". " Открытие "-" для записи означает STDOUT $option{o} = "-" unless defined $option{o};
print "Writing output to file $option{o}\n" unless $option{o} eq "-";
open(STDOUT, "> $option{o}")
or die "Can't open $option{o} for output: $!\n"; Некоторые параметры программы могут задаваться целыми словами вместо о т-дельных символов. Обычно они имеют специальный префикс - двойной дефис:
% gnutar --extract --file latest.tar Значение параметра -file также может быть задано с помощью знака равенства:
% gnutar --extract --file=latest.tar Функция GetOptions модуля Getopt::Long анализирует эту категорию параметров. Она получает хэш, ключи которого определяют параметры, а значения представляют собой ссылки на скалярные переменные:
use Getopt::Long;
Get0ptions( "extract" => \$extract, "filers" => \$file );
if ($extract) {
print "I'm extracting.\n";
}
die "I wish I had a file" unless defined $file;
print "Working on the file $file\n"; Если ключ хэша содержит имя параметра, этот параметр является логическим. Соответствующей переменной присваивается false, если параметр не задан, или 1 в противном случае. Getopt::Long не ограничивается логическими параметрами и значениями Getopt::Std
Описание Значение Комментарий
option Нет Задастся в виде "option или не задастся вообще
option! Нет Может задаваться в виде "option или "nooption
option=s Да Обязательный строковый параметр: "option-somestring
option: s Да Необязательный строковый параметр: -option
или "option-somcstring
option=i Да Обязательный целый параметр: "option-35
option: i Да Необязательный целый параметр: "option или "oplion°35
option=f Да Обязательный вещественный параметр: --option-3.141
option :f Да Необязательный вещественный параметр: "option __ или--option°3.141
> Смотри также ------------------------------
Документация по стандартным модулям getopt::Long и Getopt::Std; примеры ручного анализа аргументов встречаются в рецептах 1.5, 1.17, 6.22, 7.7, 8.19 и 15.12.
Требуется узнать, была ли ваша программа запущена в интерактивном режиме или нет. Например, запуск пользователем из командного интерпретатора является интерактивным, а запуск из cron - нет.
Решение
Воспользуйтесь оператором -t для проверки STDIN и STDOUT:
sub I_am_interactive {
return -t STDIN && -t STDOUT;
} В POSIX-совместимых системах проверяются группы процессов:
use POSIX qw/getpgrp tcgetpgrp/;
sub I_am_interactive {
local *TTY; ft local file handle open(TTY, "/dev/tty") o
r die "can't open /dev/tty: $!";
my $tpgrp = tcgetpgrp(fileno(TTY));
my $pgrp = getpgrpO;
close TTY;
return ($tpgrp == $pgrp);
}
Комментарий
Оператор -t сообщает, соответствует ли файловый манипулятор или файл терминальному устройству (tty); такие устройства являются признаком интерактивного использования. Проверка сообщит лишь о том, была ли ваша программа перенаправлена. Если программа запущ
Второй вариант проверки сообщает, находится ли терминал в монопольном распоряжении программы. Программа, чей ввод и вывод был перенаправлен, все равно при желании может управлять своим терминалом, поэтому POSIX-версня I_am_interactive возвращает true. Про
Какой бы вариант I_am_interactive вы ни выбрали, он используется следую щим образом:
while (1) {
if (I_am_interactive()) { print "Prompt: ";
}
$line = ;
last unless defined $line;
'' Обработать $line } Или более наглядно:
sub prompt { print "Prompt: " if I_am_interactive() }
for (promptO; $line = ; promptO) {
# Обработать
$line }
> Смотри также --------------------------------
Документация по стандартному модулю POSIX. Оператор проверки файлов -t описан вреr1ор(1).
Воспользуйтесь модулем Term::Cap для посылки нужной последовательности символов. Скорость вывода терминала можно определить с помощью модуля'
15.4. Определение размера терминала или окна 527
POSIX::Termios (или можно предположить 9600 бит/с).
Ошибки, возникающие при работе с POSIX::Termios, перехватываются с помощью eval:
use Term::Cap;
$@SPEED = 9600;
eval {
require POSIX;
my $termios = POSIX::Termios->new();
$termios->getattr;
$@SPEED = $termlos->getospeed;
};
@terminal = Term::Cap->Tgetent({@SPEED=>$@SPEED});
$terminal->Tputs('cl', 1, STDOUT); Или выполните команду clear:
system("clear");
Комментарий
Если вам приходится часто очищать экран, кэшируйте возвращаемое значение Term::Cap или команды clear:
$clear = $terminal->Tputs('с1'):
$clear = 'clear'; Это позволит очистить экран сто раз подряд без стократного выполнения clear:
print $clear;
> Смотри также -------------------------------
Man-страницы clear(1) и termcap(i) (если они есть); документация по стандартному модулю Term::Cap; документация по модулю Term::Lib с CPAN.
Требуется определить размер терминала или окна. Например, вы хотите отформатировать текст так, чтобы он не выходил за правую границу экрана.
Решение
Воспользуйтесь функцией iocti (см. рецепт 12.14) или модулем Term::ReadKey с CPAN:
use Term::ReadKey;
($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();
Вы хотите выводить на экране символы разных цветов. Например, цвет может использоваться для выделения текущего режима или сообщения об ошибке.
Решение
Воспользуйтесь модулем Term::ANSIColor с CPAN для передачи терминалу последовательностей изменения цвета ANSI:
use Term::ANSIColor;
print color("red"), "Danger, Will Robinson!\n", color("reset");
print "This is just normal text.\n";
print colored("", "blink"); Или воспользуйтесь вспомогательными функциями модуля Term::ANSIColor:
use Term::ANSIColor qw(:constants);
print RED, "Danger, Will Robinson!\n", RESET:
Комментарий
Модуль Term::ANSIColor готовит служебные последовательности, которые опознаются некоторыми (хотя далеко не всеми) терминалами. Например, в color-xterm этот рецепт работает. В обычной программе xterm или на терминале vt100 он работать не будет.
Существуют два варианта использования модуля: либо с экспортированными функциями соlоr($АТРИБУТ) и colored($TEKCT, $АТРИБУТ), либо с вспомогательными функциями (такими, как BOLD, BLUE и RESET).
Атрибут может представлять собой комбинацию цветов и модификаторов. Цвет символов принимает следующие значения: black, red, green, yellow, blue, magenta (черный, красный, зеленый, желтый, синий, малиновый). Цвет фона принимает значения on_black, on_re
Атрибуты могут объединяться:
print color("red on.black"), "venom lack\n";
print color("red on.yellow"), "kill that feilow\n";
print color("green on.cyan blink"), "garish!\n";
print color("reset");
Этот фрагмент можно было записать в виде:
print colored("venom lack\n", "red on_black");
print coloredC'kill that fellow\n", "red", "on_yellow");
print colored("garish!\n", "green", "on_cyan", "blink"),; или:
use Term::ANSIColor qw(:constants)
print BLACK, ON.WHITE, "black on white\n";
, print WHITE, ON.BLACK, "white on J3lack\n";
print GREEN, ON.CYAN, BLINK; "garish !\n;';
print RESET; где BLACK - функция, экспортированная из Term::ANSIColor.
Не забывайте вызвать print RESET или со1ог(" reset") в конце программы, если вызов colored не распространяется на весь текст. Если этого не сделать, ваш терминал будет раскрашен весьма экзотическим образом. Сброс даже можно включить в блок END:
END { print color("reset") } чтобы при завершении программы цвета были гарантированно сброшены.
Атрибуты, распространяющиеся на несколько строк текста, могут привести и замешательство некоторые программы или устройства. Если у вас возникнут затруднения, либо вручную установите атрибуты в начале каждой строки, либо используйте colored, предварите
$Теrm::ANSIColor::EACHLINE = $/;
print colored("EOF, RED, ON_WHITE, BOLD, BLINK);
This way each line has its own attribute set. EOF
[> Смотри также --------------------------------
Документация по модулю Term::AnsiColor с CPAN.
Требуется прочитать с клавиатуры один символ. Например, на экран выведено меню с клавишами ускоренного вызова, и вы не хотите, чтобы пользователь нажимал клавишу Enter при выборе команды.
Решение
Воспользуйтесь модулем Term::ReadKey с CPAN, чтобы перевести терминал в режим cbreak, прочитать символы из STDIN и затем вернуть терминал в обычный режим:
use Term::ReadKey;
ReadMode 'cbreak';
$key = ReadKey(O);
ReadMode 'normal';
Комментарий
Модуль Term::ReadKey может переводить терминал в разные режимы, cbreak лишь один из них. В этом режиме каждый символ становится доступным для программы сразу же после ввода (см. пример 15.1). Кроме того, в нем происходит эхо-вывод символов; пример режима
Пример 15.1. sasdi
#!/usr/bin/perl -w
# sascii - Вывод АSCII-кодов для нажимаемых клавиш
use Term::ReadKey;
ReadMode('cbreak');
print "Press keys to see their ASCII values. Use Ctrl-C to quit.\n";
while (1) {
$char = ReadKey(O);
last unless defined $char:
printf(" Decimal: %d\tHex: %x\n", ord($char), ord($char));
}
ReadMode('normal'); Режим cbreak не мешает драйверу терминала интерпретировать символы конца файла и управления. Если вы хотите, чтобы ваша программа могла прочитать комбинации Ctrl+C (обычно посылает процессу SIGINT) или Ctrl+D (признак конца файла в UNIX), используйте
Вызов Read Key с нулевым аргументом означает, что мы хотим выполнить нормальное чтение функцией getc. При отсутствии входных данных программа ожидает их появления. Кроме того, можно передать аргумент -1 (неблокирующее чтение) или положительное число, кото
Последние версии Term::ReadKey также включают ограниченную поддержку систем, не входящих в семейство UNIX.
> Смотри также --------------------------------
Документация по модулю Term::ReadKey с CPAN; рецепты 15.8-15.9. Функции getc и sysread описаны в perlfunc(1).
Требуется выдать предупреждающий сигнал на терминале пользователя.
Решение
Воспользуйтесь символом "\а" для выдачи звукового сигнала:
print "\aWake up!\n"; Другой вариант - воспользуйтесь средством терминала "vb" для выдачи визуального сигнала:
use Term::Cap;
$OSPEED = 9600;
eval {
require POSIX;
my $termios = POSIX::Termios->new();
$termios->getattr;
$OSPEED = $termios->getospeed;
};
$terminal = Term::Cap->Tgetent({OSPEED=>$OSPEED});
$vb = "";
eval {
$terminal->Trequire("vb");
$vb = $terminal->Tputs('vb', 1);
}
print $vb; # Визуальный сигнал
Комментарий
Служебный символ "\а" - то же самое, что и "\cG", "\007" и "\х07". Все эти обозначения относятся к символу ASCII BEL, который выдает на терминал противный звонок. Вам не приходилось бывать в переполненном терминальном классе в конце семестра, когда десятк
Визуальные сигналы поддерживаются не всеми терминалами, поэтому мы включили их вызов в eval. Если визуальный сигнал не поддерживается, Т require инициирует die, при этом переменная $vb останется равной "". В противном случае переменной $vb присваивает
Более разумный подход к выдаче сигналов реализован в графических терминальных системах (таких, как xterm). Многие из них позволяют включить визуальные сигналы на уровне внешнего приложения, чтобы программа, тупо выводящая chr(7), была менее шумной.
> Смотри также -------------------------------
Раздел "Quote и Quote-like Operators" в perlop(\); документация по стандартному модулю Term::Cap.
Вы хотите напрямую работать с характеристиками своего терминала.
Решение
Воспользуйтесь интерфейсом POSIX termios.
Комментарий
Представьте себе богатые возможности команды stty - можно задать все, от служебных символов до управляющих комбинаций и перевода строки. Стандартный модуль POSIX обеспечивает прямой доступ к низкоуровневому терминальному интерфейсу и позволяет реализовать
Программа из примера 15.2 показывает, какие управляющие символы используются вашим терминалом для стирания в предыдущей и текущей позиции курсора (вероятно, это клавиши "забой" и Ctrl+U). Затем она присваивает им исторические значения, # и @, и предла
Пример 15.2. demo
#!/usr/bin/perl -w
# Демонстрация работы с интерфейсом POSIX termios
use POSIX qw(:termios_h);
$term = POSIX::Termios->new;
$term->getattr(fileno(STDIN));
$erase = $term->getcc(VERASE);
Skill = $term->getcc(VKILL);
printf "Erase is character %d, %s\n", $erase, uncontrol(chr($erase));
printf "Kill is character %d, %s\n", $kill, uncontrol(chr($kill));
$term->setcc(VERASE, ord('ff'));
$term->setcc(VKILL, ord('@'));
$term->setattr(1, TCSANOW);
print "erase is #, kill is @; type something: ");
$line = ;
print "You typed: $line";
$term->setcc(VERASE, $erase);
$term->setcc(VKILL, Skill);
$term->setattr(1, TCSANOW);
sub, uncontrol {
local $_ = shift;
s/([\200-\377])/sprintf("M-%c",ord($1) & 0177)/eg;
s/([\0-\37\177])/sprintf(""%c",ord($1) " 0100)/eg;
return $_:
} Следующий модуль, HotKey, реализует функцию read key на Perl. Он не обладает никакими преимуществами по сравнению с Term::ReadKey, а всего лишь показывает интерфейс termios в действии:
# HotKey.pm
package HotKey;
@ISA = qw(Exporter);
@EXPORT = qw(cbreak cooked readkey):
use strict;
use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
$fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON;
$noecho = $oterm & ~$echo;
sub cbreak {
$term->setlflag($noecho); # Эхо-вывод не нужен
$Term->setcc(VTIME, 1);
$term->setattr($fd_stdin, TCSANOW);
}
sub cooked {
$term->setlflag($oterm);
$term->setcc(VTIME, 0);
$term->setattr($fd_stdin, TCSANOW):
}
sub readkey {
my $key = ' ';
cbreak();
sysread(STDIN, $key, 1);
cooked();
return $key;
}
END { cooked ( ) }
1;
> Смотри также
Документация по стандартному модулю POSIX; рецепты 15.6; 15.9.
Требуется узнать, имеются ли необработанные входные данные, не выполняя их фактического чтения.
Решение
Воспользуйтесь модулем Term::ReadKey от CPAN и попытайтесь прочитать символ в неблокирующем режиме, для этого используется аргумент -1:
use Term::ReadKey;
ReadMode ('cbreak''):
if (defined ($char = tieadKey(-l)) ) {
# Имеется необработанный ввод $char } else {
# Необработанного ввода нет
}
ReadMode ('normal'); # Восстановить нормальные
# параметры терминала
Комментарий
Аргумент -1 функции ReadKey означает неблокирующее чтение символа. Если символа нет, ReadKey возвращает undef.
> Смотри также --------------------------------
Документация по модулю Term::ReadKey с CPAN; рецепт 15.6.
Требуется прочитать данные с клавиатуры без эхо-вывода не экране. Например, вы хотите прочитать пароль так, как это делает passwd, то есть без отображения пароля пользователя.
Решение
Воспользуйтесь модулем Term::ReadKey с CPAN, установите режим ввода noecho, после чего воспользуйтесь функцией Read Line:
use Term::ReadKey;
ReadMode 'noecho';
$password = ReadLine 0:
Комментарий
Пример 15.3 показывает, как организовать проверку пароля пользователя. Если в вашей системе используются скрытые пароли, getpwuid вернет зашифрованный пароль лишь привилегированному пользователю. Всем остальным в соответствующем поле базы данных возвращае
Пример 15.3. checkuser
#!/usr/bin/perl -w
# checkuser - чтение и проверка пароля пользователя
use Term::ReadKey;
print "Enter your password: ";
ReadMode 'noecho';
$password = ReadLine 0;
chomp $password;
ReadMode 'normal':
print "\n";
($username, $encrypted) = ( getpwuid $< )[0,1j;
if (crypt($password, $encrypted) ne $encrypted) {
die "You are not $username\n";
} else {
print "Welcome, $username\n";
}
> Смотри также ----------------
Документация по модулю Term::ReadKey с CPAN; man-страницы crypt(3) n passwd(5) вашей системы (если есть). Функции crypt и getpwuid описаны в perlfunc(\),
Вы хотите, чтобы пользователь мог отредактировать строку перед тем, как отсылать ее вам для чтения.
Решение
Воспользуйтесь стандартной библиотекой Term::ReadLine в сочетании с модулем Term::ReadLine::Gnu с CPAN:
use Term::ReadLine;
$term = Term::Readl_ine->new("APP DESCRIPTION"):
$OUT = $term->OUT || *STDOUT;
$term->addhistory($fake_line);
$line = $term->readline(PROMPT);
print $OUT "Any program output\n";
Комментарий
Программа из примера 15.4 работает как простейший командный интерпретатор. Она читает строку и передает ее для выполнения. Метод read line читает строку с терминала с поддержкой редактирования и вызова истории команд. Вводимая пользователем строка автомат
Пример 15.4. vbsh
#!/usr/bin/perl -w
# vbsh - очень плохой командный интерпретатор
use strict;
use Term::ReadLine;
use POSIX qw(:sys_wait_h);
my $term = Term::ReadLine->new("Simple Shell");
my $OUT = $term->OUT() Ц *STDOUT;
my $cmd;
while (defined ($cmd = $term->readline('$ ') )) { my @output = '$cmd';
my $exit_value = $? " 8;
my $signal_num = $? & 127;
my $dumped_core =$? & 128;
printf $OUT "Program terminated with status %d from signal %d%s\n",
$exit_value, $signal_num,
$dumped_core ? " (core dumped)" : "";
print Ooutput;
$term->addhistory($seed_line);
} Чтобы занести в историю команд свою строку, воспользуйтесь методом
addhistory:
$term->addhistory($seed_line); В историю нельзя заносить больше одной строки за раз. Удаление строк из истории команд выполняется методом remove_history, которому передается индекс в списке истории: 0 соответствует первому (самому старому) элементу, 1 - второму и т. д. до самых пос
$term->remove_history($line_number); Для получения списка истории команд используется метод GetHistory:
@history = $term->GetHistory;
Смотри также --------------------------------
Документация по стандартным модулям Term::ReadLine и Term::ReadLine::Gnu с CPAN.