Linux_自己写一个more命令

1 分析
1.1 linux的more命令可以做什么?
more命令可以分页显示文本的内容,首先显示出第一页的内容,然后按回车显示下一行,按空格显示下一页,按q退出,按h显示帮助。同时,在显示的最下方显示文件的百分比
1.2 more是如何实现的
由more的功能可知,首先先输出一页,然后输出文件百分比,等待用户的输入,根据用户的输入进行下一步操作。
2 自己动手写一个more命令
2.1 实现打开文件
2.1.1 通过使用fopen函数来打开一个文件

#include <stdio.h>
 FILE *fopen(const char *path, const char *mode);

fopen是ANSIC标准中的C语言库函数,返回一个文件指针。
参数:
①path 文件的地址
② mode 文件打开的方式,共有一下几种

 - r  以只读的方式打开文件,文件的读写位置为文件的开头
 - r+ 以读写的方式打开文件,文件的读写位置为文件的开头
 - w 以只写的方式打开文件并清除文件的内容,文件的读写位置为文件的开头
 - w+ 以读写的方式打开文件,若文件存在就清除文件内容,若不存在及新建一个文件
 - a 以追加的方式打开文件,若文件不存在就新建一个文件,文件的读写位置在文件的末尾
 - a+ 以追加和读取的方式打开文件,读取的位置在文件的开头,写的位置仍然在文件的末尾

2.1.2 fopen与open的区别
Linux_自己写一个more命令_第1张图片

2.1.3 缓冲文件系统与非缓冲文件系统的区别
缓冲文件系统
缓冲文件系统是借助于文件结构体指针FILE *来对文件进行管理,通过文件指针对文件进行访问,即可以读写字符、字符串、格式化数据,也可以读写二进制数据。
缓 冲文件系统特点:在内存中开辟一个“缓冲区”,为程序里每一个文件使用,当执行读文件操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓 冲区”依次读入接收的变量。执行写文件操作时,也是先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大 小,影响着实际操作外在的次数,内存“缓冲区”越大,则操作外存的次数就越少,执行速度就越快,效率就越高。一般来说,文件“缓冲区”的大小跟机器是相关 的。
缓冲文件系统的IO函数主要包括:fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。
非缓冲文件系统
非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件(对于UNIX系统内核而言,文本文件和二进制代码文件并无区别),但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此,在读取正规的文件时,建议大家最好不要选择它。
非缓冲文件系统的IO函数主要包括:open, close, read, write, getc, getchar, putc, putchar等。

2.2 实现按行读取文件并显示
2.2.1 用fgets来读取文件中的一行

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);

fgets从文件中最多读取size-1个字符,最后一个字符的位置用于存放’\0’作为结束。如果小于size,则一直读取到EOF或换行符,fgets也会将换行符写入到s中。
通过szie的限制,可以保证不会出现数组越界的情况。
参数:
①用于存放读取的数据
②读取的最大长度
③读取的文件

2.2.2 用fputs来显示到屏幕上

#include <stdio.h>
int fputs(const char *s, FILE *stream);

fputs函数将s中的内容输出到stream中,以’\0’作为结束标志,但不会将其写入到文件中。
参数:
①将要写入的字符串
②写入的函数

2.3 输入的命令不显示并立刻执行而不需要输入回车
2.3.1 利用利用”tcgetattr”和”tcsetattr”函数改变终端的输入属性

#include <termios.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);

tcgetattr() 得到与 fd 指向的对象相关的参数,将它们保存于 termios_p 引用的 termios 结构中。函数可以从后台进程中调用;但是,终端属性可能被后来的前台进程所改变。

tcsetattr() 设置与终端相关的参数 (除非需要底层支持却无法满足),使用 termios_p 引用的 termios 结构。
参数:
optional_actions规定了何时应用这些变化,取值有

  • TCSANOW 立刻应用
  • TCSADRAIN 当所有的写入fd的输出被传输的时候生效,这个参数应当在改变影响输出的时候使用
  • TCSAFLUSH 当所有的写入fd的输出被传输时生效,并将所有未被读取的输入丢弃

