Linux文件重定向&&文件緩沖區(qū)解讀
一、C文件接口
stdin & stdout & stderr
C默認(rèn)會(huì)打開三個(gè)輸入輸出流,分別是stdin, stdout, stderr
仔細(xì)觀察發(fā)現(xiàn),這三個(gè)流的類型都是FILE*, fopen返回值類型,文件指針
- fwrite向指定文件寫入內(nèi)容
- fread從指定文件讀取內(nèi)容
fprintf根據(jù)指定的format(格式)發(fā)送信息(參數(shù))到由stream(流)指定的文件,fprintf可以使得信息寫入到指定的文件
調(diào)用C文件接口,以w的形式打開,若文件不存在,會(huì)在當(dāng)前目錄下新建文件,當(dāng)前路徑就是進(jìn)程的當(dāng)前路徑cwd,如果改變了進(jìn)程的cwd就可以在其他目錄下新建文件
w寫入前都會(huì)對(duì)文件進(jìn)行清空,a在文件結(jié)尾追加寫,兩者都是寫入
C默認(rèn)打開的三個(gè)輸入輸出流不是C語(yǔ)言的特性,而是操作系統(tǒng)的特性,進(jìn)程會(huì)默認(rèn)打開鍵盤,顯示器,顯示器
二、系統(tǒng)文件I/O
2.1認(rèn)識(shí)系統(tǒng)文件I/O
- 文件其實(shí)是在磁盤上的,磁盤是外設(shè),對(duì)文件進(jìn)行訪問(wèn),就是對(duì)硬件進(jìn)行訪問(wèn)
- 任何用戶都不能直接訪問(wèn)硬件的數(shù)據(jù) ,而必須通過(guò)系統(tǒng)調(diào)用
- 幾乎所有的庫(kù)只要是訪問(wèn)硬件設(shè)備,必須封裝系統(tǒng)調(diào)用
- C文件接口就是一種庫(kù)函數(shù),是對(duì)系統(tǒng)調(diào)用的封裝
2.2系統(tǒng)文件I/O
open( )
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
- pathname: 要打開或創(chuàng)建的目標(biāo)文件
- flags: 打開文件時(shí),可以傳入多個(gè)參數(shù)選項(xiàng),用下面的一個(gè)或者多個(gè)常量進(jìn)行 “ 或 ” 運(yùn)算,構(gòu)成 flags
參數(shù) :
- O_RDONLY: 只讀打開
- O_WRONLY: 只寫打開
- O_RDWR : 讀寫打開
- O_CREAT : 若文件不存在,則創(chuàng)建它,需要使用 mode(例0666) 選項(xiàng),來(lái)指明新文件的訪問(wèn)權(quán)限
- O_APPEND: 追加寫
- O_TRUNC: 每一次寫入都清空文件
返回值:
- 成功:新打開的文件描述符
- 失敗:-1
代碼示例:
umask( )可以用來(lái)設(shè)置掩碼的值
比特方位式的標(biāo)志位傳遞方式通過(guò)位運(yùn)算來(lái)實(shí)現(xiàn)
2.3系統(tǒng)調(diào)用和庫(kù)函數(shù)
上面的 fopen fclose fread fwrite 都是C標(biāo)準(zhǔn)庫(kù)當(dāng)中的函數(shù),我們稱之為庫(kù)函數(shù)(libc)
open close read write lseek 都屬于系統(tǒng)提供的接口,稱之為系統(tǒng)調(diào)用接口
可以認(rèn)為,f#系列的函數(shù),都是對(duì)系統(tǒng)調(diào)用的封裝,方便二次開發(fā)。
2.4open( )的返回值--文件描述符
Linux進(jìn)程默認(rèn)情況下會(huì)有3個(gè)缺省打開的文件描述符,分別是標(biāo)準(zhǔn)輸入0, 標(biāo)準(zhǔn)輸出1, 標(biāo)準(zhǔn)錯(cuò)誤2
0,1,2對(duì)應(yīng)的物理設(shè)備一般是:鍵盤,顯示器,顯示器
linux下文件描述符的分配規(guī)則:從0下標(biāo)開始,尋找最小沒有被使用過(guò)的數(shù)組位置,它的下標(biāo)就是新文件的文件描述符--結(jié)合訪問(wèn)文件的本質(zhì)來(lái)說(shuō)明
代碼示例:
- 因?yàn)镃庫(kù)函數(shù)是對(duì)系統(tǒng)接口的封裝,系統(tǒng)接口下只認(rèn)識(shí)文件描述符,所以C庫(kù)自己提供的FILE結(jié)構(gòu)體中必定也包含著文件描述符,用_fileno記錄
如果關(guān)閉了1號(hào)文件,printf就無(wú)法向1號(hào)文件(顯示器)寫入了 ,但可以向3號(hào)文件寫入,所以我們打印就只能看到n的值
2.5訪問(wèn)文件的本質(zhì)
任何一個(gè)被打開的文件在內(nèi)存中都要被管理起來(lái),操作系統(tǒng)如果管理被打開的文件?----先描述再組織
當(dāng)我們打開文件時(shí),操作系統(tǒng)在內(nèi)存中要?jiǎng)?chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)描述目標(biāo)文件--file結(jié)構(gòu)體(直接或間接包含如下屬性:文件的基本屬性,文件的內(nèi)核緩沖區(qū)信息,引用計(jì)數(shù),struct file*next,在磁盤的什么位置),表示一個(gè)已經(jīng)打開的文件對(duì)象而進(jìn)程執(zhí)行open系統(tǒng)調(diào)用,所以必須讓進(jìn)程和文件關(guān)聯(lián)起來(lái),每個(gè)進(jìn)程都有一個(gè)指針*files, 指向一張表files_struct,該表最重要的部分就是包涵一個(gè)指針數(shù)組,每個(gè)元素都是一個(gè)指向打開文件的指針!
所以,本質(zhì)上,文件描述符就是該數(shù)組的下標(biāo),只要拿著文件描述符,就可以找到對(duì)應(yīng)的文件
- 當(dāng)一個(gè)進(jìn)程open()一個(gè)文件時(shí),操作系統(tǒng)會(huì)在struct_file的指針數(shù)組中從下標(biāo)為0的地方在開始尋找一個(gè)沒有被使用過(guò)的數(shù)組位置,填入要打開文件的struct file*,再將數(shù)組下標(biāo)返回給open( )調(diào)用,作為該文件的文件描述符fd
- 當(dāng)一個(gè)進(jìn)程要向某個(gè)文件寫入的時(shí)候,操作系統(tǒng)只認(rèn)識(shí)文件描述符,根據(jù)文件描述符找到對(duì)應(yīng)的數(shù)組下標(biāo),根據(jù)數(shù)組下標(biāo)位置里的內(nèi)容找到所對(duì)應(yīng)的文件再寫入
- close關(guān)閉文件本質(zhì)上是清空對(duì)應(yīng)fd數(shù)組下標(biāo)位置的內(nèi)容,再將該fd內(nèi)容指向的文件的引用計(jì)數(shù)--,引用計(jì)數(shù)為0才釋放銷毀相應(yīng)的struct_ file
三、文件重定向
3.1認(rèn)識(shí)文件重定向
關(guān)閉1號(hào)文件再打開新文件 ,向1號(hào)文件寫入內(nèi)容
可以看到,原來(lái)要向1號(hào)文件(顯示屏)打印的信息,被寫入到了新打開的文件,其中,fd=1。這種現(xiàn)象叫做輸出重定向
常見的重定向有:>輸出重定向, >>追加重定向, <輸入重定向
追加重定向
輸入重定向
3.2文件重定向的本質(zhì)
- 文件重定向的本質(zhì):將1號(hào)文件描述符在指針數(shù)組中對(duì)應(yīng)位置的內(nèi)容,用log.txt文件描述符在指針數(shù)組中對(duì)應(yīng)位置的內(nèi)容進(jìn)行覆蓋,原本數(shù)組內(nèi)的指向1號(hào)文件的文件指針就被替換成log.txt的文件指針,當(dāng)我們?cè)傧?號(hào)文件描述符寫入內(nèi)容的時(shí)候,就是向文件指針指向的log.txt內(nèi)寫入而不再寫到標(biāo)準(zhǔn)輸出
- dup2系統(tǒng)調(diào)用
- 原本向顯示屏打印的內(nèi)容被寫入到log.txt文件中
3.3在shell中添加重定向功能
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<assert.h> #include<ctype.h> #include<fcntl.h> #define LEFT "[" #define RIGHT "]" #define LABLE "#" #define DELIM " \t" #define LINE_SIZE 1024 #define ARGV_SIZE 32 #define NONE -1 #define IN_RDIR 0 #define OUT_RDIR 1 #define APPEND_RDIR 2 extern char** environ; char commandline[LINE_SIZE]; char* argv[ARGV_SIZE]; char pwd[LINE_SIZE]; char myenv[LINE_SIZE]; int lastcode=0; int quit=0; char *rdirfilename = NULL; int rdir = NONE; const char* getuser() { return getenv("USER"); } const char* gethostname() { return getenv("HOSTNAME"); } void getpwd() { getcwd(pwd,sizeof(pwd)); } void check_redir(char *cmd) { // ls -al -n // ls -al -n >/</>> filename.txt char *pos = cmd; while(*pos) { if(*pos == '>') { if(*(pos+1) == '>'){ *pos++ = '\0'; *pos++ = '\0'; while(isspace(*pos)) pos++; rdirfilename = pos; rdir=APPEND_RDIR; break; } else{ *pos = '\0'; pos++; while(isspace(*pos)) pos++; rdirfilename = pos; rdir=OUT_RDIR; break; } } else if(*pos == '<') { *pos = '\0'; // ls -a -l -n < filename.txt pos++; while(isspace(*pos)) pos++; rdirfilename = pos; rdir=IN_RDIR; break; } else{ //do nothing } pos++; } } void interact(char* cline,int size) { getpwd(); printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getuser(),gethostname(),pwd); char* s=fgets(cline,size,stdin); assert(s); (void)s; cline[strlen(cline)-1]='\0'; //printf("echo : %s",cline); //ls -a -l > myfile.txt check_redir(cline); } int splitstring(char cline[],char* _argv[]) { int i=0; _argv[i++]=strtok(cline,DELIM); while(_argv[i++]=strtok(NULL,DELIM)); return i-1; } void normalexcute(char* _argv[]) { pid_t id=fork(); if(id<0) { perror("fork"); //continue; return ; } else if(id==0) { int fd = 0; // 后面我們做了重定向的工作,后面我們?cè)谶M(jìn)行程序替換的時(shí)候,難道不影響嗎??? if(rdir == IN_RDIR) { fd = open(rdirfilename, O_RDONLY); dup2(fd, 0); } else if(rdir == OUT_RDIR) { fd = open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666); dup2(fd, 1); } else if(rdir == APPEND_RDIR) { fd = open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666); dup2(fd, 1); } //子進(jìn)程執(zhí)行指令 //execvpe(argv[0],argv,environ); execvp(argv[0],argv); } else{ int status=0; pid_t rid=waitpid(id,&status,0); if(rid==id) { lastcode=WEXITSTATUS(status); } } } int buildcommand(char* _argv[],int _argc) { if(_argc==2&&strcmp(_argv[0],"cd")==0) { chdir(_argv[1]); getpwd(); sprintf(getenv("PWD"),"%s",pwd); return 1; } else if(_argc==2&&strcmp(_argv[0],"export")==0) { strcpy(myenv,_argv[1]); putenv(myenv); return 1; } else if(_argc==2&&strcmp(_argv[0],"echo")==0) { if(strcmp(_argv[1],"$?")==0) { printf("%d\n",lastcode); lastcode=0; } else if(*_argv[1]=='$') { char* s=getenv(_argv[1]+1); if(s) printf("%s\n",s); } else{ printf("%s\n",_argv[1]); } return 1; } //特殊處理ls if(_argc==2&&strcmp(_argv[0],"ls")==0) { _argv[_argc++]="--color"; _argv[_argc]=NULL; } return 0; } int main() { while(!quit) { //交互問(wèn)題,獲得命令行參數(shù) interact(commandline,sizeof commandline); //字符串分割,解析命令行參數(shù) int argc = splitstring(commandline,argv); if(argc==0) continue; //指令的判斷 int n=buildcommand(argv,argc); //普通指令的執(zhí)行 if(!n)normalexcute(argv); } return 0; }
- 進(jìn)程歷史打開的文件以及文件的重定向關(guān)系,并不會(huì)被程序替換所影響??!進(jìn)程程序替換之后影響頁(yè)表右邊的物理地址所指向的內(nèi)容,虛擬地址并左邊的部分并不會(huì)受到影響
- 程序替換并不會(huì)影響文件訪問(wèn)
3.4stdout和stderr
- stdout和stderr對(duì)應(yīng)的硬件設(shè)備都是顯示屏,訪問(wèn)的都是同一個(gè)文件(引用計(jì)數(shù))
- 在重定向的時(shí)候,默認(rèn)只對(duì)stdout的fd進(jìn)行重定向
代碼示例:
如果對(duì)1號(hào)和2號(hào)文件都要進(jìn)行重定向呢?
示例:./mytest 1> log.txt 2>err.txt
示例:./mytest > log.txt 2>&1
3.5如何理解“linux下一切皆文件” --以對(duì)外設(shè)的IO操作為例
- 不同的外設(shè)在進(jìn)行IO操作時(shí)都有自己對(duì)應(yīng)的讀寫方法,放在struct device里
- 這些讀寫方法如何被找到?--由struct operation_func來(lái)對(duì)讀寫方法進(jìn)行管理,該結(jié)構(gòu)體里存在指向?qū)?yīng)讀寫法的函數(shù)指針
- 如何找到struct operation_func?--由struct file來(lái)對(duì)struct operation_func進(jìn)行管理,file結(jié)構(gòu)體存在指向struct operation_func的指針,基于struct file之上的被稱為虛擬文件系統(tǒng)(VFS)--一切皆文件
- 當(dāng)我們打開一個(gè)文件的時(shí)候,通過(guò)進(jìn)程的pcb數(shù)據(jù)結(jié)構(gòu)找到struct struct_file,操作系統(tǒng)根據(jù)文件描述符的分配規(guī)則,在struct struct_file的指針數(shù)組中為該文件分配一個(gè)fd;當(dāng)我們要訪問(wèn)一個(gè)外設(shè)的時(shí)候,根據(jù)該外設(shè)文件fd對(duì)應(yīng)的數(shù)組下標(biāo)內(nèi)容找到該外設(shè)文件的struct file,根據(jù)file結(jié)構(gòu)體找到對(duì)應(yīng)的struct operation_func,由于訪問(wèn)的外設(shè)的不同,在struct operation_func中根據(jù)函數(shù)指針找到對(duì)應(yīng)的讀寫方法,就可以對(duì)外設(shè)進(jìn)行訪問(wèn)了
四、文件緩沖區(qū)
4.1認(rèn)識(shí)FILE
因?yàn)镮O相關(guān)函數(shù)與系統(tǒng)調(diào)用接口對(duì)應(yīng),并且?guī)旌瘮?shù)封裝系統(tǒng)調(diào)用,所以本質(zhì)上,訪問(wèn)文件都是通過(guò)fd訪問(wèn)的
所以C庫(kù)當(dāng)中的FILE結(jié)構(gòu)體內(nèi)部,必定封裝了fd
4.2文件緩沖區(qū)引入
- 對(duì)比有無(wú)fork( )的代碼
我們發(fā)現(xiàn) printf 和 fwrite (庫(kù)函數(shù))都輸出了 2 次,而 write 只輸出了一次(系統(tǒng)調(diào)用),為什么呢?肯定和 fork有關(guān)!
再來(lái)驗(yàn)證一個(gè)現(xiàn)象:
不加'\n'并且在最后close(1)
代碼運(yùn)行的結(jié)果是:只有系統(tǒng)調(diào)用接口寫入的內(nèi)容被打印出來(lái)了
加上'\n',結(jié)果又不一樣了
4.3文件緩沖區(qū)的原理
C語(yǔ)言會(huì)提供一個(gè)緩沖區(qū),我們調(diào)用C文件接口寫入的數(shù)據(jù)會(huì)被暫存在這個(gè)緩沖區(qū)內(nèi),緩沖區(qū)的刷新方式有三種:
- 無(wú)緩沖:直接刷新,一般我們使用的fflush( )就是無(wú)緩沖的刷新方式
- 行緩沖:遇到'\n'才刷新,一般對(duì)應(yīng)顯示器
- 全緩沖:緩沖區(qū)滿了才刷新,一般對(duì)應(yīng)普通文件的寫入
- 特殊說(shuō)明:進(jìn)程結(jié)束的時(shí)候會(huì)自動(dòng)刷新緩沖區(qū)
在操作系統(tǒng)的內(nèi)核中也存在一個(gè)內(nèi)核級(jí)別的緩沖區(qū),目前認(rèn)為,只要將數(shù)據(jù)刷新到了內(nèi)核,數(shù)據(jù)就可以到硬件了,內(nèi)核緩沖區(qū)也有自己的刷新方式
為什么要有C層面的緩沖區(qū)?
- 用戶不需要一步一步將數(shù)據(jù)寫入到硬件中,而是可以直接調(diào)用C庫(kù)為我們提供的讀寫方法,將數(shù)據(jù)交給庫(kù)函數(shù)來(lái)處理,解決用戶的效率問(wèn)題
- 我們真正存到文件里的都是一個(gè)個(gè)的字符,調(diào)用C庫(kù)的讀寫方法,可以在放入緩沖區(qū)之前將我們的數(shù)據(jù)格式化成字符串,再刷新到內(nèi)核中進(jìn)而寫入文件,C層面的緩沖區(qū)可以配合格式化的工作
C為我們提供的緩沖區(qū)在FILE結(jié)構(gòu)體里,F(xiàn)ILE里面有相關(guān)緩沖區(qū)的字段和維護(hù)信息,F(xiàn)ILE屬于用戶層面,而不屬于操作系統(tǒng)
文件寫入的過(guò)程:
- 首先,在文件寫入之前,進(jìn)程會(huì)打開一個(gè)文件,通過(guò)對(duì)各種內(nèi)核數(shù)據(jù)結(jié)構(gòu)的訪問(wèn)和操作,獲得該文件的文件描述符
- 如果使用系統(tǒng)調(diào)用接口來(lái)對(duì)文件進(jìn)行寫入,數(shù)據(jù)直接通過(guò)write和fd寫入對(duì)應(yīng)的內(nèi)核級(jí)別緩沖區(qū),默認(rèn)最后都會(huì)刷新到硬件中
- 如果使用fwrite等庫(kù)函數(shù)來(lái)對(duì)文件進(jìn)行寫入,首先,在語(yǔ)言層面會(huì)malloc出一個(gè)FILE結(jié)構(gòu)體,F(xiàn)ILE里面有對(duì)應(yīng)的緩沖區(qū)信息以及文件的fd,然后內(nèi)容會(huì)先被暫存在C層面的緩沖區(qū),如果是無(wú)緩沖,數(shù)據(jù)直接被刷新到內(nèi)核中,如果是行緩沖,遇到'\n'就會(huì)被刷新到內(nèi)核中,如果是全緩沖,等緩沖區(qū)滿了就被刷新到內(nèi)核中
- 由于庫(kù)函數(shù)是對(duì)系統(tǒng)調(diào)用接口的封裝,用戶通過(guò)write和fd將數(shù)據(jù)刷新到對(duì)應(yīng)的文件的內(nèi)核緩沖區(qū)內(nèi),再由該內(nèi)核緩沖區(qū)刷新到外設(shè)
4.4解釋現(xiàn)象
為什么不加'\n'并且close(1)的時(shí)候,使用庫(kù)函數(shù)寫入的內(nèi)容不會(huì)被顯示?
不加'\n',調(diào)用庫(kù)函數(shù)寫入的數(shù)據(jù)都會(huì)被暫存在C層面的緩沖區(qū)
close(1)后,即使進(jìn)程退出后緩沖區(qū)會(huì)自動(dòng)刷新,但是此時(shí)已經(jīng)找不到1號(hào)文件的fd了,緩沖區(qū)內(nèi)的數(shù)據(jù)也無(wú)法被寫入到內(nèi)核中,最后也不會(huì)顯示到顯示器上
加了'\n'即使最后close(1),遇到'\n'緩沖區(qū)就會(huì)立馬將數(shù)據(jù)刷新到內(nèi)核中,就會(huì)顯示到顯示器上
為什么fork()之后重定向C接口會(huì)被調(diào)用兩次?
- 重定向后,緩沖區(qū)的刷新方式會(huì)從行緩沖變成全緩沖,也就說(shuō),數(shù)據(jù)要么等到緩沖區(qū)滿了再被刷新,要么等待進(jìn)程結(jié)束后再刷新,所以我們放在緩沖區(qū)中的數(shù)據(jù),就不會(huì)被立即刷新,甚至fork之后
- fork( )之后,創(chuàng)建子進(jìn)程,子進(jìn)程會(huì)繼承父進(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu)對(duì)象的內(nèi)容,父子進(jìn)程在一開始的時(shí)候數(shù)據(jù)和代碼是共享的,緩沖區(qū)也屬于數(shù)據(jù)
- 進(jìn)程退出后,要對(duì)緩沖區(qū)的數(shù)據(jù)進(jìn)行統(tǒng)一刷新,刷新就是對(duì)數(shù)據(jù)進(jìn)行訪問(wèn)寫入,此時(shí)父子數(shù)據(jù)會(huì)發(fā)生寫時(shí)拷貝,所以當(dāng)父進(jìn)程準(zhǔn)備刷新的時(shí)候,子進(jìn)程也就有了同樣的一份數(shù)據(jù),隨即產(chǎn)生兩份數(shù)據(jù)
- 由于write沒有所謂的緩沖區(qū),write()寫入的數(shù)據(jù)直接在內(nèi)核中,所以write( )的數(shù)據(jù)只有一份
總結(jié)
printf fwrite 庫(kù)函數(shù)會(huì)自帶緩沖區(qū),而 write 系統(tǒng)調(diào)用沒有帶緩沖區(qū)。這里所說(shuō)的緩沖區(qū), 都是用戶級(jí)緩沖區(qū)。其實(shí)為了提升整機(jī)性能,OS也會(huì)提供相關(guān)內(nèi)核級(jí)緩沖區(qū)
那這個(gè)用戶級(jí)緩沖區(qū)誰(shuí)提供呢? printf fwrite 是庫(kù)函數(shù), write 是系統(tǒng)調(diào)用,庫(kù)函數(shù)在系統(tǒng)調(diào)用的“上層”, 是對(duì)系統(tǒng) 調(diào)用的“封裝”,但是 write 沒有緩沖區(qū),而 printf fwrite 有,說(shuō)明該緩沖區(qū)是二次加上的,由C標(biāo)準(zhǔn)庫(kù)提供
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
linux服務(wù)器上安裝jdk的兩種方法(yum+下載包)
這篇文章主要給大家介紹了關(guān)于在linux服務(wù)器上安裝jdk的兩種方法,分別是利用yum安裝和從官網(wǎng)下載包安裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-05-05Linux定時(shí)自動(dòng)刪除舊垃圾文件的Autotrash工具
今天小編就為大家分享一篇關(guān)于Linux定時(shí)自動(dòng)刪除舊垃圾文件的工具,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-09-09linux下如何把進(jìn)程/線程綁定到特定cpu核上運(yùn)行
這篇文章主要介紹了linux下如何把進(jìn)程/線程綁定到特定cpu核上運(yùn)行問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Linux字符終端如何用鼠標(biāo)移動(dòng)一個(gè)紅色矩形詳解
這篇文章主要給大家介紹了關(guān)于Linux字符終端如何用鼠標(biāo)移動(dòng)一個(gè)紅色矩形的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Linux具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05