• Чтение с терминала и запись на терминал
  • Сравнение канонического и неканонического режимов
  • Обработка перенаправленного вывода
  • Диалог с терминалом
  • Драйвер терминала A и общий терминальный интерфейс
  • Обзор
  • Аппаратная модель
  • Структура типа termios
  • Режимы ввода
  • Режимы вывода
  • Режимы управления
  • Локальные режимы
  • Специальные управляющие символы
  • Скорость терминала
  • Дополнительные функции
  • Вывод терминала
  • Тип терминала
  • Установите тип вашего терминала
  • Применение характеристик terminfo
  • Обнаружение нажатий клавиш
  • Виртуальные консоли
  • Псевдотерминалы
  • Резюме 
  • Глава 5

    Терминалы

    В этой главе вы познакомитесь с некоторыми улучшениями, которые вам, возможно, захочется внести в базовое приложение из главы 2. Его, быть может, самый очевидный недостаток — пользовательский интерфейс; он достаточно функционален, но не слишком элегантен. Теперь вы узнаете, как сделать более управляемым терминал пользователя, т. е. ввод с клавиатуры и вывод на экран. Помимо этого вы научитесь обеспечивать написанным вами программам возможность получения вводимых данных от пользователя даже при наличии перенаправления ввода и гарантировать вывод данных в нужное место на экране.

    Несмотря на то, что заново реализованное приложение для управления базой данных компакт-дисков не увидит свет до конца главы 7, его основы вы заложите в этой главе. Глава 6 посвящена curses, которые представляют собой вовсе не древнее проклятие, а библиотеку функций, предлагающих программный код высокого уровня для управления отображением на экране терминала. Попутно вы узнаете чуть больше о размышлениях прежних профи UNIX, познакомившись с основными принципами систем Linux и UNIX и понятием терминала. Низкоуровневый доступ, представленный в этой главе, быть может именно то, что вам нужно. Большая часть того, о чем мы пишем здесь, хорошо подходит для программ, выполняющихся в окне консоли, таких как эмуляторы терминала KDE's Konsole, GNOME's gnome-terminal или стандартный X11 xterm.

    В этой главе вы, в частности, узнаете о:

    □ чтении с терминала и записи на терминал;

    □ драйверах терминала и общем терминальном интерфейсе (General Terminal Interface, GTI);

    □ структуре типа

    termios
    ;

    □ выводе терминала и базе данных

    terminfo
    ;

    □ обнаружении нажатия клавиш.

    Чтение с терминала и запись на терминал

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

    getchar
    и
    printf
    для чтения из стандартного потока ввода и записи в стандартный поток вывода.

    В упражнении 5.1 в программе menu1.c вы попытаетесь переписать на языке С подпрограммы формирования меню, использующие только эти две функции.

    Упражнение 5.1. Подпрограммы формирования меню на языке C

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

    getchoice
    :

    #include <stdio.h>

    #include <stdlib.h>


    char *menu[] = {

     "a — add new record", "d — delete record", "q - quit", NULL,

    };


    int getchoice(char *greet, char *choices[]);

    2. Функция

    main
    вызывает функцию
    getchoice
    с образцом пунктов меню
    menu
    :

    int main() {

     int choice = 0;

     do {

      choice = getchoice("Please select an action", menu);

      printf("You have chosen: %c\n", choice);

     } while (choice != 'q');

     exit(0);

    }

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

    int getchoice(char *greet, char *choices[]) {

     int chosen = 0;

     int selected;

     char **option;

     do {

      printf("Choice: %s\n", greet);

      option = choices;

      while (*option) {

       printf("%s\n", *option);

       option++;

      }

      selected = getchar();

      option = choices;

      while (*option) {

       if (selected == *option[0]) {

        chosen = 1;

        break;

       }

       option++;

      }

      if (!chosen) {

       printf("Incorrect choice, select again\n");

      }

     } while (!chosen);

     return selected;

    }

    Как это работает

    Функция

    getchoice
    выводит на экран приглашение для ввода
    greet
    и меню
    choices
    и просит пользователя ввести первый символ выбранного пункта. Далее выполняется цикл до тех пор, пока функция
    getchar
    не вернет символ, совпадающий с первой буквой одного из элементов массива option.

    Когда вы откомпилируете и выполните программу, то обнаружите, что она ведет себя не так, как ожидалось. Для того чтобы продемонстрировать возникающую проблему, далее приведен вариант диалога на экране терминала.

    $ ./menu1

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    a

    You have chosen: a

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    Incorrect choice, select again

    Choice: Please select an action

    а — add new record

    d — delete record

    q — quit

    q

    You have chosen: q $

    Для того чтобы сделать выбор, пользователь должен последовательно нажать клавиши <А>, <Enter>, <Q>, <Enter>. Здесь возникают, как минимум, две проблемы; самая серьезная заключается в том, что вы получаете сообщение "Incorrect choice" ("Неверный выбор") после каждого корректного выбора. Кроме того, вы еще должны нажать клавишу <Enter> (или <Return>), прежде чем программа считает введенные данные.

    Сравнение канонического и неканонического режимов

    Обе эти проблемы тесно связаны. По умолчанию ввод терминала не доступен программе до тех пор, пока пользователь не нажмет клавишу <Enter> или <Return>. В большинстве случаев это достоинство, поскольку данный способ позволяет пользователю корректировать ошибки набора с помощью клавиш <Backspace> или <Delete>. Только когда он остается доволен увиденным на экране, пользователь нажимает клавишу <Enter>, чтобы ввод стал доступен программе.

    Такое поведение называется каноническим или стандартным режимом. Весь ввод обрабатывается как последовательность строк. Пока строка ввода не завершена (обычно с помощью нажатия клавиши <Enter>), интерфейс терминала управляет всеми нажатыми клавишами, включая <Backspace>, и приложение не может считать ни одного символа.

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

    Помимо всего прочего, обработчик терминала в ОС Linux помогает превращать символы прерываний в сигналы (например, останавливающие выполнение программы, когда вы нажмете комбинацию клавиш <Ctrl>+<C>), он также может автоматически выполнить обработку нажатых клавиш <Backspace> и <Delete> и вам не придется реализовывать ее в каждой написанной вами программе. О сигналах вы узнаете больше в главе 11.

    Итак, что же происходит в данной программе? ОС Linux сохраняет ввод до тех пор, пока пользователь не нажмет клавишу <Enter>, и затем передает в программу символ выбранного пункта меню и следом за ним код клавиши <Enter>. Каждый раз, когда вы вводите символ пункта меню, программа вызывает функцию

    getchar
    , обрабатывает символ и снова вызывает
    getchar
    , немедленно возвращающую символ клавиши <Enter>.

    Символ, который на самом деле видит программа, — это не символ ASCII возврата каретки CR (десятичный код 13, шестнадцатеричный 0D), а символ перевода строки LF (десятичный код 10, шестнадцатеричный 0A). Так происходит потому, что на внутреннем уровне ОС Linux (как и UNIX) всегда применяет перевод строки для завершения текстовых строк, т. е. в отличие от других ОС, таких как MS-DOS, использующих комбинацию символов возврата каретки и перевода строки, ОС UNIX применяет, для обозначения новой строки только символ перевода строки. Если вводное или выводное устройство посылает или запрашивает и символ возврата каретки, в ОС Linux об этом заботится обработчик терминала. Если вы привыкли работать в MS-DOS или других системах, это может показаться странным, но одно из существенных преимуществ заключается в отсутствии в ОС Linux реальной разницы между текстовыми и бинарными файлами. Символы возврата каретки обрабатываются, только когда вы вводите или выводите их на терминал или некоторые принтеры и плоттеры.

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

    do {

     selected = getchar();

    } while (selected == '\n');

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

    $ ./menu1

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    a

    You have chosen: a

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    q

    You have chosen: q $

    Мы вернемся позже ко второй проблеме, связанной с необходимостью нажимать клавишу <Enter>, и более элегантному решению для обработки символа перевода строки.

    Обработка перенаправленного вывода

    Для программ, выполняющихся в ОС Linux, даже интерактивных, характерно перенаправление своего ввода и вывода как в файлы, так и в другие программы. Давайте рассмотрим поведение вашей программы при перенаправлении ее вывода в файл.

    $ ./menu1 > file

    a

    q

    $

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

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

    isatty
    . Вы просто передаете ему корректный дескриптор файла, и он проверяет, связан ли этот дескриптор в данный момент с терминалом.

    #include <unistd.h>

    int isatty(int fd);

    Системный вызов

    isatty
    возвращает 1, если открытый дескриптор файла
    fd
    связан с терминалом, и 0 в противном случае.

    В данной программе используются файловые потоки, но

    isatty
    оперирует только дескрипторами файлов. Для выполнения необходимого преобразования вам придется сочетать вызов
    isatty
    с подпрограммой
    fileno
    , обсуждавшейся в главе 3.

    Что вы собираетесь делать, если стандартный вывод

    stdout
    перенаправлен? Просто завершить программу — не слишком хорошо, потому что у пользователя нет возможности выяснить, почему программа аварийно завершила выполнение. Вывод сообщения в
    stdout
    тоже не поможет, поскольку оно будет перенаправлено с терминала. Единственное решение — записать сообщение в стандартный поток ошибок
    stderr
    , который не перенаправляется командой оболочки
    > file
    (упражнение 5.2).

    Упражнение 5.2. Проверка для выявления перенаправления вывода

    Внесите следующие изменения в директивы включения заголовочных файлов и функцию main программы menu1.с из упражнения 5.1. Назовите новый файл menu2.c.

    #include <unistd.h>

    ...

    int main() {

     int choice = 0;

     if (!isatty(fileno(stdout))) {

      fprintf(stderr, "You are not a terminal!\n");

      exit(1);

     }

     do {

      choice = getchoice("Please select an action", menu);

      printf("You have chosen: %c\n", choice);

     } while (choice != 'q');

     exit(0);

    }

    Теперь посмотрите на следующий пример вывода:

    $ ./menu2

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    q

    You have chosen: q $ ./menu2 > file

    You are not a terminal! $

    Как это работает

    В новом фрагменте программного кода функция

    isatty
    применяется для проверки связи стандартного вывода с терминалом и прекращения выполнения программы при отсутствии этой связи. Это тот же самый тест, который командная оболочка использует для решения, нужно ли выводить строки приглашения. Возможно и довольно обычно перенаправление и
    stdout
    , и
    stderr
    с терминала на другое устройство. Вы можете направить поток ошибок в другой файл:

    $ ./menu2 >file 2>file.error

    $

    или объединить оба выводных потока в одном файле:

    $ ./menu2 >file 2>&1

    $

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

    Диалог с терминалом

    Если нужно защитить части вашей программы, взаимодействующие с пользователем, от перенаправления, но разрешить его для других входных и выходных данных, вы должны отделить общение с пользователем от потоков

    stdout
    и
    stderr
    . Это можно сделать, непосредственно считывая данные с терминала и прямо записывая данные на терминал. Поскольку ОС Linux с самого начала создавалась, как многопользовательская система, включающая, как правило, множество терминалов, как непосредственно подсоединенных, так и подключенных по сети, как вы сможете определить тот терминал, который следует использовать?

    К счастью, Linux и UNIX облегчают жизнь, предоставляя специальное устройство /dev/tty, которое всегда является текущим терминалом или сеансом работы в системе (login session). Поскольку ОС Linux все интерпретирует как файлы, вы можете выполнять обычные файловые операции для чтения с устройства /dev/tty и записи на него.

    В упражнении 5.3 вы исправите программу выбора пункта меню так, чтобы можно было передавать параметры в подпрограмму

    getchoice
    и благодаря этому лучше управлять выводом. Назовите ее menu3.c.

    Упражнение 5.3. Применение /dev/tty

    Загрузите файл menu2.c и измените программный код так, чтобы входные и выходные данные приходили с устройства /dev/tty и направлялись на это устройство.

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>


    char *menu[] = {

     "a — add new record", "d — delete record", "q - quit", NULL,

    };

    int getchoice(char* greet, char* choices[], FILE* in, FILE* out);


    int main() {

     int choice = 0;

     FILE* input;

     FILE* output;

     if (!isatty(fileno(stdout))) {

      fprintf(stderr, "You are not a terminal, OK.\n");

     }

     input = fopen("/dev/tty", "r");

     output = fopen("/dev/tty", "w");

     if (!input || !output) {

      fprintf(stderr, "Unable to open /dev/tty\n");

      exit(1);

     }

     do {

      choice = getchoice("Please select an action", menu, input, output);

      printf("You have chosen: %c\n", choice);

     } while (choice != 'q');

     exit(0);

    }


    int getchoice(char* greet, char *choices[], FILE* in, FILE *out) {

     int chosen = 0;

     int selected;

     char **option;

     do {

      fprintf(out, "Choice: %s\n", greet);

      option = choices;

      while (*option) {

       fprintf(out, "%s\n", *option);

       option++;

      }

      do {

       selected = fgetc(in);

      } while(selected == '\n');

      option = choices;

      while (*option) {

       if (selected == *option[0]) {

        chosen = 1;

        break;

       }

       option++;

      }

      if (!chosen) {

       fprintf(out, "Incorrect choice, select again\n");

      }

     } while (!chosen);

     return selected;

    }

    Теперь, когда вы выполните программу с перенаправленным выводом, вы сможете увидеть строки приглашения, а стандартный вывод программы (обозначающий выбранные пункты меню) перенаправляется в файл, который можно просмотреть позже:

    $ ./menu3 > file

    You are not a terminal, OK.

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    d

    Choice: Please select an action

    a — add new record

    d - delete record

    q — quit

    q

    $ cat file

    You have chosen: d

    You have chosen: q

    Драйвер терминала A и общий терминальный интерфейс

    Иногда программе нужно более мощные средства управления терминалами, чем простые файловые операции. ОС Linux предоставляет ряд интерфейсов, позволяющих управлять поведением драйвера терминала и обеспечивающих больше возможностей управления вводом и выводом терминала.

    Обзор

    Как показано на рис. 5.1, вы можете управлять терминалом с помощью вызовов набора функций общего терминального интерфейса (General Terminal Interface, GTI), разделяя их на применяемые для чтения и для записи. Такой подход сохраняет ясность интерфейса данных (чтение/запись), позволяя при этом искусно управлять поведением терминала. Нельзя сказать, что терминальный интерфейс ввода/вывода очень понятен — он вынужден иметь дело с множеством разнообразных физических устройств.

    Рис. 5.1 


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

    К основным функциям, которыми вы можете управлять, относятся следующие:

    □ редактирование строки — применение для редактирования клавиши <Backspace>;

    □ буферизация — считывание символов сразу или после настраиваемой задержки;

    □ отображение — управление отображением так же, как при считывании паролей;

    □ CR/LF — отображение для ввода и вывода: что происходит при выводе символа перевода строки (\n);

    □ скорости передачи данных по линии — редко применяется для консоли ПК, эти скорости очень важны для модемов и терминалов на линиях последовательной передачи.

    Аппаратная модель

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

    Концептуальная схема (физическая модель на некоторых старых узлах UNIX подобна данной) включает машину с ОС UNIX, подключенную через последовательный порт с модемом и далее по телефонной линии с другим модемом к удаленному терминалу (рис. 5.2). На деле это просто вариант установки, применявшийся некоторыми малыми провайдерами интернет-услуг "на заре туманной юности" Интернета. Эта модель отдаленно напоминает организацию "клиент — сервер", при использовании которой программа выполняется на большом компьютере, а пользователи работают на терминалах ввода/вывода.

    Рис. 5.2


    Если вы работаете на ПК под управлением ОС Linux, эта модель может показаться чересчур сложной. Однако, поскольку у обоих авторов есть модемы, мы можем при желании использовать программу эмуляции терминала, например, minicom для запуска удаленного сеанса работы в системе на любой другой машине, подобной этой, с помощью пары модемов и телефонной линии связи. Конечно, сегодня быстрый широкополосный доступ вытеснил из потребления эту рабочую модель, но она до сих пор не лишена некоторых достоинств.

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

    Структура типа termios

    Тип

    termios
    — стандартный интерфейс, заданный стандартом POSIX и похожий на интерфейс
    termio
    системы System V. Интерфейс терминала управляется значениями в структуре типа
    termios
    и использует небольшой набор вызовов функций. И то и другое определено в заголовочном файле termios.h.

    Примечание

    Программы, применяющие вызовы функций, определенных в файле termios.h, нуждаются в компоновке с соответствующей библиотекой функций. Ею может быть в зависимости от установленной у вас системы просто стандартная библиотека С или библиотека curses. При необходимости во время компиляции примеров этой главы добавьте аргумент

    -lcurses
    в конец строки команды компиляции. В некоторых более старых системах Linux библиотека curses представлена в версии, известной под названием "new curses". В этих случаях имя библиотеки и аргумент компоновки становятся
    ncurses
    и
    -lncurses
    соответственно.

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

    □ ввод;

    □ вывод;

    □ управление;

    □ локальный;

    □ специальные управляющие символы.

    Минимальная структура типа

    termios
    обычно объявляется следующим образом (хотя в стандарте X/Open разрешено включение дополнительных полей):

    #include <termios.h>

    struct termios {

     tcflag_t c_iflag;

     tcflag_t c_oflag;

     tcflag_t c_cflag;

     tcflag_t c_lflag;

     cc_t c_cc[NCCS];

    };

    Имена элементов структуры соответствуют пяти типам параметров из предыдущего перечня.

    Инициализировать структуру типа

    termios
    для терминала можно, вызвав функцию
    tcgetattr
    со следующим прототипом или описанием:

    #include <termios.h>

    int tcgetattr(int fd, struct termios *termios_p);

    Этот вызов записывает текущие значения переменных интерфейса терминала в структуру, на которую указывает параметр

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

    #include <termios.h>

    int tcsetattr(int fd, int actions, const struct termios *termios_p);

    Поле

    actions
    функции
    tcsetattr
    управляет способом внесения изменений. Есть три варианта:

    TCSANOW
    — изменяет значения сразу;

    TSCADRAIN
    — изменяет значения, когда текущий вывод завершен;

    TCSAFLUSH
    — изменяет значения, когда текущий вывод завершен, но отбрасывает любой ввод, доступный в текущий момент и все еще не возвращенный вызовом
    read
    .

    Примечание

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

    Теперь рассмотрим более подробно режимы и связанные с ними вызовы функций. Некоторые характеристики режимов довольно специализированные и редко применяются, поэтому мы остановимся только на основных. Если вы хотите знать больше, просмотрите страницы интерактивного справочного руководства вашей системы либо скопируйте стандарт POSIX или X/Open.

    Наиболее важный режим, который следует принять во внимание при первом прочтении, — локальный (local). Канонический и неканонический режимы — решение второй проблемы в вашем первом приложении: пользователь должен нажимать клавишу <Enter> или <Return> для чтения программой входных данных. Вам следует заставить программу ждать всю строку ввода или набрасываться на ввод, как только он набран на клавиатуре.

    Режимы ввода

    Режимы ввода управляют тем, как обрабатывается ввод (символы, полученные драйвером терминала от последовательного порта или клавиатуры) до передачи его в программу. Вы управляете вводом, устанавливая флаги в элементе

    c_iflag
    структуры
    termios
    . Все флаги определены как макросы и могут комбинироваться с помощью поразрядной операции
    OR
    . Это свойственно всем режимам терминала.

    В элементе

    c_iflag
    могут применяться следующие макросы:

    □ 

    BRKINT
    — генерирует прерывание, когда в линии связи обнаруживается разрыв (потеря соединения);

    □ 

    IGNBRK
    — игнорирует разрывы соединения в линии связи;

    □ 

    ICRNL
    — преобразует полученный символ возврата каретки в символ перехода на новую строку;

    □ 

    IGNCR
    — игнорирует полученные символы возврата каретки;

    □ 

    INLCR
    — преобразует полученные символы перехода на новую строку в символы возврата каретки;

    □ 

    IGNPAR
    — игнорирует символы с ошибками четности;

    □ 

    INCPK
    — выполняет контроль четности у полученных символов;

    □ 

    PARMRK
    — помечает ошибки четности;

    □ 

    ISTRIP
    — обрезает (до семи битов) все входные символы;

    □ 

    IXOFF
    — включает программное управление потоком при вводе;

    □ 

    IXON
    — включает программное управление потоком при выводе.

    Примечание

    Если флаги

    BRKINT
    и
    IGNBRK
    не установлены, сбой на линии связи считывается как символ
    NULL
    (0x00).

    Вам не придется часто изменять режимы ввода, поскольку обычно стандартные значения — наиболее подходящие, и поэтому мы больше не будем их обсуждать.

    Режимы вывода

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

    terminfo
    , которую вы примените позже в этой главе.

    Вы управляете режимами вывода, устанавливая флаги элемента

    c_oflag
    структуры типа
    termios
    . В элементе
    c_oflag
    могут применяться следующие макросы:

    □ 

    OPOST
    — включает обработку вывода;

    □ 

    ONLCR
    — преобразует в символ перевода строки пару символов возврат каретки/перевод строки;

    □ 

    OCRNL
    — преобразует любой символ возврата каретки в выводе в символ перевода строки;

    □ 

    ONOCR
    — не выводит символ возврата каретки в столбце 0;

    □ 

    ONLRET
    — символ перехода на новую строку выполняет возврат каретки;

    □ 

    OFILL
    — посылает символы заполнения для формирования задержки;

    □ 

    OFDEL
    — применяет символ
    DEL
    как заполнитель вместо символа
    NULL
    ;

    □ 

    NLDLY
    — выбор задержки для символа перехода на новую строку;

    □ 

    CRDLY
    — выбор задержки для символа возврата каретки;

    □ 

    TABDLY
    — выбор задержки для символа табуляции;

    □ 

    BSDLY
    — выбор задержки для символа
    Backspace
    ;

    □ 

    VTDLY
    — выбор задержки для символа вертикальной табуляции;

    □ 

    FFDLY
    — выбор задержки для символа прокрутки страницы.

    Примечание

    Если флаг

    OPOST
    не установлен, все остальные флаги игнорируются.

    Режимы вывода тоже обычно не используются, поэтому мы не будем их обсуждать в дальнейшем.

    Режимы управления

    Эти режимы управляют аппаратными характеристиками терминала. Вы задаете режимы управления, устанавливая флаги элемента

    c_cflag
    структуры типа
    termios
    , включающие следующие макросы:

    □ 

    CLOCAL
    — игнорирует управление линиями с помощью модема;

    □ 

    CREAD
    — включает прием символов;

    □ 

    CS5
    — использует пять битов в отправляемых и принимаемых символах;

    □ 

    CS6
    — использует шесть битов в отправляемых и принимаемых символах;

    □ 

    CS7
    — использует семь битов в отправляемых и принимаемых символах;

    □ 

    CS8
    — использует восемь битов в отправляемых и принимаемых символах;

    □ 

    CSTOPB
    — устанавливает два стоповых бита вместо одного;

    □ 

    HUPCL
    — выключает управление линиями модема при закрытии;

    □ 

    PARENB
    — включает генерацию и проверку четности;

    □ 

    PARODD
    — применяет контроль нечетности вместо контроля четности.

    Примечание

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

    HUPCL
    установлен, он устанавливает линии управления модема в состояние останова (hang-up).

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

    termios
    .

    Локальные режимы

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

    c_iflag
    структуры
    termios
    с помощью следующих макросов:

    □ 

    ECHO
    — включает локальное отображение вводимых символов;

    □ 

    ECHOE
    — выполняет комбинацию
    Backspace
    ,
    Space
    ,
    Backspace
    при получении символа
    ERASE
    (стереть);

    □ 

    ECHOK
    — стирает строку при получении символа
    KILL
    ;

    □ 

    ECHONL
    — отображает символы перехода на новую строку;

    □ 

    ICANON
    — включает стандартную обработку ввода (см. текст, следующий за данным перечнем);

    □ 

    IEXTEN
    — включает функции, зависящие от реализации;

    □ 

    ISIG
    — включает генерацию сигналов;

    □ 

    NOFLSH
    — отключает немедленную запись очередей;

    □ 

    TOSTOP
    — посылает сигнал фоновым процессам при попытке записи.

    Два самых важных флага в этой группе —

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

    Специальные управляющие символы

    Специальные управляющие символы — это коллекция символов подобных символам от комбинации клавиш <Ctrl>+<C>, действующих особым образом, когда пользователь вводит их. В элементе

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

    Массив

    c_cc
    используется двумя очень разными способами, зависящими от того, установлен для терминала канонический режим (т.е. установлен флаг
    ICANON
    в элементе
    c_lflag
    структуры
    termios
    ) или нет.

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

    Для канонического режима применяются следующие индексы:

    □ 

    VEOF
    — символ
    EOF
    ;

    □ 

    VEOL
    — дополнительный символ конца строки
    EOL
    ;

    □ 

    VERASE
    — символ
    ERASE
    ;

    □ 

    VINTR
    — символ прерывания
    INTR
    ;

    □ 

    VKILL
    — символ уничтожения
    KILL
    ;

    □ 

    VQUIT
    — символ завершения
    QUIT
    ;

    □ 

    VSUSP
    — символ приостанова
    SUSP
    ;

    □ 

    VSTART
    — символ запуска
    START
    ;

    □ 

    VSTOP
    — символ останова
    STOP
    .

    Для канонического режима применяются следующие индексы:

    □ 

    VINTR
    — символ
    INTR
    ;

    □ 

    VMIN
    — минимальное значение
    MIN
    ;

    □ 

    VQUIT
    — символ
    QUIT
    ;

    □ 

    VSUSP
    — символ
    SUSP
    ;

    □ 

    VTIME
    — время ожидания
    TIME
    ;

    □ 

    VSTART
    — символ
    START
    ;

    □ 

    VSTOP
    — символ
    STOP
    .

    Символы

    Поскольку для более сложной обработки вводимых символов специальные символы и неканонические значения очень важны, мы описываем их в табл. 5.1.


    Таблица 5.1

    Символ Описание
    INTR
    Заставляет драйвер терминала отправить сигнал
    SIGINT
    процессам, подключенным к терминалу. Мы обсудим сигналы более подробно в главе 11
    QUIT
    Заставляет драйвер терминала отправить сигнал
    SIGQUIT
    процессам, подключенным к терминалу
    ERASE
    Заставляет драйвер терминала удалить последний символ в строке
    KILL
    Заставляет драйвер терминала удалить всю строку
    EOF
    Заставляет драйвер терминала передать все символы строки во ввод, считываемый приложением. Если строка пустая, вызов
    read
    вернет ноль символов, как будто он встретил на конец файла
    EOL
    Действует как ограничитель строки в дополнение к более привычному символу перехода на новую строку
    SUSP
    Заставляет драйвер терминала послать сигнал
    SIGSUSP
    процессам, подключенным к терминалу. Если ваша система UNIX поддерживает управление заданиями, текущее приложение будет приостановлено
    STOP
    Действует как "прерыватель потока", т. е. прекращает дальнейший вывод на терминал. Применяется для поддержки управления потоком XON/XOFF и обычно задается как ASCII-символ
    XOFF
    (<Ctrl>+<S>)
    START
    Возобновляет вывод после символа
    STOP
    , часто ASCII-символ
    XON
    Значения TIME и MIN

    Значения

    TIME
    и
    MIN
    применяются только в неканоническом режиме и действуют вместе для управления считыванием входных данных. Вместе они управляют действиями при попытке программы прочесть дескриптор файла, ассоциированный с терминалом.

    Возможны четыре варианта.

    □ 

    MIN = 0
    и
    TIME = 0
    . В этом случае вызов
    read
    всегда завершается сразу же. Если какие-то символы доступны, они будут возвращены, если нет, то
    read
    вернет ноль, и никакие символы не будут считаны.

    □ 

    MIN = 0
    и
    TIME > 0
    . В этом случае вызов
    read
    завершится, когда все доступные символы будут считаны или когда пройдет
    TIME
    десятых долей секунды. Если нет прочитанных символов из-за превышения отпущенного времени,
    read
    вернет 0. В противном случае он вернет количество прочитанных символов.

    □ 

    MIN > 0
    и
    TIME = 0
    . В этом случае вызов
    read
    будет ждать до тех пор, пока можно будет считать
    MIN
    символов, и затем вернет это количество символов. В случае конца файла возвращается 0.

    □ 

    MIN > 0
    и
    TIME > 0
    . Это самый сложный случай. После вызова
    read
    ждет получения символа. Когда первый символ получен, каждый раз при получении последующего символа запускается межсимвольный таймер (или перезапускается, если он уже был запущен). Вызов
    read
    завершится, когда либо можно будет считать
    MIN
    символов, либо межсимвольное время превысит
    TIME
    десятых долей секунды. Это может пригодиться для подсчета разницы между единственным нажатием клавиши <Esc> и запуском функциональной клавиатурной escape-последовательности. Тем не менее следует знать, что сетевые соединения или высокая загрузка процессора могут полностью стереть такие полезные сведения о времени.

    Установив неканонический режим и используя значения

    MIN
    и
    TIME
    , программы могут выполнять посимвольную обработку ввода.

    Доступ к режимам терминала из командной оболочки

    Если вы хотите просмотреть параметры

    termios
    , находясь в командной оболочке, примените следующую команду для получения их списка:

    $ stty -a

    На установленных у авторов системах Linux, обладающих структурами

    termios
    с некоторыми расширениями по сравнению со стандартными, получен следующий вывод:

    speed 38400 baud; rows 24; columns 80; line = 0;

    intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;

    eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;

    werase = ^W; lnext = ^V; flush = ^O, min = 1; time = 0;

    -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts

    -ignbrk -brkint -ignpar -parmirk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff

    -iuclc -ixany -imaxbe1 iutf8

    opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel n10 cr0 tab0 bs0 vt0 ff0

    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt

    echoctl echoke

    Среди прочего, как видите, символ

    EOF
    — это <Ctrl>+<D>, и включено отображение. Экспериментируя с установками терминала, легко получить в результате терминал в нестандартном режиме, что затруднит его дальнейшее использование. Есть несколько способов справиться с этой трудностью.

    □ Первый способ — применить следующую команду, если ваша версия

    stty
    поддерживает ее:

    $ stty sane

    Если вы потеряли преобразование клавиши возврата каретки в символ перехода на новую строку (который завершает строку), возможно, потребуется ввести

    stty sane
    , но вместо нажатия клавиши <Enter> нажать комбинацию клавиш <Ctrl>+<J> (которая обозначает переход на новую строку).

    □ Второй способ — применить команду

    stty -g
    и записать текущие установки
    stty
    в форму, готовую к повторному считыванию. В командной строке вы можете набрать следующее:

    $ stty -g > save_stty

    ...

    <эксперименты с параметрами>

    ...

    $ stty $(cat save_stty)

    В финальной команде

    stty
    вам все еще придется использовать комбинацию клавиш <Ctrl>+<J> вместо клавиши <Enter>. Ту же самую методику можно применить и в сценариях командной оболочки.

    save_stty="$(stty -g)"

    <изменение stty-параметров>

    stty $save_stty

    □ Если вы все еще в тупике, третий способ — перейти на другой терминал, применить команду

    ps
    для поиска оболочки, которую вы сделали непригодной, и затем использовать команду
    kill hup <id процесса>
    для принудительного завершения этой командной оболочки. Поскольку перед выводом регистрационного приглашения параметры
    stty
    всегда восстанавливаются, у вас появится возможность нормально зарегистрироваться в системе еще раз.

    Задание режимов терминала из командной строки

    Вы также можете применять команду

    stty
    для установки режимов терминалов непосредственно из командной строки.

    Для установки режима, в котором ваш сценарий командной оболочки сможет выполнять посимвольное считывание, вы должны отключить канонический режим и задать 1 и 0. Команда будет выглядеть следующим образом:

    $ stty -icanon min 1 time 0

    Теперь терминал будет считывать символы немедленно, вы можете попробовать выполнить еще раз первую программу menu1. Вы увидите, что она работает, как первоначально и предполагалось.

    Вы также могли бы улучшить вашу попытку проверки пароля (см. главу 2), отключив отображение перед приглашением ввести пароль. Команда, выполняющая это действие, должна быть следующей:

    $ stty -echo

    Примечание

    Не забудьте применить команду

    stty echo
    для возврата отображения после ваших экспериментов!

    Скорость терминала

    Последняя функция, обслуживаемая структурой

    termios
    , — манипулирование скоростью линии передачи. Для этой скорости не определен отдельный элемент структуры; она управляется вызовами функций. Скорости ввода и вывода обрабатываются отдельно.

    Далее приведены четыре прототипа вызовов:

    #include <termios.h> 

    speed_t cfgetispeed(const struct termios *);

    speed_t cfgetospeed(const struct termios *);

    int cfsetispeed(struct termios *, speed_t speed);

    int cfsetospeed(struct termios *, speed_t speed);

    Обратите внимание на то, что они воздействуют на структуру

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

    В вызовах перечисленных функций допускается задание разных значений скорости

    speed
    , но к основным относятся следующие константы:

    □ 

    B0
    — отключение терминала;

    □ 

    B1200
    — 1200 бод;

    □ 

    B2400
    — 2400 бод;

    □ 

    B9600
    — 9600 бод;

    □ 

    B19200
    — 19 200 бод;

    □ 

    B38400
    — 38 400 бод.

    Не существует скоростей выше 38 400 бод, задаваемых стандартом, и стандартного метода обслуживания последовательных портов на более высоких скоростях.

    Примечание

    В некоторых системах, включая Linux, для выбора более высоких скоростей определены константы

    В57600
    ,
    B115200
    и
    В230400
    . Если вы пользуетесь более старой версией ОС Linux и эти константы недоступны, можно применить команду
    setserial
    для получения нестандартных скоростей 57 600 и 115 200. В этом случае указанные скорости будут использоваться при выборе константы B38400. Оба эти метода непереносимы, поэтому применяйте их с осторожностью.

    Дополнительные функции

    Есть небольшое число дополнительных функций для управления терминалами. Они работают непосредственно с дескрипторами файлов без необходимости считывания и записывания структур типа

    termios
    .

    #include <termios.h>

    int tcdrain(int fd);

    int tcflow(int fd, int flowtype);

    int tcflush(int fd, int in_out_selector);

    Функции предназначены для следующих целей:

    □ 

    tcdrain
    — заставляет вызвавшую программу ждать до тех пор, пока не будет отправлен весь поставленный в очередь вывод;

    □ 

    tcflow
    — применяется для приостановки или возобновления вывода;

    □ 

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

    Теперь, когда мы уделили довольно много внимания структуре

    termios
    , давайте рассмотрим несколько практических примеров. Возможно, самый простой из них — отключение отображения при чтении пароля (упражнение 5.4). Это делается сбрасыванием флага
    echo
    .

    Упражнение 5.4. Программа ввода пароля с применение
    termios

    1. Начните вашу программу password.с со следующих определений:

    #include <termios.h>

    #include <stdio.h>

    #include <stdlib.h>

    #define PASSWORD_LEN 8


    int main() {

     struct termios initialrsettings, newrsettings;

     char password[PASSWORD_LEN + 1];

    2. Далее добавьте строку, считывающую текущие установки из стандартного ввода и копирующую их в только что созданную вами структуру типа

    termios
    :

     tcgetattr(fileno(stdin), &initialrsettings);

    3. Создайте копию исходных установок, чтобы восстановить их в конце. Сбросьте флаг

    ECHO
    в переменной
    newrsettings
    и запросите у пользователя его пароль:

     newrsettings = initialrsettings;

     newrsettings.с_lflag &= ~ЕСНО;

     printf("Enter password: ");

    4. Далее установите атрибуты терминала в newrsettings и считайте пароль. И наконец, восстановите первоначальные значения атрибутов терминала и выведите пароль на экран, чтобы свести на нет все предыдущие усилия по обеспечению безопасности:

     if (tcsetattr(fileno(stdin), TCSAFLUSH, &newrsettings) != 0) {

      fprintf(stderr, "Could not set attributes\n");

     } else {

      fgets(password, PASSWORD_LEN, stdin);

      tcsetattr(fileno(stdin), TCSANOW, &initialrsettings);

      fprintf(stdout, "\nYou entered %s\n", password);

     }

     exit(0);

    }

    Когда вы выполните программу, то увидите следующее:

    $ ./password

    Enter password: You entered hello

    $
     

    Как это работает

    В этом примере слово

    hello
    набирается на клавиатуре, но не отображается на экране в строке приглашения
    Enter password:
    . Никакого вывода нет до тех пор, пока пользователь не нажмет клавишу <Enter>.

    Будьте осторожны и изменяйте с помощью конструкции

    X&=~FLAG
    (которая очищает бит, определенный флагом
    FLAG
    в переменной
    X
    ) только те флаги, которые вам нужно изменить. При необходимости можно воспользоваться конструкцией
    X|=FLAG
    для установки одиночного бита, определяемого
    FLAG
    , хотя в предыдущем примере она не понадобилась.

    Для установки атрибутов применяется действие

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

    Другой распространенный пример использования структуры

    termios
    — перевод терминала в состояние, позволяющее вам считывать каждый набранный символ (упражнение 5.5). Для этого отключается канонический режим и используются параметры
    MIN
    и
    TIME
    .

    Упражнение 5.5. Считывание каждого символа

    Применяя только что полученные знания, вы можете изменить программу menu. Приведенная далее программа menu4.c базируется на программе menu3.c и использует большую часть кода из файла password.с, включенного в нее. Внесенные изменения выделены цветом и объясняются в пунктах описания.

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

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <termios.h>


    char *menu[] = {

     "a — add new record",

     "d — delete record",

     "q - quit",

     NULL,

    };

    2. Затем нужно объявить пару новых переменных в функции

    main
    :

    int getchoice(char *greet, char *choices[], FILE *in, FILE *out);


    int main() {

     int choice = 0;

     FILE *input;

     FILE *output;

     struct termios initial_settengs, new_settings;

    3. Перед вызовом функции

    getchoice
    вам следует изменить характеристики терминала, этим определяется место следующих строк:

     if (!isatty(fileno(stdout))) {

      fprintf(stderr, "You are not a terminal, OK.\n");

     }

     input = fopen("/dev/tty", "r");

     output = fopen("/dev/tty", "w");

     if (!input || !output) {

      fprintf(stderr, "Unable to open /dev/tty\n");

      exit(1);

     }

     tcgetattr(fileno(input), &initial_settings);

     new_settings = initial_settings;

     new_settings.c_lfag &= ~ICANON;

     new_settings.c_lflag &= ~ECHO;

     new_settings.c_cc[VMIN] = 1;

     new_settings.c_cc[VTIME] = 0;

     new_settings.c_lflag &= ~ISIG;

     if (tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {

      fprintf(stderr, "could not set attributes\n");

     }

    4. Перед завершением вы также должны вернуть первоначальные значения:

     do {

      choice = getchoice("Please select an action", menu, input, output);

      printf("You have chosen: %c\n", choice);

     } while (choice != 'q');

     tcsetattr(fileno(input), TCSANOW, &initial_settings);

     exit(0);

    }

    5. Теперь, когда вы в неканоническом режиме, необходимо проверить на соответствие возвраты каретки, поскольку стандартное преобразование CR (возврат каретки) в LF (переход на новую строку) больше не выполняется:

    int getchoice (char *greet, char *choices[], FILE *in, FILE *out) {

     int chosen = 0;

     int selected;

     char **option;

     do {

      fprintf(out, "Choice: %s\n", greet);

      option = choices;

      while (*option) {

       fprintf(but, "%s\n", *option);

       option++;

      }

      do {

       selected = fgetc(in);

      } while (selected == '\n' || selected == '\r');

      option = choices;

      while (*option) {

       if (selected == *option[0]) {

        chosen = 1;

        break;

       }

       option++;

      }

      if (!chosen) {

       fprintf(out, "Incorrect choice, select again\n");

      }

     } while(!chosen);

     return selected;

    }

    Пока вы не устроите все иначе, теперь, если пользователь нажмет в вашей программе комбинацию клавиш <Ctrl>+<C>, программа завершится. Вы можете отключить обработку этих специальных символов, очистив флаг

    ISIG
    в локальных режимах. Для этого в функцию
    main
    включается следующая строка:

    new_settings.c_lflag &= ~ISIG;

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

    $ ./menu4

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    You have chosen: a

    Choice: Please select an action

    a — add new record

    d — delete record

    q — quit

    You have chosen: q $

    Если вы нажмете комбинацию клавиш <Ctrl>+<C>, символ будет передан прямо в программу и будет истолкован, как неверный выбор.

    Вывод терминала

    С помощью структуры типа

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

    Тип терминала

    Во многих системах UNIX применяются терминалы, несмотря на то, что сегодня во многих случаях "терминал" может на самом деле быть ПК, выполняющим программу эмуляции терминала или терминальным приложением в оконной среде, таким как xterm в графической оболочке X11.

    Исторически существовало очень большое число аппаратных терминалов разных производителей. Несмотря на то, что почти все они применяют escape-последовательности (строки символов, начинающиеся с escape-символа) для управления положением курсора и другими атрибутами, такими как жирное начертание или мерцание, способы реализации управления при этом слабо стандартизованы. У некоторых старых моделей терминалов также разные характеристики прокрутки экрана, который может очищаться или не очищаться, когда посылается символ

    Backspace
    , и т.д.

    Примечание

    Существует стандарт ANSI для набора escape-последовательностей (в основном базирующихся на последовательностях, применяемых в серии VT-терминалов компании Digital Equipment Corporation, но не идентичных им). Многие терминальные программы обеспечивают эмуляцию стандартного аппаратного терминала, часто VT100, VT220 или ANSI, а иногда и других типов.

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

    Escape, [, A
    для перемещения курсора вверх на одну строку. Терминал ADM-За (очень распространенный несколько лет назад) использует один управляющий символ от комбинации клавиш <Ctrl>+<K>.

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

    Как ни странно, решение существует в пакете, известном как terminfo. Вместо необходимости обслуживания любого типа терминала в каждой программе, ей достаточно просмотреть базу данных типов терминалов для получения корректной информации. В большинстве современных систем UNIX, включая Linux, эта база данных объединена с другим пакетом, названным curses, о котором вы узнаете в следующей главе.

    Для применения функций terminfo вы, как правило, должны подключить заголовочный файл curses.h пакета curses и собственный заголовочный файл term.h пакета terminfo. В некоторых системах Linux вам, возможно, придется применять реализацию curses, известную как ncurses, и включить файл ncurses.h для предоставления прототипов вашим функциям terminfo.

    Установите тип вашего терминала

    Окружение ОС Linux содержит переменную

    TERM
    , которая хранит тип используемого терминала. Обычно она устанавливается системой автоматически во время регистрации в системе. Системный администратор может задать тип терминала по умолчанию для каждого непосредственно подключенного терминала и может сформировать подсказку с типом терминала для удаленных сетевых пользователей. Значение
    TERM
    может быть передано
    rlogin
    через telnet.

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

    $ echo $TERM

    xterm

    $

    В данном случае оболочка выполняется из программы, называемой xterm — эмулятора терминала для графической оболочки X Window System, или программы, обеспечивающей "такие же функциональные возможности, как KDE's Konsole или GNOME's gnome-terminal.

    Пакет terminfo содержит базу данных характеристик и управляющих escape-последовательностей для большого числа терминалов и предоставляет единообразный программный интерфейс для их использования. Отдельная программа, таким образом, сможет извлечь выгоду от применения новых моделей терминалов по мере расширения базы данных и не заботиться о поддержке множества разных терминалов.

    Характеристики терминалов в terminfo описываются с помощью атрибутов. Они хранятся в наборе откомпилированных файлов terminfo, которые обычно находятся в каталогах /usr/lib/terminfo или /usr/share/terminfo. Для каждого терминала (и многих принтеров, которые тоже могут быть заданы в terminfo) есть файл, в котором определены характеристики терминала и способ доступа к его функциям. Для того чтобы не создавать слишком большого каталога, реальные файлы хранятся в подкаталогах, имена которых — первый символ типа терминала. Так определение терминала VT100 можно найти в файле …terminfo/v/vt100.

    Файлы terminfo пишутся по одному на каждый тип терминала в исходном формате, пригодном (или почти пригодном!) для чтения, который затем компилируется командой

    tic
    в более компактный и эффективный формат, используемый прикладными программами. Странно, стандарт X/Open ссылается на описания исходного и откомпилированного формата, но не упоминает команду
    tic
    , необходимую для реального преобразования исходного формата в откомпилированный. Для вывода пригодной для чтения версии откомпилированного элемента набора terminfo можно использовать программу infocmp.

    Далее приведен пример файла terminfo для терминала VT100:

    $ infocmp vt100

    vt100|vt100-am|dec vt100 (w/advanced video),

     am, mir, msgr, xenl, xon, cols#80, it#8, lines#24, vt#3,

     acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,

     bel=^G, blink=\E[5m$<2>, bold=\E[1m$<2>,

     clear=\E[H\E[J$<50>, cr=\r, csr=\E[%i%p1%d;%p2%dr,

     cub=\E[%p1%dD, cub1=\b, cud=\E[%p1%dB, cud1=\n,

     cuf=\E[%p1%dC, cuf1=\E[C$<2>,

     cup=\E[%i%p1%d; %p2%dH$<5>, cuu=\E[%p1%dA,

     cuu1=\E[A$<2>, ed=\E[J$<50>, el=\E[K$<3>,

     el1=\E[1K$<3>, enacs=\E(B\E)0, home=\E[H, ht=\t,

     hts=\EH, ind=\n, ka1=\EOq, ka3=\EOs, kb2=\EOr, kbs=\b,

     kc1=\EOp, kc3=\EOn, kcub1=\EOD, kcud1=\EOB,

     kcuf1=\EOC, kcuu1=\EOA, kent=\EOM, kf0=\EOy, kf1=\EOP,

     kf10=\EOx, kf2=\EOQ, kf3=\EOR, kf4=\EOS, kf5=\EOt,

     kf6=\EOu, kf7=\EOv, kf8=\EOl, kf9=\EOw, rc=\E8,

     rev=\E[7m$<2>, ri=\EM$<5>, rmacs=^O, rmkx=\E[?11\E>,

     rmso=\E[m$<2>, rmul=\E[m$<2>,

     rs2=\E>\E[?31\E[?41\E[?51\E[?7h\E[?8h, sc=\E7,

     sgr=\E[0%?%p1%p6%|%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t^N%e^O%;,

     sgr0=\E[m^0$<2>, smacs=^N, smkx=\E[?1h\E=,

     smso=\E[1;7m$<2>; smul=\E[4m$<2>, tbc=\E[3g,

    Каждое определение в

    terminfo
    состоит из трех типов элементов. Каждый элемент называется
    capname
    (имя характеристики) и определяет характеристику терминала.

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

    xon
    присутствует, если терминал поддерживает управление потоком
    XON/XOFF
    .

    Числовые характеристики определяют размеры или объемы, например

    lines
    — это количество строк на экране, a
    cols
    — количество столбцов. Число отделяется от имени характеристики символом
    #
    . Для описания терминала с 80 столбцами и 24 строками следует написать
    cols#80, lines#24
    .

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

    el
    , что означает "стереть до конца строки". Для того чтобы сделать это на терминале VT100, потребуется escape-последовательность
    Esc, [, K
    . В исходном формате terminfo это записывается как
    еl=\Е[K
    .

    Специальные клавиши определены аналогичным образом. Например, функциональная клавиша <F1> на терминале VT100 посылает последовательность

    Esc, O, P
    , которая определяется как
    kf1=\EOP
    .

    Все несколько усложняется, если escape-последовательности требуются какие-либо параметры. Большинство терминалов могут перемещать курсор в заданные строку и столбец. Ясно, что неразумно хранить отдельную характеристику для каждой точки экрана, в которую можно переместить курсор, поэтому применяется общая строковая характеристика с параметрами, определяющими значения, которые вставляются при использовании характеристики. Например, терминал VT100 использует последовательность

    Esc, [, <row>, <col>, H
    для перемещения курсора в заданную позицию. В исходном формате terminfo это записывается довольно устрашающе:
    cup=\E[%i%p1%d;%p2%dH$<5>
    .

    Эта строка означает следующее:

    □ 

    \E
    — послать escape-символ;

    □ 

    [
    — послать символ
    [
    ;

    □ 

    %i
    — дать приращение аргументам;

    □ 

    %p1
    — поместить первый аргумент в стек;

    □ 

    %d
    — вывести число из стека как десятичное;

    □ 

    ;
    — послать символ
    ;
    ;

    □ 

    %р2
    — поместить второй аргумент в стек;

    □ 

    %d
    — вывести число из стека как десятичное;

    □ 

    H
    —послать символ
    H
    .

    Данная запись кажется сложной, но позволяет задавать параметры в строгом порядке, не зависящем от порядка, в котором терминал ожидает их появления в финальной escape-последовательности. Приращение аргументов

    %i
    необходимо, поскольку стандартная адресация курсора задается, начиная от верхнего левого угла экрана (0, 0), а терминал VT100 обозначает начальную позицию курсора как (1, 1). Заключительные символы
    $<5>
    означают, что для обработки терминалом перемещения курсора требуется задержка, эквивалентная времени вывода пяти символов.

    Примечание

    Мы могли бы описывать огромное множество характеристик, но, к счастью, в основном системы UNIX и Linux приходят с большинством предопределенных терминалов. Если нужно добавить новую модель терминала, вы можете найти полный список характеристик на странице интерактивного справочного руководства, посвященной terminfo. Лучше всего начать с поиска включенного в базу данных терминала, похожего на ваш новый, и затем создания описания новой модели как вариации существующего, т. е. осуществить последовательный просмотр характеристик, одну за другой, и исправление нуждающихся в корректировке.

    Применение характеристик terminfo

    Теперь, когда вы знаете, как определить характеристики терминала, нужно научиться обращаться к ним. Когда используется terminfo, прежде всего вам нужно задать тип терминала, вызвав функцию

    setupterm
    . Она инициализирует структуру
    TERMINAL
    для текущего типа терминала. После этого вы сможете запрашивать характеристики терминала и применять его функциональные возможности. Делается это с помощью вызова
    setupterm
    , подобного приведенному далее:

    #include <term.h>

    int setupterm(char *term, int fd, int *errret);

    Библиотечная функция

    setupterm
    задает текущий тип терминала в соответствии с заданным параметром
    term
    . Если
    term
    — пустой указатель, применяется переменная окружения
    TERM
    . Открытый дескриптор файла, предназначенный для записи на терминал, должен передаваться в параметре
    fd
    . Результат функции хранится в целой переменной, на которую указывает
    errret
    , если это не пустой указатель. Могут быть записаны следующие значения:

    □ -1 — нет базы данных terminfo;

    □ 0 — нет совпадающего элемента в базе данных terminfo;

    □ 1 — успешное завершение.

    Функция

    setupterm
    возвращает константу
    OK
    в случае успешного завершения и
    ERR
    в случае сбоя. Если на параметр
    errret
    установлен как пустой указатель,
    setupterm
    выведет диагностическое сообщение и завершит программу в случае своего аварийного завершения, как в следующем примере:

    #include <stdio.h>

    #include <term.h>

    #include <curses.h>

    #include <stdlib.h>


    int main() {

     setupterm("unlisted", fileno(stdout), (int *)0);

     printf("Done.\n");

     exit(0);

    }

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

    Done.
    " не выводится, поскольку функция
    setupterm
    после своего аварийного завершения вызвала завершение программы:

    $ cc -о badterm badterm.с -lncurses

    $ ./badterm

    'unlisted': unknown terminal type.

    $

    Обратите внимание на строку компиляции в примере: в этой системе Linux мы используем реализацию ncurses библиотеки curses со стандартным заголовочным файлом, находящимся в стандартном каталоге. В таких системах вы можете просто включить файл curses.h и задать

    -lncurses
    для библиотеки.

    В функции выбора пункта меню хорошо было бы иметь возможность очищать экран, перемещать курсор по экрану и записывать его положение на экране. После вызова функции

    setupterm
    вы можете обращаться к характеристикам базы данных terminfo с помощью вызовов трех функций, по одной на каждый тип характеристики:

    #include <term.h>

    int tigetflag(char *capname);

    int tigetnum(char *capname);

    char *tigetstr(char *capname);

    Функции

    tigetflag
    ,
    tigetnum
    и
    tigetstr
    возвращают значения характеристик terminfo булева или логического, числового и строкового типов соответственно. В случае сбоя (например, характеристика не представлена)
    tigetflag
    вернет -1,
    tigetnum
     — -2, a
    tigetstr
    — (char*)-1.

    Вы можете применять базу данных terminfo для определения размера экрана терминала, извлекая характеристики

    cols
    и
    lines
    с помощью следующей программы sizeterm.c:

    #include <stdio.h>

    #include <term.h>

    #include <curses.h>

    #include <stdlib.h>


    int main() {

     int nrows, ncolumns;

     setupterm(NULL, fileno(stdout), (int *)0);

     nrows = tigetnum("lines");

     ncolumns = tigetnum("cols");

     printf("This terminal has %d columns and %d rows\n", ncolumns, nrows);

     exit(0);

    }


    $ echo $TERM

    vt100

    $ ./sizeterm

    This terminal has 80 columns and 24 rows

    Если запустить эту программу в окне рабочей станции, вы получите результат, отражающий размер текущего окна:

    $ echo $TERM

    xterm

    $ ./sizeterm

    This terminal has 88 columns and 40 rows

    $

    Если применить функцию

    tigetstr
    для получения характеристики перемещения курсора (
    cup
    ) терминала типа xterm, вы получите параметризованный ответ:
    \Е[%p1%d;%p2%dH
    .

    Этой характеристике требуются два параметра: номер строки и номер столбца, в которые перемещается курсор. Обе координаты измеряются, начиная от нулевого значения в левом верхнем углу экрана.

    Вы можете заменить параметры в характеристике реальными значениями с помощью функции

    tparm
    . До девяти параметров можно заменить значениями и получить в результате применяемую escape-последовательность символов.

    #include <term.h>

    char *tparm(char *cap, long p1, long p2, ..., long p9);

    После формирования escape-последовательности с помощью

    tparm
    , ее нужно отправить на терминал. Для корректной обработки этой последовательности не следует пересылать строку на терминал с помощью функции
    printf
    . Вместо нее примените одну из специальных функций, обеспечивающих корректную обработку любых задержек, необходимых для завершения операции, выполняемой терминалом. К ним относятся следующие:

    #include <term.h>

    int putp(char *const str);

    int tputs(char *const str, int affcnt, int (*putfunc)(int));

    В случае успешного завершения функция

    putp
    вернет константу
    OK
    ,в противном случае —
    ERR
    . Эта функция принимает управляющую строку терминала и посылает ее в стандартный вывод stdout.

    Итак, для перемещения в строку 5 и столбец 30 на экране можно применить блок программного кода, подобный приведенному далее:

    char *cursor;

    char *esc_sequence;

    cursor = tigetstr("cup");

    esc_sequence = tparm(cursor, 5, 30);

    putp(esc_sequence);

    Функция

    tputs
    предназначена для ситуаций, в которых терминал не доступен через стандартный вывод
    stdout
    , и позволяет задать функцию, применяемую для вывода символов. Она возвращает результат заданной пользователем функции
    putfunc
    . Параметр
    affcnt
    предназначен для обозначения количества строк, подвергшихся изменению. Обычно он устанавливается равным 1. Функция, используемая для вывода строки, должна иметь те же параметры и возвращать тип значения как у функции
    putfunc
    . В действительности
    putp(string)
    эквивалентна вызову
    tputs (string, 1, putchar)
    . В следующем примере вы увидите применение функции
    tputs
    , используемой с функцией вывода, определенной пользователем.

    Имейте в виду, что в некоторых старых дистрибутивах Linux последний параметр функции

    tputs
    определен как
    int (*putfunc)(char)
    , что заставит вас изменить определение функции
    char_to_terminal
    из упражнения 5.6.

    Примечание

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

    tparm
    и характеристиках терминалов, то можете встретить функцию
    tgoto
    . Причина, по которой мы не используем эту функцию, хотя она, очевидно, предлагает более легкий способ перемещения курсора, заключается в том, что она не включена в стандарт X/Open (Single UNIX Specification Version 2) по данным издания 1997 г. Следовательно, мы не рекомендуем применять любую из этих функций в ваших новых программах.

    Вы почти готовы добавить обработку экрана в вашу функцию выбора пункта меню. Единственно, что осталось, — очистить экран просто с помощью свойства

    clear
    . Некоторые терминалы не поддерживают характеристику
    clear
    , которая помещает курсор в левый верхний угол экрана. В этом случае вы можете поместить курсор в левый верхний угол и применить команду
    ed
    — удалить до конца экрана.

    Для того чтобы собрать всю полученную информацию вместе, напишем окончательную версию примера программы выбора пункта меню screenmenu.c, в которой вы "нарисуете" варианты пунктов меню на экране для того, чтобы пользователь выбрал нужный пункт (упражнение 5.6).

    Упражнение 5.6. Полное управление терминалом

    Вы можете переписать функцию

    getchoice
    из программы menu4.c для предоставления полного управления терминалом. В этом листинге функция
    main
    пропущена, потому что она не меняется. Другие отличия от программы menu4.c выделены цветом.

    #include <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <termios.h>

    #include <term.h>

    #include <curses.h>


    static FILE* output_stream = (FILE *)0;

    char *menu[] = {

     "a — add new record",

     "d — delete record",

     "q - quit",

     NULL,

    };


    int getchoice(char *greet, char *choices[], FILE *in, FILE *out);

    int char_to_terminal(int_char_to_write);


    int main() {

     ...

    }


    int getchoice(char *greet, char* choices[], FILE[]* in, FILE* out) {

     int chosen = 0;

     int selected;

     int screenrow, screencol = 10;

     char **option;

     char* cursor, *clear;

     output_stream = out;

     setupterm(NULL, fileno(out), (int*)0);

     cursor = tigetstr("cup");

     clear = tigetstr("clear");

     screenrow =4;

     tputs(clear, 1, (int*)char_to_terminal);

     tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);

     fprintf(out, "Choice: %s", greet);

     screenrow += 2;

     option = choices;

     while (*option) {

      ftputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);

      fprintf(out, "%s", *option);

      screenrow++;

      option++

     }

     fprintf(out, "\n");

     do {

      fflush(out);

      selected = fgetc(in);

      option = choices;

      while (*option) {

       if (selected == *option[0]) {

        chosen = 1;

        break;

       }

       option++;

      }

      if (!chosen) {

       tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);

       fprintf(out, "Incorrect choice, select again\n");

      }

     } while (!chosen);

     tputs(clear, 1, char_to_terminal);

     return selected;

    }


    int char_to_terminal(int char_to_write) {

     if (output_stream) putc(char_to_write, output_stream);

     return 0;

    }

    Сохраните эту программу как menu5.с.

    Как это работает

    Переписанная функция

    getchoice
    выводит то же меню, что и в предыдущих примерах, но подпрограммы вывода изменены так, чтобы можно было воспользоваться характеристиками из базы данных
    terminfo
    . Если вы хотите видеть на экране сообщение "You have chosen:" дольше, чем одно мгновение перед очисткой экрана и подготовкой его к следующему выбору пункта меню, добавьте в функцию
    main
    вызов
    sleep
    :

    do {

     choice = getchoice("Please select an action", menu, input, output);

     printf("\nYou have chosen: %c\n", choice);

     sleep(1);

    } while (choice != 'q');

    Последняя функция в этой программе

    char_to_terminal
    включает в себя вызов функции
    putc
    , которую мы упоминали в главе 3.

    В завершение этой главы бегло рассмотрим пример определения нажатий клавиш.

    Обнаружение нажатий клавиш

    Пользователи, программировавшие в ОС MS-DOS, часто ищут в ОС Linux эквивалент функции

    kbhit
    , которая определяет, была ли нажата клавиша, без реального ее считывания. К сожалению, их поиски оказываются безуспешными, поскольку прямого аналога нет. Программисты в среде UNIX не ощущают этого отсутствия, т.к. UNIX запрограммирована так, что программы очень редко (если когда-либо вообще) озабочены ожиданием события. Поскольку это обычный способ применения
    kbhit
    , ее нехватка редко ощущается в системах UNIX и Linux.

    Однако, когда вы переносите программы из MS-DOS, часто удобно эмулировать функцию

    kbhit
    , которую можно применять на деле в неканоническом режиме ввода (упражнение 5.7).

    Упражнение 5.7. Исключительно ваша собственная
    kbhit

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

    peek_character
    применяется для проверки нажатия клавиши. Далее описываются функции, которые будут использоваться позже:

    #include <stdio.h>

    #include <stdlib.h>

    #include <termios.h>

    #include <term.h>

    #include <curses.h>

    #include <unistd.h>


    static struct termios initial_settings, new_settings;

    static int peek_character = -1;

    void init_keyboard();

    void close_keyboard();

    int kbhit();

    int readch();

    2. Функция main вызывает функцию

    init_keyboard
    для настройки терминала, затем выполняет цикл один раз в секунду, каждый раз вызывая в нем функцию
    kbhit
    . Если нажата клавиша <q>, функция
    close_keyboard
    восстанавливает нормальный режим и программа завершается:

    int main() {

     int ch = 0;

     init_keyboard();

     while (ch != 'q') {

      printf("looping\n");

      sleep(1);

      if (kbhit()) {

       ch = readch();

       printf("you hit %c\n", ch);

      }

     }

     close_keyboard();

     exit(0);

    }

    3. Функции

    init_keyboard
    и
    close_keyboard
    настраивают терминал в начале и конце программы:

    void init_keyboard() {

     tcgetattr(0, &initial_settings);

     new_settings = initial_settings;

     new_settings.c_lflag &= ~ICANON;

     new_settings.c_lflag &= ~ECHO;

     new_settings.c_lflag &= ~ISIG;

     new_settings.c_cc[VMIN] = 1;

     new_settings.c_cc[VTIME] = 0;

     tcsetattr(0, TCSANOW, &new_settings);

    }


    void close_keyboard() {

     tcsetattr(0, TCSANOW, &initial_settings);

    }

    4. Теперь функция, проверяющая нажатие клавиши:

    int kbhit() {

     char ch;

     int nread;

     if (peek_character != -1) return 1;

     new_settings.c_cc[VMIN] = 0;

     tcsetattr(0, TCSANOW, &new_settings);

     nread = read(0, sch, 1);

     newrsettings.c_cc[VMIN] = 1;

     tcsetattr(0, TCSANOW, &new_settings);

     if (nread == 1) {

      peek_character = ch;

      return 1;

     }

     return 0;

    }

    5. Нажатый символ считывается следующей функцией

    readch
    , которая затем восстанавливает значение -1 переменной
    peek_character
    для выполнения следующего цикла:

    int readch() {

     char ch;

     if (peek_character != -1) {

      ch = peek_character;

      peek_character = -1;

      return ch;

     }

     read(0, &ch, 1);

     return ch;

    }

    Когда вы выполните программу (kbhit.c), то получите следующий вывод:

    $ ./kbhit

    looping

    looping

    looping

    you hit h

    looping

    looping

    looping

    you hit d

    looping

    you hit q

    $

    Как это работает

    Терминал настраивается в функции

    init_keyboard
    на считывание одного символа (
    MIN=1, TIME=0
    ). Функция
    kbhit
    изменяет это поведение на проверку ввода и его немедленный возврат (
    MIN=0, TIME=0
    ) и затем восстанавливает исходные установки перед завершением.

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

    Виртуальные консоли

    ОС Linux предоставляет средство, называемое виртуальными консолями. Экран, клавиатуру и мышь одного ПК может использовать ряд терминальных устройств, доступных на этом компьютере. Обычно установка ОС Linux рассчитана на использование от 8 до 12 виртуальных консолей. Виртуальные консоли становятся доступными благодаря символьным устройствам /dev/ttyN, где N — номер, начинающийся с 1.

    Если вы регистрируетесь в вашей системе Linux в текстовом режиме, как только система активизируется, вам будет предложено регистрационное приглашение. Далее вы регистрируетесь с помощью имени пользователя и пароля. В этот момент используемое вами устройство — первая виртуальная консоль, терминальное устройство /dev/tty1.

    С помощью команд

    who
    и
    ps
    вы можете увидеть, кто зарегистрировался и какие командная оболочка и программы выполняются на этой виртуальной консоли:

    $ who

    neil tty1 Mar 8 18:27

    $ ps -e

     PID TTY      TIME CMD

    1092 tty1 00:00:00 login

    1414 tty1 00:00:00 bash

    1431 tty1 00:00:00 emacs

    Из этого укороченного вывода видно, что пользователь neil зарегистрировался и запустил редактор Emacs на консоли ПК, устройстве /dev/tty1.

    Обычно ОС Linux запускается с процессом

    getty
    , выполняющимся на первых шести виртуальных консолях, поэтому есть возможность зарегистрироваться шесть раз, используя одни и те же экран, клавиатуру и мышь. Увидеть эти процессы можно с помощью и команды
    ps
    :

    $ ps -а

     PID TTY      TIME CMD

    1092 tty1 00:00:00 login

    1093 tty2 00:00:00 mingetty

    1094 tty3 00:00:00 mingetty

    1095 tty4 00:00:00 mingetty

    1096 tty5 00:00:00 mingetty

    1097 tty6 00:00:00 mingetty

    В этом выводе представлен стандартный вариант программы getty для обслуживания консоли в системе SUSE, mingetty, выполняющийся на пяти следующих виртуальных консолях и ожидающий регистрации пользователя.

    Переключаться между виртуальными консолями можно с помощью комбинации клавиш <Ctrl>+<Alt>+<FN>, где N — номер виртуальной консоли, на которую вы хотите переключиться. Таким образом, для того чтобы перейти на вторую виртуальную консоль, нажмите <Ctrl>+<Alt>+<F2>, и <Ctrl>+<Alt>+<F1>, чтобы вернуться на первую консоль. (При переключении из регистрации в текстовом режиме, а не графическом, также работает комбинация клавиш <Ctrl>+<FN>.)

    Если в Linux запущена регистрация в графическом режиме, либо с помощью программы startx илн менеджера экранов xdm, на первой свободной консоли, обычно /dev/tty7, стартует графическая оболочка X Window System. Переключиться с нее на текстовую, консоль вы сможете с помощью комбинации клавиш <Ctrl>+<Alt>+<FN>, а вернуться с помощью <Ctrl>+<Alt>+<F7>.

    В ОС Linux можно запустить более одного сеанса X. Если вы сделаете это, скажем, с помощью следующей команды

    $ startx -- :1

    Linux запустит сервер X на следующей свободной виртуальной консоли, в данном случае на /dev/tty8, и переключаться между ними вы сможете с помощью комбинаций клавиш <Ctrl>+<Alt>+<F8> и <Ctrl>+<Alt>+<F7>.

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

    Псевдотерминалы

    У многих UNIX-подобных систем, включая Linux, есть средство, именуемое псевдотерминалом. Это устройства, очень похожие на терминалы, которые мы использовали в данной главе, за исключением того, что у них нет связанного с ними оборудования. Они могут применяться для предоставления терминалоподобного интерфейса другим программам.

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

    Одно время реализация псевдотерминалов (если вообще существовала) сильно зависела от конкретной системы. Сейчас они включены в стандарт Single UNIX Specification (единый стандарт UNIX) как UNIX98 Pseudo-Terminals (псевдотерминалы стандарта UNIX98) или PTY.

    Резюме 

    В этой главе вы узнали о трех аспектах управления терминалом. В начале главы рассказывалось об обнаружении перенаправления и способах прямого диалога с терминалом в случае перенаправления дескрипторов стандартных файлов. Вы посмотрели на аппаратную модель терминала и немного познакомились с его историей. Затем вы узнали об общем терминальном интерфейсе и структуре termios, предоставляющей в ОС Linux возможность тонкого управления и манипулирования терминалом. Вы также увидели, как применять базу данных terminfo и связанные с ней функции для управления в аппаратно-независимом стиле выводом на экран, и познакомились с приемами мгновенного обнаружения нажатий клавиш. В заключение вы узнали о виртуальных консолях и псевдотерминалах. 








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх