本文隻是學渣作者的學習經曆,能幫到讀者是作者的榮幸。功能沒有實作完全,希望各位多多提意見。
more指令基本功能實作:‘q’--退出,空格--下一頁,Enter鍵--下一行,清螢幕顯示,關閉回顯,執行指令無需按鍵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中獲得
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;
}
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;
}