學習《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 將從同一個資料流中讀使用者的輸入,這顯然有問題。
程式缺陷:
- more01.c 只實現了檢視一個檔案( more filename ),當標準輸入輸出被重定向到其他管道時,程式無法正常接受來自鍵盤的資訊(無法使用管道命令「|」、重定向「<」「>」)。
- 無法輸入立即響應,需要按回車。
3.2 more02.c
如何改進?
/dev/tty
是鍵盤和顯示器的裝置描述檔案,程式可以從/dev/tty
得到鍵盤資料,避免因為重定向管道造成無法正常接收鍵盤資料。- 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” 後直接退出,不需要按回車。