termios结构体
这个结构体至少包括了以下四个变量

tcflag_t c_iflag;      /* 输入模式 */
tcflag_t c_oflag;      /* 输出模式*/
tcflag_t c_cflag;      /* 控制模式 */
tcflag_t c_lflag;      /* 本地模式 */
cc_t     c_cc[NCCS];   /* 控制字符 */

有关该结构体的应用可以参阅man的在线帮助文档

2.4 显示已读部分的百分比
2.4.1 使用fseek,ftell函数获得文件的所有字符数

int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);

介绍:
fseek是用于改变stream中读写指针的位置的函数。由whence规定的位置加上offset的大小就是新的位置,以字节为单位。
whence的取值:

  • SEEK_SET 文件的开始位置
  • SEEK_END 文件的结尾
  • SEEK_CUR 当前文件位置
    ftell返回的是当前读写指针的之前所有字符的个数。
    rewind 将读写指针放到文件的开头

2.4.2 获得已读字符的个数
设置一个变量用于记录字符的个数,每次输出就加上输出字符串的长度。

3 源码

#include <stdio.h>
#include <sys/stat.h>
#include <termios.h>
#include <string.h>
#define MAXSIZE 1024//缓存的最大值
#define DEFAULT 10//默认显示的行数

void showTheFile(FILE *, int);
int getLineNum(FILE *, long, long);
void changeMode(int);
long getInputSize(FILE *);

int main(int argc, char *argv[])
{
    FILE *fd = NULL;
    if(argc == 1)
        showTheFile(stdin, DEFAULT);
    else
    {

        while(--argc)
        {
            fd = fopen(* ++argv, "r");
            if(fd != NULL)
            {
                showTheFile(fd, DEFAULT);
                fclose(fd);
            }
            else break;
        }
    }
    return 0;
}

void showTheFile(FILE *fd, int linenum)
{
    char buff[MAXSIZE];
    int  i = 0;
    FILE *fdin = fopen("/dev/tty", "r");//打开tty文件获得用户的输入
    long total = getInputSize(fd);
    long readsize = 0;
    if(fdin == NULL) return;

    while(fgets(buff, MAXSIZE, fd)!=NULL)
    {
        if(i == DEFAULT)
        {
            linenum = getLineNum(fdin, total, readsize);
            changeMode(1);
            if(linenum == 0) break;
            i-=linenum;
        }
        readsize+=strlen(buff);
        if(!fputs(buff, stdout)) break;
        i++;
    }
}
int getLineNum(FILE *fdin, long total, long readsize)
{
    double result = (double)readsize/(double)total;
    result*=100;
    printf("\033[33m --More--(%d%) \033[0m", (int)result);
    char input;
    changeMode(0);
    while((input = getc(fdin))!=EOF)
    {
        int i;
        switch(input)
        {
            case 'q':
                return 0;
            case ' ':
                return 10;
            case '\n' :
                return 1;
        }
    }
}

void changeMode(int mode)
{

    struct termios new;
    struct termios old;
    tcgetattr(0,&old);
    new = old;

    new.c_lflag &= ~(ICANON | ISIG);//不许需要输入回车并忽视Ctrl+C等终止符
    new.c_cc[VTIME] = 0;
    new.c_cc[VMIN] = 1;
    if(mode == 0) new.c_lflag &= ~ECHO;不显示输入的值
    if(mode == 1) new.c_lflag |= ECHO;
    tcsetattr(0, TCSANOW, &new);
}

long getInputSize(FILE *input)
{
    long size;
    fseek(input, 0L, SEEK_END);
    size = ftell(input);
    rewind(input);
    return size;
}

4 BUG
4.1 当输入回车或空格时,最下方显示的的more仍然会在上面显示
4.2 无法判断文件的类型,不论是否是文本文件都可以打开
4.3 无法根据终端的类型控制显示的行数

5 参考文献

  • Unix-Linux 编程实践教程
  • linux下改变终端的输入属性
  • 【Linux开发习作】more命令的编写(2)

你可能感兴趣的:(linux)