1. 程式人生 > >學習《Unix/Linux程式設計實踐教程》(2):實現 more

學習《Unix/Linux程式設計實踐教程》(2):實現 more

0.目錄

1.more 能做什麼?

2.more 是如何實現的?

3.實現 more

1.more 能做什麼?

more 可以分頁顯示檔案的內容。正常執行後 more 會顯示檔案第一屏的內容,在螢幕的底部,more 用反白字型顯示檔案的百分比,這時如果按空格鍵,檔案的下一屏內容會顯示出來,如果按回車鍵,顯示的則是下一行,如果輸入“q”,結束顯示,如果輸入“h”,顯示出來的是 more 的聯機幫助。

more 有三種用法:

more filename

顯示檔案filename的內容。

command | more

more 將 command 命令的輸出分頁顯示。

more < filename

從標準輸入獲取要分頁顯示的內容,而這時 more 的標準輸入被重定向到檔案 filename。

2.more 是如何實現的?

書中的流程圖:

more 命令展示:
執行命令more test.c

按回車:

按空格:

按q:

3.實現 more

3.1 more01.c

/* more01.c - version 0.1 of more
 * read and print 24 lines then pause for a few special commands
 */

#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 );
    else
        while ( --ac )
            if ( (fp = fopen( *++av, "r" )) != NULL )
            {
                do_more( fp );
                fclose( fp );
            }
            else
                exit(1);
    return 0;
}

void do_more( FILE *fp )
/*
 * read PAGELEN lines, then call see_more() for further instructions
 */
{
    char line[LINELEN];
    int num_of_lines = 0;
    int see_more(), reply;

    while ( fgets( line, LINELEN, fp ) ) {     /* more input */
        if ( num_of_lines == PAGELEN ) {       /* full screen? */
            reply = see_more();                /* y: ask user */
            if ( reply == 0 )                  /* n: done */
                break;
            num_of_lines -= reply;             /* reset count */
        }
        if ( fputs( line, stdout ) == EOF )    /* show line */
            exit(1);                           /* or die */
        num_of_lines++;                        /* count it */
    }
}

int see_more()
/*
 * print message, wait for response, return # of lines to advance
 * q means no, space means yes, CR means one line
 */
{
    int c;

    printf("\033[7m more? \033[m");            /* reverse on a vt100 */
    while( (c=getchar()) != EOF )              /* get response */
    {
        if ( c == 'q' )                        /* q -> N */
            return 0;
        if ( c == ' ' )                        /* ' ' => next page */
            return PAGELEN;                    /* how many to show */
        if ( c == '\n' )                       /* Enter key => 1 line */
            return 1;
    }
    return 0;
}

測試執行:

按兩下回車:

按空格 + 回車:

按q + 回車:

程式碼分析:
這段程式碼有 3 個函式,在主函式中判斷應該從檔案還是標準輸人中獲取資料,並開啟相應的資料來源,然後呼叫 do_more 函式,do_more 將資料顯示在顯示器上,滿一屏後,呼叫 see_more 函式接收使用者的輸入,以決定下一步的動作。

先來看對資料來源的處理,在 main 函式中檢查命令引數的個數,如果沒有引數,那就從標準輸入讀取資料,這樣一來 more 就可以通過管道重定向來得到資料,如:
who | more
who 命令列出當前系統中活動的使用者,管道命令“|”將 who 的輸出重定向到 more 的輸入,結果是每次顯示 24 個使用者後暫停,在有很多使用者的情況下,用 more 來對 who 的輸出進行分頁就會很有必要。
接下來是輸入重定向的問題,看以下例子:
ls /bin | more01


期望的結果是將 /bin 目錄下的檔案分頁,顯示 24 行以後暫停。
然而實際的執行結果並不是這樣的,24 行以後並沒有暫停而是繼續輸出,問題在哪裡呢?

已經將 more01 的標準輸入重定向到 ls 的標準輸出,這樣 more01 將從同一個資料流中讀使用者的輸入,這顯然有問題。

程式缺陷:

  1. more01.c 只實現了檢視一個檔案( more filename ),當標準輸入輸出被重定向到其他管道時,程式無法正常接受來自鍵盤的資訊(無法使用管道命令「|」、重定向「<」「>」)。
  2. 無法輸入立即響應,需要按回車。

3.2 more02.c

如何改進?

  1. /dev/tty是鍵盤和顯示器的裝置描述檔案,程式可以從/dev/tty得到鍵盤資料,避免因為重定向管道造成無法正常接收鍵盤資料。
  2. getchar() 相當於 getc(stdin)
/* more02.c - version 0.2 of more
 * read and print 24 lines then pause for a few special commands
 * feature of version 0.2: reads from /dev/tty for commands
 */
