`
helloyesyes
  • 浏览: 1305641 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论
阅读更多

终端输出

使用termios结构,我们可以控制键盘输入,但是如果在显示在屏幕上的输出上可以进行同样级别的控制也许会更好。在我们本章的开始,我们使用printf来向屏幕输出字符,但是却没有办法将输出定位在屏幕上的某个特定位置上。

终端类型


许多Unix系统使用终端,尽管在今天的许多情况下,终端也许实际上是一个运行终端程序的PC。从历史上来说,不同的生产产商提供了大量的硬件终端。尽管他们都是使用转义序列(以转义字符开始的字符串)来提供对光标与属性的控制,例如粗体与闪烁等,但是他们并没有以标准的方式来提供这些特性。某些老的终端同时还具有不同的滚动功能,当发送backspace滚动条也许会消失。

硬件终端的多样性对于那些希望编写控制屏幕以及运行在多个终端类型上的软件的程序员是一个极大的问题。例如,ANSI标准使用转义序列Escape+[+A来将光标上移一行,然而ADM-3a终端却使用单独的控制字符Ctrl+K。

要编写处理各种不同的连接到Unix系统上的终端类型的程序是一件极其困难的任务。程序也许要为每一个终端类型提供不同的源代码。

这样在一个名为terminfo的包中提供一个解决方案就显得并不惊奇。程序并不会迎合各种终端类型,相反,程序会查找一个终端类型数据库来得到正确的信息。在大多数的现代Unix系统中,包括Linux,这些已经被集成到一个名为curses的软件包中,这就是我们下一章要了解的内容。

在Linux上,我们也许要使用curses的一个名为ncurses的实现,并且要包含ncurses.h文件来提供我们terminfo函数的原型。terminfo函数本身声明在他们自己的头文件term.h中,或者至少以前是这种情况。而在新版本的Linux系统上,在terminfo与ncurses之间有一个模糊的界线,许多需要terminfo函数的程序必须同时包含ncurses头文件。为避免以后的混乱,现代的Linux发行版本同时提供一个与Unix系统更兼容的curses头文件与库。在这些系统上,我们推荐使用curses.h与-lcurses。

标识我们的终端类型


Linux环境包含一个变量,TERM,他被设置为我们正在使用的终端类型。通常他是在系统登陆时由系统自动设置的。系统管理员也许会为每一个直接连接到终端的用户设置一个默认的终端类型,这些用户也许是要提供终端类型的远程或是网络用户。TERM的值可以通过telnet协商,并通过rlogin传递。

用户可以查询shell来确定他正使用的终端类型。

$ echo $TERM
xterm
$

在这个例子中,shell是由一个名为xterm的程序来运行的,这是一个X Window系统的终端模拟器,或是提供类似功能的程序,例如KDE的Konsole或是Gnome的gnome-terminal。

terminfo软件包包含了一个功能与大量终端的转义序列的数据库,并且为程序员提供了统一的接口。这样编写的程序就可以在数据库扩展时利用未来终端的优点,而不是每一个程序都必须为不同的终端提供支持。

terminfo的功能是通过属性来描述的。这些属性存储在已编译的terminfo文件集合中,并且通常可以在/usr/lib/terminfo或是/usr/share/terminfo中找到。对于每一个终端(也有一些可以在terminfo中指定的打印机),有一个文件来定义其功能以及如何访问这些特性。为了避免创建一个非常大的目录,实际的文件存储在子目录中,而子目录的名字只是简单的终端类型的第一个字符。所以,VT100的定义可以在...terminfo/v/vt100中找到。

对于每一个终端类型都会以可读的源码的格式来编写一个terminfo文件,然后使用tic命令将其编译为应用程序可用的更为紧凑和高效的格式。奇怪的是,X/Open规范谈到源码以及编译的格式定义,但是却没有提到实际编译源码的tic命令。我们可以使用infocmp程序来输出一个已编译的terminfo实体的可读版本信息。

下面是一个VT100终端的terminfo文件的例子:

$ 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[?1l\E>,
rmso=\E[m$<2>, rmul=\E[m$<2>,
rs2=\E>\E[?3l\E[?4l\E[?5l\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^O$<2>, smacs=^N, smkx=\E[?1h\E=,
smso=\E[1;7m$<2>, smul=\E[4m$<2>, tbc=\E[3g,

每一个terminfo定义由三种类型的实体构成。每一个实体被称之为capname并且定义了一个终端功能。

布尔功能只是简单的指示一个终端是否支持一个特定的特性。例如,如果终端支持XON/XOFF流控制就会显示xon布尔功能。

数字功能定义了尺寸,例如lines定义了屏幕上的行数,而cols定义了屏幕上的列数。实际的数字是通过#字符与功能相区分的。要定义一个具有80列与24行的终端,我们可以写成cols#80,lines#24。

字符串功能要显得有些复杂。他们用于两种不同的功能:定义访问终端所需要的输出字符串以及定义当用户按下特定的按键时会接收的输入字符串,通常为功能键或是数字键盘上的特殊键。某些字符功能相当简单,例如el,表示"清除直到一行结束"。在一个VT100的终端上,要完成这个任务的转义序列为Esc+[+K。在terminfo的源码格式中则为el=\E[K。

特殊的键的定义与此相类似。例如,VT100上的功能键F1发送的转义序列为Esc+O+P,其定义为kf1=\EOP。

当转义序列需要一些参数时,其定义会显得有些复杂。大多数的终端可以将光标移动到一个特定的行与列位置。对于每一个可能的光标位置都会有一个不 的功能是不切实际的,所有使用一个通用字符串,并且带有参数定义当字符串被使用时要插入的值。例如,VT100终端使用转义序列Esc+[+<row>+;+<col>+H来将光标移到一个特定的位置。在terminfo源码格式中,其定义为cup=\E[%i%p1%d;%p2%dH$<5>。

其含义为:

\E:发送Escape
[:发送[字符
%i:增加参数
%p1:将第一个参数放入堆栈
%d:将堆栈上的数字作为十进制数字输出
;:发送;字符
%p2:将第二个参数放入堆栈
%d:将堆栈上的数字作为十进制数字输出
H:发送H字符。

这看起来似乎有一些复杂,但是却允参数以固定的顺序出现,独立于终端希望他们出现在最终的转义序列中的顺序。增加参数的%i是必须的,因为标准的光标位置是屏幕的左上角(0,0),但是VT100的光标位置为(1,1)。最后的$<5>表明需要等同于输出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设置为一个空指针,函数执行失败时就会输出一个诊断信息并且退出程序,如下面的例子所示:

#include <stdio.h>
#include <term.h>
#include <ncurses.h>
int main()
{
setupterm(“unlisted”,fileno(stdout),(int *)0);
printf(“Done.\n”);
exit(0);
}

运行在我们系统上的程序输出也许并不是这里给出的样子,但是其含义已经足够明显了。在这里并没有打印出Done,因为setupterm函数执行失败从而导致程序退出。

$ cc -o badterm badterm.c -I/usr/include/ncurses -lncurses
$ badterm
‘unlisted’: unknown terminal type.
$

注意上面例子中的编译命令:在这个Linux系统上,ncurses头文件位于/usr/include/ncurses目录,所以我们必须使用-I选项来指示编译器在这里进行查找。而某些Linux系统也许会将ncurses库可以由标准位置进行访问。在这些系统上,我们只需要简单的包含curses.h头文件,并且为库指定-lcurses选项。

对于我们的菜单选择函数,我们希望可以清屏,在屏幕上移动光标,并且可以屏幕上的任意位置写入。一旦我们调用了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,而tigetstr会返回(char *)-1。

下面我们使用程序sizeterm.c程序取得cols与lines功能来确定终端尺寸:

#include <stdio.h>
#include <term.h>
#include <ncurses.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来取得xterm终端类型的光标移动功能(cup),我们会得到一个参数化的答案: \E[%p1%d;%p2%dH。

这个功能需要两个参数:光标要移动到的行与列。这两个坐标都是由屏幕左上角的零点处开始计量的。

我们可以使用tparm函数用实际的值来代替功能中的参数。最多可以替换九个参数,并且会返回一个可用的转义序列:

#include <term.h>
char *tparm(char *cap, long p1, long p2, ..., long p9);

一旦我们使用tparm来组织终端转义序列,我们必须将其发送到终端。要正确的处理,我们不应使用printf来向终端发送字符串,相反,我们要使用特殊的函数,这些函数为终端完成一个操作的正确处理提供了必要的延时。这些函数为:

#include <term.h>
int putp(char *const str);
int tputs(char *const str, int affcnt, int (*putfunc)(int));

如果成功,putp返回OK,如果失败,则会返回ERR。putp函数将终端控制字符串作为参数并且将其发送到标准输出。

所以要移动到屏幕的第5行,第30列,我们可以使用下面的代码块:

char *cursor;
char *esc_sequence;
cursor = tigetstr(“cup”);
esc_sequence = tparm(cursor,5,30);
putp(esc_sequence);

tputs函数是为那些不可以通过stdout访问终端并且允许我们指定输出字符所使用的函数的情况而提供的。他会返回用户指定的函数putfunc的结要。affcnt参数用来指明更改会影响到的行数,通常将其设置为1。用于输出字符串的函数必须与putchar函数具有相同的参数与返回结果。事实上,putp(string)等同于调用tputs(string,1,putchar)。我们将会在下面的例子中的看到用用户指定的输出函数来使用tputs函数。

要小心,一些老的Linux发行版本将tputs函数的最后一个参数定义为int (*putfunc)(char),这会强制我们修改在我们的下一个试验中所定的char_to_terminal函数。

注意,如果我们查看tparm与终端功能的信息手册页,我们也许会遇到一个tgoto函数。他为移动光标提供了一个更为简单的方案,但是我们不使用这个函数的原因是因为X/Open规范并没有将其包含在1997版本中。所以我们推荐不要在新程序中使用这些函数。

现在我们准备好为我们的菜单选择功能添加屏幕处理了。还有一件需要做的事就是简单的使用clear来清除屏幕。某些终端并不支持clear功能,从而会使得光标停留在屏幕的左上角。在这种情况下,我们可以将光标放置在左上角,并且使用"删除直到显示结尾"命令ed。

将所有这些信息结合在一起,我们可以编写我们例子菜单程序的最终版本,screen-menu.c,在这里我们将会在屏幕上"画"出选项,从而供用户选择。

试验--完全终端控制

我们可以重新编写menu4.c的getchoice函数从而为我们提供完全的终端控制。在这个列表中,省略了main函数,因为他并没有改变。

#include <stdio.h>
#include <unistd.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) {
tputs(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;
}

工作原理

重写的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,包含了一个我们在第3章提到的putc函数调用。

要结束这一章,我们将会看一个如何检测击键的例子。
分享到:
评论

相关推荐

    linux 终端

    linux 终端 linux 终端 操作工具

    基于ARM的嵌入式Linux终端系统性能实时优化.pdf

    "基于ARM的嵌入式Linux终端系统性能实时优化" 本文主要讨论基于ARM的嵌入式Linux终端系统性能实时优化问题。传统的嵌入式Linux终端系统性能优化方法通常忽略了硬件方面的优化,导致整体优化结果不佳。本文提出了一...

    实验一_登录Linux终端.docx

    Linux 终端登录实验 Linux 终端登录实验是 Unix 操作系统的基础实验之一,本实验旨在让学生掌握 Linux 终端的基本操作,包括登录系统、使用 shell、创建新用户、切换虚拟终端等。 一、实验准备 在进行 Linux 终端...

    Linux终端的设计与应用

    ### Linux终端的设计与应用 #### 引言 随着信息技术的快速发展和全球化的加深,Linux作为一款开源的操作系统,正受到越来越多国家政府和大型跨国公司的关注。特别是在金融、海关、铁路、证券、航空、图书馆等领域...

    WinSCP linux 终端工具

    这款工具以其直观的图形界面、丰富的功能以及与Linux终端的无缝集成,成为了许多IT专业人员的首选。 **1. WinSCP的主要功能** - **文件传输**:WinSCP支持断点续传、批量传输和同步传输,使得在Linux服务器间移动...

    Linux终端实用技巧篇.docx

    Linux 终端实用技巧篇 本篇文章将详细介绍 Linux 终端中的实用技巧和命令,旨在帮助用户更好地掌握 Linux 终端的使用。以下是文章中的知识点总结: 一、基本命令 * Tab 键组合:在终端输入命令时,可以使用 Tab ...

    linux 终端快捷键.pdf

    Linux终端快捷键是提高操作效率的重要工具,尤其对于系统管理员和需要频繁使用命令行的用户来说,掌握它们可以显著提升工作效率和操作精准度。Linux系统自诞生以来,其命令行界面一直以其强大的灵活性和控制能力被...

    宁盾多因子认证(MFA)与Linux终端安全认证方案.pdf

    宁盾多因子认证(MFA)与Linux终端安全认证方案.pdf宁盾多因子认证(MFA)与Linux终端安全认证方案.pdf宁盾多因子认证(MFA)与Linux终端安全认证方案.pdf宁盾多因子认证(MFA)与Linux终端安全认证方案.pdf宁盾多因子认证...

    Linux终端下的直接写屏技术

    Linux终端下的直接写屏技术Linux终端下的直接写屏技术Linux终端下的直接写屏技术

    Teplayer linux终端播放器

    解码器用的是神器ffmpeg,所以在终端下可以播放大部分视频,图片,动态图片。使用时改变终端的字体大小,行数,列数,最大行数,最大列数可调节观看效果。播放器有两种播放模式,默认灰度播放,输入1使能颜色播放。...

    linux 终端和颜色控制说明

    很实用的linux 终端和颜色控制说明,觉得还不错,拿出来分享一下。

    linux终端常用命令总结

    linux终端常用命令总结,有需要或者忘记的可以看一下。

    Linux终端仿真软件SecureCRT-5.13.rar

    《SecureCRT:Linux终端仿真软件详解》 在IT领域,特别是Linux系统管理中,终端仿真软件扮演着至关重要的角色。SecureCRT是一款广受欢迎的终端模拟器,尤其在Windows平台上,它为用户提供了与Linux和Unix服务器进行...

    linux-超级终端

    Linux超级终端,也被称为控制台或TTY(Teletype),是Linux系统中一个重要的命令行界面,用于远程或本地访问操作系统。它为用户提供了一个与操作系统交互的文本接口,允许用户执行各种系统管理任务,如运行命令、...

    linux终端常用的快捷键

    这是一个txt的文本文件,主要总结了一些常见linux终端的快捷的使用。

    C#通过ssh调用linux终端及文件上传

    本教程将详细讲解如何使用C#通过SSH(Secure Shell)协议来调用Linux终端并实现文件上传。 SSH是一种网络协议,用于安全地远程登录到Linux或Unix服务器,执行命令,以及传输文件。C#中可以借助第三方库,如SSH.NET...

    linux终端设置(提供代码)

    ### Linux终端设置详解 在日常使用Linux的过程中,用户往往希望能够个性化自己的系统,使其不仅功能强大而且界面友好。本文将详细介绍如何对Linux终端进行个性化设置,包括改变终端的提示符、字体颜色等内容,让...

    向Linux终端服务器迁移.pdf

    【向Linux终端服务器迁移】 Linux操作系统以其开源、稳定和低成本的特性,在企业级应用中越来越受欢迎。本文将探讨如何通过K-12LTSP(Linux Terminal Server Project)软件包,帮助小型公司或学校实验室快速迁移至...

    当Linux终端出现混乱时,如何让它恢复正常

    本文将详细介绍如何在Linux终端出现混乱时,使其恢复正常。 ### 1. 使用`stty`命令恢复终端 `stty`命令是用于配置和查询终端输入输出特性的强大工具。当终端显示出现问题时,`stty`可以帮助我们恢复其默认设置。在...

Global site tag (gtag.js) - Google Analytics