本文只是学渣作者的学习经历,能帮到读者是作者的荣幸。功能没有实现完全,希望各位多多提意见。
more命令基本功能实现:‘q’--退出,空格--下一页,回车键--下一行,清屏幕显示,关闭回显,执行命令无需按键enter.
支持多种用法: more file ls /bin/ | more more < file
在实现ls /bin/ | more 时,需要将显示输入和用户命令输入分开,即打开/dev/tty来实现命令输入。
不足:1.在每次空格或回车后,"more?"都会显示出来
2.没有显示百分数
3.没有对文件类型进行判断--可以通过magic number实现
1)读写函数的用法---在实现more命令时的总结
fopen\fclose
1.FILE *fopen(const char *filename, const char *mode)
fopen打开有filename指定的文件,并把它与一个文件流关联起来。
成功打开,返回一个非空FILE *指针,失败返回NULL。
filename可以是某个text文件,也可以是某个设备名,比如/dev/tty
mode 是打开模式,可以是 r--只读。w--写方式,新内容覆盖就的内容。a--写方式,新内容追加在文件尾。a+ -- 更新的方式打开,追加。其它。
2. int fclose(FILE *stream)
关闭指定的文件流stream
fread\fwrite
1. size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream)
fread 将文件流中的数据读到ptr所指空间中。
fread(buf,sizeof(char),strlen(buf),stream);
buf是空闲内存空间,一般是 char buf[1024]
sizeof(char)是一个char类型的大小,也是fread一次读取的大小
strlen(buf)是读取的次数,一般是数组长度。
stream是fopen返回的非空文件流指针。
读取与stream关联的文件,每次读sizeof(char)大小,读strlen(buf)次。
fread 返回的是成功读到数据缓冲区里的记录个数。这里是strlen(buf)个记录。
读到buf中的数据,系统自动在数据最后添加一个“\n”
2.size_t fwrite(const void *ptr,size_t size,size_t nitems, FILE *stream)
fwrite将ptr所指空间的内容写到文件流中。
用法与fread相似
fgets\fputs
1. char *fgets(char *s,int n,FILE *stream)
fgets把读到的字符写到s指向的字符串里,知道出现以下情况;遇到换行符,已经传输了n-1个字符,或者到达文件尾。
成功完成,返回一个指向字符串s的指针; 出错,返回一个空指针; 到达文件尾,fgets会设置这个文件流的EOF标识,并返回一个空指针。
2. int fputs(const char *s, FILE *stream)
fputs将s指向字符串写到文件流stream中(不自动写入字符串结束标记符'\0')。
成功完成,返回非负数; 失败返回EOF。
fseek----本实例中没有用到 ,但通过它读取文件magic number来实现对文件类型的判断
1. int fseek(FILE *stream,long int offset, int whence)
fseek在文件流里为下一次读写操作指定位置。
offset--指定位置,与whence联用
whence取值:
SEEK_SET --- 表明offset是相对于文件头的一个相对值,则从offset处读取数据(其实是相对于文件头的)
SEEK_CUR --- 表明offset是相对于当前位置的一个相对值,则从当前位置偏移offset出读取数据
SEEK_END --- 表明offset是相对于文件尾的一个相对值,则从距文件尾offset处开始读取数据。特别注意:此处的offset是负值
fseek 返回0表示成功 ,返回-1表示失败,并设置errno指出错误。
memset
1. void *memset(void *s, int ch, size_t n)
在一段内存中填充ch,以达到对较大结构体或数组进行初始化(清零操作)。
试验案例:在声明一个字符数组后 char buf[1024],系统会随机给buf赋值,如果没有进行memset(buf,0,1024)操作,则在进行fread之后, 用printf输出buf时会有乱码。
2)终端控制
(1)利用终端结构体termios实现回显关闭,非标准输入行处理设 置
termios可对终端接口进行控制,termios数据结构和相关函数调用定义在termios.h中
可以被调整来影响终端的值按照不同的模式被分成如下几组:输入模式,输出模式,控制模式 ,本地模式,特殊控制字符。
在本例中用到了本地模式,特殊控制字符,以下是运行本例的前期知识准备:
最小的termios结构的典型定义如下:
回显功能关闭: c_lflag &= ~ECHO
非标准输入行处理: c_lflag &= ~ICANON; c_cc[VMIN] = 1;c_cc[VTIME] = 0
涉及到的函数原型:
int tcgetattr(int fd, struct termios *termios_p)
int tcsetattr(int fd,int actions,const struct termios *termios_p)
参数列表中:fd为文件流的文件描述符,可以通过函数fileno(FILE *)来得到。
(2)利用terminfo软件包完成清屏,光标定位。
terminfo使程序不必去迎合多变的终端类型,其只要通过查询终端类型数据库来找到正确的终端信息。
在多数现代UNIX系统(包括linux)中,这个软件包和另一个软件包curses集成在一起。
为了使用terminfo函数,通常需要包括curses头文件curses.h和terminfo自己的头文件term.h。
在本例中,我们利用terminfo获取屏幕信息,实现了清屏和光标定位的功能。
涉及到的函数原型为:
1. int setuptterm(char *term, int fd, int *errret);
设置终端类型,为当前的终端类型初始化一个TERMINAL结构。
char *term为NULL是则使用环境变量TERM值(其实我们的目的就是获取当前终端类型的TERMINAL结构体,所以此项一般设置为NULL)
2. int tigetnum(char *capname);
通过capname获取数值型terminfo数据项,如终端显示的最大行数,最大列数等数值型数据项。
3.char *tigetstr(char *capname);
通过capname获取字符型terminfo数据项,如清屏的命令字符串,光标移动的命令字符串,其是参数化字符串,即还需要通过tparm函数来输入具体的光标位置。
4.char *tparm(char *cap,long p1,long p2,..., long p9);
同过tparm函数实际的数值替换功能替换命令字符串中的参数,如光标移动的命令字符串。
5.int putp(char *const str);
putp将命令字符串发到终端,其针对的是标准输出流。如果不能通过标准输出stdout访问终端,则需要 tputs(char *const str,int affcnt, int (*putfunc)(int))来
指定一个用 于输出 字符的函数putfunc, putp(string)相当于tputs(string,1,putchar),int affcnt一般置为1。
(3)对于终端的控制还可以用ncurses库函数实现。
Referrences:
《Unix/Linux编程实践教程》
《Linux程序设计》 Edition 4
代码实现:(站在巨人的肩膀上)
运行平台:CentOS6.4 GCC 4.4.7
#include
#include
#include
#include
#include
typedef int Status;
int LINELEN,PAGELEN; //全局变量,在GetTermInfo中获得
/*more的具体实现*/
Status DoMore(FILE *);
/*用户命令输入处理*/
Status SeeMore(FILE *);
/*实现回显功能关闭和非标准行处理*/
Status EchoSet(struct termios *,FILE *);
/*恢复初始设置*/
Status EchoBack(struct termios *,FILE *);
/*实现清屏,光标定位*/
Status GetTermInfo();
Status DoMore(FILE *fp)
{
struct termios *initialrsettings,*newrsettings;
initialrsettings = (struct termios *)malloc(sizeof(struct termios));
newrsettings = (struct termios *)malloc(sizeof(struct termios));
char line[LINELEN];
int num_of_lines = 0;
int user_action;
FILE *fp_tty;
fp_tty = fopen("/dev/tty","r");
if(NULL == fp_tty)
{
exit(1);
}
tcgetattr(fileno(fp_tty),initialrsettings);
*newrsettings = *initialrsettings;//此处不要用指针赋值,否则无法恢复初始设置.
while(fgets(line,LINELEN,fp))
{
if(num_of_lines == PAGELEN)
{
EchoSet(newrsettings,fp_tty);
user_action = SeeMore(fp_tty);
if(user_action==0)
{
EchoBack(initialrsettings,fp_tty); //恢复终端初始设置
printf("\n");
break;
}
num_of_lines = num_of_lines - user_action;
}
if(fputs(line,stdout)==EOF)
{
exit(1);
}
num_of_lines++;
}
EchoBack(initialrsettings,fp_tty);//恢复终端初始设置
}
int SeeMore(FILE *cmd)
{
char c;
printf("\033[7m more? \033[m");
while((c=fgetc(cmd))!=EOF)
{
if(c == 'q')
{
return 0;
}
if(c == ' ')
{
return PAGELEN;
}
if(c == '\n')
{
return 1;
}
}
return 0;
}
/*关闭会显.需要termios.h*/
Status EchoSet(struct termios *newrsettings,FILE *fp_tty)
{
if(NULL == newrsettings||NULL == fp_tty)
{
return 1;
}
(*newrsettings).c_lflag &= ~ECHO;//关闭回显
(*newrsettings).c_lflag &= ~ICANON;//以下三行--无需输入回车即可执行用户命令
(*newrsettings).c_cc[VMIN] = 1;
(*newrsettings).c_cc[VTIME] = 0;
tcsetattr(fileno(fp_tty),TCSAFLUSH,newrsettings);
//free(newrsettings); //运行此行代码出错,因为 tcsetattr调用完毕后自动free.
//newrsettings = NULL;
return 0;
}
/*恢复回显功能*/
Status EchoBack(struct termios *initialrsettings,FILE *fp_tty)
{
if(NULL == initialrsettings||NULL == fp_tty)
{
return 1;
}
tcsetattr(fileno(fp_tty),TCSANOW,initialrsettings);
return 0;
}
Status GetTermInfo()
{
char *clear,*cursor;
setupterm(NULL,fileno(stdout),(int *)0);
clear = (char *)tigetstr("clear");
cursor = (char *)tigetstr("cup");
PAGELEN = tigetnum("lines")-1;
LINELEN = tigetnum("cols");
putp(clear);
putp(tparm(cursor,0,0));
return 0;
}
int main(int ac,char *av[])
{
FILE *fp;
if(ac == 1)
{
GetTermInfo();
DoMore(stdin);
}else
{
while(--ac)
{
if((fp=fopen(*++av,"r"))!=NULL)
{
GetTermInfo();
DoMore(fp);
fclose(fp);
}else
{
exit(1);
}
}
}
return 0;
}