曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 4976|回复: 2
打印 上一主题 下一主题

初级 more 命令

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2008-11-16 23:15:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
#include <stdio.h>
#include <stdlib.h>

#define PAGELEN 24
#define LINELEN 512

void do_more(FILE *);
int see_more();


int main(int ac, char *av[])
{
    FILE *fp;
    if( ac == 1 )
    do_more(stdin);  //stdin 是标准输入,可以是来自键盘的输入,还可以来自被重定向的管道输入
    else
    while( --ac )  /*若ac=2,则 -1 后,表示第2个参数,一般是要显示的文件名*/
        if( (fp = fopen(* ++av, "r")) != NULL )
        {
        do_more( fp );
        fclose( fp );
        }
        else
        exit(1);   
    return 0;
}

void do_more( FILE *fp )
{
    char line[LINELEN];
    int num_of_lines = 0;
    int reply;

    while( fgets( line, LINELEN, fp ) ){
    if( num_of_lines == PAGELEN ){
        reply = see_more();    //从输入流中取值,可能来自键盘,也可能是来自别的命令中的重定向
        if( reply == 0 )
        break;
        num_of_lines -= reply;  //按要求显示内容
    }
    if( fputs(line, stdout) == EOF )   //输出
        exit(1);
    num_of_lines++;
    }
}

int see_more()
{
    int c;
    printf("\033[7m more? \033[m");

    while( (c = getchar()) != EOF )
    {
    if( c == 'q' )
        return 0;
    if( c == ' ' )
        return PAGELEN;
    if( c == '\n' )
        return 1;
    }
    return 0;
}

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2008-11-16 23:15:25 | 只看该作者

深入解析 more01.c

上面的 more 命令雏形,存在一个问题,比如使用以下的命令:

ls /bin | ./more01.exe

则输出并不是在显示第 24 行后暂停下来,而是继续输出,那问题出在哪里了呢?

分析一下这个程序的执行流程:

在输入命令 ls /bin | ./more01.exe 时,因为 more01.exe 后不再带有参数,于是 ac=1 ,那么执行:

do_more(stdin);

参数 stdin,是标准输入流,这个流的输入来源正好是来自 ls /bin 的输出然后通过管道 | 进入到 stdin 中。

接着,程序开始执行,按照流程,先是显示输入流中的 24 行的内容。显示完后,此时:

num_of_lines -= PAGELEN;

从而,程序进入到 see_more() 里。

上面的命令中,ls /bin 的输出是作为 标准输入 而进入到 more01.exe 中的;反过来就是说 more01.exe 的标准输入重定向到 ls 的标准输出。

这样,当 num_of_lines 一直增加到 24 行时,程序进入 see_more() 函数。此时需要注意的是,see_more()函数里,使用了getchar()从流中获得输入。getchar()函数是没有参数的,也就是说,只要是标准输入流不论是来自键盘或者来自文件流,getchar()都可以捕获---然而这个捕获,是不能指定的捕获--而使用 getc()函数却是可以指定流的,也就是说 getc()是带有参数的。在改进后的more 命令修改版本中,可以看到使用 getc() 的实现。

说回来,在 see_more() 函数里,getchar() 函数收到了什么呢?这里是很关键的。因为 fgets() 函数读取是以“指定的字节数-1”读入的,最后一个字节则以换行符 '\\n'作为结束。所以,see_more() 函数会返回 1。接下来回到 do_more()中后,程序就会连续的输出所有内容。


这里需要区分两种命令的不同效果:

./more01.exe text.txt

more text.txt | ./more01.exe

对于 more text.txt | ./more01.exe 中,上面已经分析过。这里再补充说明一下,more text.txt |是通过管道进行数据流的“强制灌输”。在这个数据流里,包含有潜藏的“回车键”输入。具体的说,就是当 getchar()每次在接收到换行符'\\n' 后,被管道强制给予了一个回车,所以我们看到程序是连续着没有暂停的输出。

然而,./more01.exetext.txt 这个又是另外一种不同的情况。和上面的命令的区别在于 ./more01.exe 后带不带参数。./more01.exetext.txt 是带有一个参数 text.txt 的。这样一来,程序是从 do_more(fp)进去的。其中的 fp 指的就是参数text.txt。此时,没有了像上面那种情况的管道强制性的灌输形为,所以,在显示完24行的内容后,需要手动的把 '\\n'送过去(即在键盘上按下回车键)。

再具体点的分析就是,当输出 24 行的内容后,文件指针执行了 '\\n' 这里,那么在使用 getchar() 后,文件指针现把 '\\n' 送给变量 c ,然后 fp++ 指向了下一个内容单元。

在第三帖中,给出修改后的 more01.exe 的修改版本 more02.exe,这个版本解决了连续输出的问题,但对于完善的 more 命令自然还是远远不够的。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2008-11-16 23:15:53 | 只看该作者

more02.c

#include <stdio.h>
#include <stdlib.h>

#define PAGELEN 24
#define LINELEN 512

void do_more(FILE *);
int see_more(FILE *);


int main(int ac, char *av[])
{
    FILE *fp;
    if( ac == 1 )
    do_more(stdin);
    else
    while( --ac )
        if( (fp = fopen(* ++av, "r")) != NULL )
        {
        do_more( fp );
        fclose( fp );
        }
        else
        exit(1);   
    return 0;
}

void do_more( FILE *fp )
{
    char line[LINELEN];
    int num_of_lines = 0;
    int reply;
    FILE *fp_tty;

    fp_tty = fopen("/dev/tty", "r");
    if( fp_tty == NULL )              //假如读取设备描述文件失败则退出
    exit(1);                         

    while( fgets( line, LINELEN, fp ) ){
    if( num_of_lines == PAGELEN ){
        reply = see_more(fp_tty);
        if( reply == 0 )
        break;
        num_of_lines -= reply;
    }
    if( fputs(line, stdout) == EOF )
        exit(1);
    num_of_lines++;
    }
}

int see_more(FILE *cmd)
{
    int c;
    printf("\\033[7m more? \\033[m");

    while( (c = getc(cmd)) != EOF )
    {
    if( c == 'q' )
        return 0;
    if( c == ' ' )
        return PAGELEN;
    if( c == '\\n' )
        return 1;
    }
    return 0;
}


more02.c 解决了 more01.c 中关于连续输出的问题,这里的解决办法是:从标准输入中读入要分页的数据,直接从用户键盘独用户的输入。

文件 /dev/tty 是键盘和显示器的设备描述文件,向这个文件写相当于显示在用户的屏幕上,读相当于从键盘获取用户的输入。即使程序的输入/输出被 "<" 或 ">"重定向,程序还是可以通过这个文件与终端交换数据。

形象的说,在程序中使用了 /dev/tty 这个控制性行为后,似乎是给程序的输出上了一把锁,而这把锁的钥匙正是来自用户从键盘上的相应输入。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 01:03 , Processed in 0.077139 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表