#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 )
/*
 * read PAGELEN lines, then call see_more() for further instructions
 */
{
    char line[LINELEN];
    int num_of_lines = 0;
    int see_more(FILE *), reply;
    FILE *fp_tty;

    fp_tty = fopen( "/dev/tty", "r" );          /* NEW: cmd stream */
    if ( fp_tty == NULL )                       /* if open fails */
        exit(1);                                /* no use in running */

    while ( fgets( line, LINELEN, fp ) ) {      /* more input */
        if ( num_of_lines == PAGELEN ) {        /* full screen? */
            reply = see_more( fp_tty );         /* NEW: pass FILE * */
            if ( reply == 0 )                   /* n: done */
                break;
            num_of_lines -= reply;              /* reset count */
        }
        if ( fputs( line, stdout ) == EOF )     /* show line */
            exit(1);                            /* or die */
        num_of_lines++;                         /* count it */
    }
}

int see_more(FILE *cmd)                         /* NEW: accepts arg */
/*
 * print message, wait for response, return # of lines to advance
 * q means no, space means yes, CR means one line
 */
{
    int c;

    printf("\033[7m more? \033[m");             /* reverse on a vt100 */
    while( (c=getc(cmd)) != EOF )               /* NEW: reads from tty */
    {
        if ( c == 'q' )                         /* q -> N */
            return 0;
        if ( c == ' ' )                         /* ' ' => next page */
            return PAGELEN;                     /* how many to show */
        if ( c == '\n' )                        /* Enter key => 1 line */
            return 1;
    }
    return 0;
}

測試執行:

3.3 more03.c

改進:不需要回車,直接立即響應輸入的字元。

/* more03.c - version 0.3 of more
 * read and print 24 lines then pause for a few special commands
 * feature of version 0.3: no need to press return
 */
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

#define PAGELEN 24
#define LINELEN 512

void do_more(FILE *);
int see_more(FILE *);
void set_crmode();
tty_mode(int how);

int main( int ac, char *av[] )
{
    FILE *fp;

    tty_mode(0);                                /* save tty mode */
    set_crmode();                               /* set chr-by-chr mode */

    if ( ac == 1 )
        do_more( stdin );
    else
        while ( --ac )
            if ( (fp = fopen( *++av, "r" )) != NULL )
            {
                do_more( fp );
                fclose( fp );
            }
            else
                exit(1);

    tty_mode(1);                                /* restore tty mode */
    return 0;
}

void do_more( FILE *fp )
/*
 * read PAGELEN lines, then call see_more() for further instructions
 */
{
    char line[LINELEN];
    int num_of_lines = 0;
    int see_more(FILE *), reply;
    FILE *fp_tty;

    fp_tty = fopen( "/dev/tty", "r" );          /* NEW: cmd stream */
    if ( fp_tty == NULL )                       /* if open fails */
        exit(1);                                /* no use in running */

    while ( fgets( line, LINELEN, fp ) ) {      /* more input */
        if ( num_of_lines == PAGELEN ) {        /* full screen? */
            reply = see_more( fp_tty );         /* NEW: pass FILE * */
            if ( reply == 0 )                   /* n: done */
                break;
            num_of_lines -= reply;              /* reset count */
        }
        if ( fputs( line, stdout ) == EOF )     /* show line */
            exit(1);                            /* or die */
        num_of_lines++;                         /* count it */
    }
}

int see_more(FILE *cmd)                         /* NEW: accepts arg */
/*
 * print message, wait for response, return # of lines to advance
 * q means no, space means yes, CR means one line
 */
{
    int c;

    printf("\033[7m more? \033[m");             /* reverse on a vt100 */
    while( (c=getc(cmd)) != EOF )               /* NEW: reads from tty */
    {
        if ( c == 'q' )                         /* q -> N */
            return 0;
        if ( c == ' ' )                         /* ' ' => next page */
            return PAGELEN;                     /* how many to show */
        if ( c == '\n' )                        /* Enter key => 1 line */
            return 1;
    }
    return 0;
}

void set_crmode()
/*
 * purpose: put file descriptor 0 (i.e. stdin) into chr-by-chr mode
 * method: use bits in termios
 */
{
    struct termios ttystate;

    tcgetattr( 0, &ttystate);                   /* read curr. setting */
    ttystate.c_lflag    &= ~ICANON;             /* no buffering */
    ttystate.c_cc[VMIN] = 1;                    /* get 1 char at a time */
    tcsetattr( 0, TCSANOW, &ttystate);          /* install settings */
}

/* how == 0 => save current mode; how == 1 => restore mode */
tty_mode(int how)
{
    static struct termios original_mode;
    if ( how == 0 )
        tcgetattr(0, &original_mode);
    else
        return tcsetattr(0, TCSANOW, &original_mode);
}

測試執行:

可以看到,輸入 “q” 後直接退出,不需要按回車。