03.Linux文件操作

1.操作系統與Linux io框架

1.1 io與操作系統

1.1.1 io概念

  • io 描述的是硬件設備之間的數據交互,分為輸? (input) 與輸出 (output)。
    • 輸?:應?程序從其他設備獲取數據 (read) 暫存到內存設備中;
    • 輸出:應?程序將內存暫存的數據寫?到其他設備 (write)。

1.1.2 操作系統概念

  • 操作系統通常包含兩種不同的含義

    • 第?種含義: 指完整的軟件包 : 包括核?軟件與應?軟件。

      • 應?軟件: 命令解釋器, 圖形?戶界?, ?件操作?具與?件編輯器;
      • 核?軟件: 管理和分配計算機資源 (這些計算機資源即 cpu,RAM, 其他設備),即操作系統核?軟件 (內核)。
    • 第?種含義: 專指操作系統核?軟件 (內核)。我們以后就按第二種含義來理解。

    • 內核的職責如下:

      • 進程管理 : 分配 cpu 資源,?于執?程序指令;
      • 內存管理 : 如今計算機內存容量可謂相當可觀, 但軟件的規模也保持了相應的增?, 內存資源仍然屬于有限資源, 內核必須公平與?效的管理內存資源, 其中虛擬內存管理技術;
      • ?件管理 : 允許對?件執?創建,獲取,更新以及刪除;
      • 設備管理 : 計算機外界設備可實現計算機與外部世界的通訊;
      • 聯?管理 : 使計算機可以進??絡通訊;
      • 提供應?編程接? (API): 進程可利?內核??點請求內核區執?各種任務。

1.1.3 Linux操作系統結構

  • Linux操作系統結構

    • ?般分為?戶層與內核層

      • ?戶層 : 表示在內核層之上的庫 (如 glibc) 與 應?程序 (app);
      • 內核層 : 操作系統內核;
    • ?戶層與內核層是相輔相成,?戶層的應?程序依賴于庫或者內核, 庫與內核給應?層提供服務;

    • 內核通過系統調?來給應?層提供接?。

      外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 系統調用與庫函數

    • 系統調?是 Linux 內核提供給應?程序的訪問接?, 當需要 Linux 內核提供服務時, 則需要訪問系統調?。
    • 庫函數是為了實現某個功能?封裝起來的 API 集合, 能夠提供統?的編程接?,更加便于應?程序的移植。
    • glibc 是屬于 GNU(GNU’s Not unix) ?程的?部分, 這個?程當初的?標是為了開發?款完整的操作系統, 但在開發過程中將除了 Linux 內核以外的組件都開發完成, 由于難度很?, 開發周期?, 在 1992 年 由 Linus Torvalds 開發出來了 Linux 內核, 填補了 GNU 系統的?個重要空?, 所有后?將 GNU 組件與 Linux 合并組成現在的 GNU/Linux。
    • glibc 包含 標準 c 庫函數集合 和 系統調?
      • 標準的 c 庫函數是跨平臺的,既可以在 Linux 系統下調?, 也可以在 windows 系統下調?;
      • 系統調?是 Linux 內核給?戶提供的訪問接?, 但在 glibc 中封裝了系統調?接??形成了 glibc 的庫函數;
      • glibc 庫函數主要是封裝了系統調?的過程, 相應的系統調??般實現在 Linux 內核中;
      • ?般的 glibc 中的庫函數都會與系統調?關聯,但也有庫函數不需要使?系統調?,?如字符串操作函數。

1.2 Linux io框架

1.2.1 Linux io框架介紹

  • Linux io 框架也是分層設計, 這?以將內存中的數據存儲到硬盤中為例

    外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 應?程序通過調?操作系統提供的 io 接? (函數) 向內核進? io 請求 , 由內核最終完整相應的io操作;

  • Linux io 框架基于?切皆?件的思想來設計;

    • ?的 : 屏蔽底層不同設備之間的 io 差異, 給應?層提供統?的操作接?;
    • 思想 : 即將底層的 io 操作統?抽象成?件操作,操作提供系統只需要提供?組?件 io操作接?就可以為應?程序提供 io 服務。
  • ?件 io 操作主要包含:

    • open:打開
    • close:關閉
    • read:讀取
    • write:寫?
    • lseek:定位
  • ?件 io 接?的設計本身來沿?了?的 操作習慣

    • ?腦相當于 內存設備
    • 書籍或者其他筆記本相當于另?個設備
    • 讀書 : 相當于?腦獲取數據 (read)
    • 寫字 : 相當于將?腦數據寫?到其他存儲介質中
  • 下?以 printf io 過程為例來說明

    外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

    • printf io 的過程本質上是將暫存在內存中的數據寫?到顯示器中;

    • printf 函數?先會調? glibc 中 write 函數來發出 io 請求;

    • write 函數在通過調?由操作系統內核提供的系統調? sys_write 函數最終完成 io 操作.

    • 下?是 sys_write 系統調?在內核中的實現

      SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
      {struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;if (f.file) {loff_t pos = file_pos_read(f.file);ret = vfs_write(f.file, buf, count, &pos);if (ret >= 0)file_pos_write(f.file, pos);fdput_pos(f);} return ret; 
      }
      

2.Linux文件io接口

2.1 Linux文件io簡介

2.1.1 關于Linux文件io

  • 在 Linux 系統下, ?于對?件操作的庫函數叫做?件 I/O;
  • 主要包括 open()/close()/read()/write() /lseek() 相應的系統調?(準確說法是對系統調?的封裝的庫函數)。

2.1.2 文件描述符

  • ?件描述符是?個?負整數 , 當打開?個已存在?件或者創建?個新?件時, 內核向進程返回?個?件描述符;

  • 每個程序運?后, 操作系統會默認打開三個?件(標準輸?、標準輸出、標準錯誤輸出) , ?件描述符分別為 0 , 1 , 2;

    • 標準輸?對應的設備?般為鍵盤;
    • 標準輸出與標準錯誤輸出設備?般為顯示器;
  • 示例:通過 write 函數 (后?會詳細講解) 使?標準輸出來打印 Hello world。

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    int main(int argc,char *argv[])
    {write(1,"helloworld",10); return 0;
    }
    

2.2 Linux文件io操作

2.2.1 open函數

  • open函數說明

    • 函數功能

      • 打開文件,并得到文件描述符。
    • 函數原型

      int open(const char *pathname, int flags);
      int open(const char *pathname, int flags, mode_t mode);
      
    • 頭文件說明

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      
    • 參數說明

      • pathname : ?件路徑名
      • flags : 打開標志
        • O_RDONLY: 只讀?式打開?件(read only)
        • O_WRONLY: 可寫?式打開?件(write only)
        • O_RDWR: 讀寫?式打開?件(read write)
        • O_CREAT: 如果該?件不存在就創建?個新的?件,并?第三的參數為其設置權限
        • O_EXCL: 如果使? O_CRATE 時?件存在, open() 報錯(exclusive,排外的)
        • O_TRUNC: 如果?件已經存在,并且以讀 / 寫或只寫成功打開, 并清零,即清空文件內容;
        • O_APPEND: 以添加的?式打開?件,在打開?件的同時,?件指針指向?件末尾
      • mode : 指定創建新的?件的默認權限
    • 返回值

      成功:返回?件描述符
      失敗:返回-1, 并將錯誤編碼保存到 errno
      
  • 示例1:通過只讀的?式打開?個?件

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    int main(int argc,char *argv[])
    {int fd; if (argc != 2){ /*int main(int argc, const char *argv[])argc:命令行傳遞參數的個數argv[0]:命令行傳遞的第一個參數argv[1]:命令行傳遞的第二個參數argv[2]:命令行傳遞的第三個參數*/fprintf(stderr,"Usage : < %s > < pathname >\n", argv[0]);return -1; } fd = open(argv[1], O_RDONLY);if (fd == -1){perror("Open(): ");return -1; } close(fd);return 0;
    }
    
  • 練習:以只寫的?式打開?件, 如果不存在則創建, 如果?件存在則截短(即清空文件內容)

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    int main(int argc,char *argv[])
    {int fd; if(argc != 2){printf("Usage : %s <pathname> .\n",argv[0]);}fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0644);if (fd == -1) { perror("open()");exit(-1);} printf("fd = %d\n",fd);close(fd);return 0;
    }
    
  • 函數錯誤處理與errno

    • errno 是 Linux 操作系統中?于存儲錯誤編碼的全局變量, 錯誤編碼在 Linux 系統中的定義如下:

      #define EPERM 1 
      #define ENOENT 2 
      #define ESRCH 3 
      #define EINTR 4 
      #define EIO 5 
      #define ENXIO 6 
      #define E2BIG 7 
      #define ENOEXEC 8 
      #define EBADF 9 
      #define ECHILD 10 
      #define EAGAIN 11 
      #define ENOMEM 12 
      #define EACCES 13 
      #define EFAULT 14 
      #define ENOTBLK 15 
      #define EBUSY 16
      
  • 錯誤信息打印

    • 錯誤信息打印主要使用perror() 函數。

      • 函數頭文件

        #include <stdio.h>
        
      • 函數原型

        void perror(const char *s)
        
      • 函數參數

        s : ?定義字符串參數
        
    • 錯誤信息轉換主要使? strerror() 函數, 具體說明如下:

      • 函數頭文件

        #include <string.h>
        
      • 函數原型

        char *strerror(int errnum)
        
      • 函數功能

        將錯誤編碼轉換成字符串信息,并返回該字符串的地址。

      • 函數參數

        errnum : 錯誤編碼
        
      • 函數返回值

        返回錯誤碼轉換之后的字符串 or “Unknown error nnn”。

      • 示例 : 使? perror 函數打印 出錯信息

        #include <stdio.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
        int main(int argc,char *argv[])
        {int fd; fd = open(argv[1],O_RDONLY,0644);if (fd == -1) { perror("open(): ");//perror(strerror(EIO));//將錯誤編碼EIO轉換成出錯信息字符串,通過perror函數將出錯信息字符串打印出來return -1; } return 0;
        }
        

2.2.2 close函數

  • close函數說明

    • 函數頭文件

      #include <unistd.h>
      
    • 函數原型

      int close(int fd);
      
    • 函數功能

      close 函數?于關閉?件,在 io 操作結束后需要關閉?件,釋放相關資源。

    • 函數參數

      fd : ?件描述符
      
    • 函數返回值

      成功:返回0
      失敗:返回-1
      
  • 示例:將前?已經打開的?件使? close 函數關閉。

2.2.3 read函數

  • 函數頭文件

    #include <unistd.h>
    
  • 函數原型

    ssize_t read(int fd, void *buf, size_t count)
    
  • 函數功能

    從?件中讀取數據保存緩沖區中。

  • 函數參數

    fd : ?件描述符
    buf : 數據緩沖區
    count : 能夠讀取的最?字節數
    
  • 函數返回值

    成功 : 返回實際讀取的字節數
    失敗 : -1, 并將錯誤編碼設置到 errno 中
    
  • 示例:從指定?件中讀取 10 個字節數據,并進?打印

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h> 
    #include <errno.h>
    #include <unistd.h>
    int main(int argc,char *argv[])
    {int fd;char buffer[64] = {0};ssize_t rbytes;if (argc != 2){fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1; }fd = open(argv[1],O_RDONLY); if (fd == -1){ perror("Open(): ");return -1;}rbytes = read(fd,buffer,10);if (rbytes == -1){perror("Read(): ");return -1;}printf("Buffer : %s\n",buffer);close(fd);return 0;
    }
    

2.2.4 write函數

  • 函數頭文件

    #include <unistd.h>
    
  • 函數原型

    ssize_t write(int fd, const void *buf, size_t count);
    
  • 函數功能

    把緩沖區中的數據寫入到指定文件中。

  • 函數參數

    fd : ?件描述符
    buf : 緩沖區地址
    count : 需要寫?的字節數
    
  • 函數返回值

    成功: 返回實際成功寫?的字節數
    失敗: 返回 -1, 并設置 errno
    
  • 示例:將 ABCDE12345 字符串寫?到指定?件中, 并驗證是否寫?正確

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    int main(int argc,char *argv[])
    {int fd;char buffer[64] = "ABCED12345";ssize_t wbytes;if (argc != 2){fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1;}fd = open(argv[1],O_RDWR|O_CREAT);if (fd == -1){perror("Open(): ");return -1;}wbytes = write(fd,buffer,10);if (wbytes == -1){perror("Write(): ");return -1;}close(fd);return 0;
    }
    

2.2.5 lseek函數

  • 函數原型

    off_t lseek(int fd, off_t offset, int whence);
    
  • 函數參數

    • fd : ?件描述符
    • offset : 偏移量, 可以為正數或者負數
    • whence : 偏移相對位置
      • SEEK_CUR : 相對于?件當前偏移
      • SEEK_SET : 相對于?件開始位置
      • SEEK_END : 相對于?件尾偏移
  • 函數返回值

    • 成功: 返回 0
    • 失敗 : 返回 -1, 并設置 errno
    • 當前?件的偏移量決定下次 io 操作時的起始位置
    • 對于同?個?件描述符,共享同?個偏移量
  • 示例:將?個字符串 “hello,linux io” 寫?到?件中,在讀取出來

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    int main(int argc,char *argv[])
    {int fd; char buffer[64] = "hello,Linux io";char rbuffer[64] = {0};ssize_t wbytes = 0,rbytes = 0;if (argc != 2){ fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1; } fd = open(argv[1],O_RDWR|O_CREAT);if (fd == -1){perror("Open(): ");return -1; } wbytes = write(fd,buffer,strlen(buffer));if (wbytes == -1){perror("Write(): ");return -1;} lseek(fd,0,SEEK_SET);rbytes = read(fd,rbuffer,wbytes);if (rbytes == -1){perror("Read(): ");return -1; } printf("rbuffer : %s\n",rbuffer);close(fd);return 0;
    }
    
  • 練習 : 使? Linux ?件 io 接?實現 ?件復制

    #include <unistd.h>
    #include <string.h>
    int main(int argc, const char *argv[])
    {if(argc != 3){fprintf(stderr, "Usage : <%s> <pathname>\n", argv[0]);return -1;}int source_fd = open(argv[1], O_RDONLY);int destination_fd = open(argv[2], O_RDWR | O_CREAT);int rbytes = 0;char buffer[64] = {0};if(source_fd == -1 || destination_fd == -1){perror("open()");return -1;}while(rbytes = read(source_fd, buffer, 64)){if(rbytes == -1){perror("read()");return -1;}int wbytes = write(destination_fd, buffer, rbytes);if(wbytes == -1){perror("write()");return -1;}}close(source_fd);close(destination_fd);return 0;
    }
    

3.Linux標準io接口

3.1 標準io簡介

3.1.1 標準io與文件io

  • 標準 IO 是另外?套 IO 接?,具有如下特點:
    • 標準 I/O 是屬于跨平臺, 可以在 Linux、windows、mac os 上運?, ?件 IO 只能在Linux 平臺運?
    • 標準 I/O ?帶緩沖區,有更?的 IO 效率
    • 標準 IO 提供豐富的操作?本信息接?
    • 標準 IO 底層需要依賴于 ?件 IO
    • 在 Linux 系統下, 標準 I/O 是屬于 glibc 庫的?部分

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

3.1.2 流與FILE對象

  • 流(stream):流是?串連續不斷的傳輸的數據的集合,就像?管??的?流,在?管的?端?點?點地供?,?在?管的另?端看到的是?股連續不斷的?流。

  • ?般流可以分為 ?本流 與 ?進制流

    • ?本流:
      • 在流中處理的數據是以字符出現。
      • 在?本流中,’\n’被轉換成回?符 CR 和換?符 LF的 ASCII 碼 0DH 和 0AH, ?當輸出時,0DH 和 0AH 被轉換成’\n’。
    • 二進制流:
      • 流中處理的是?進制序列。
      • 若流中有字符,則??個字節的?進制 ASCII 碼表示;若是數字,則?對應的?進制數表示
  • ?件指針:

    • FILE 指針:每個被使?的?件都在內存中開辟?個區域,?來存放?件的有關信息,這些信息是保存在?個結構體類型的變量中,該結構體類型是由系統定義的,取名為 FILE。

    • FILE 結構體定義在 /usr/libio.h 中 struct _IO_FILE

       struct _IO_FILE;typedef struct _IO_FILE __FILE;
      
    • 標準 I/O 庫的所有操作都是圍繞流 (stream) 來進?的,在標準 I/O 中,流? FILE * 來描述

    • 標準 I/O 庫是由 Dennis Ritchie 在 1975 年左右編寫的

  • ?件指針關聯到數據流的兩端, 可以抽象成 “?管”

3.2 標準輸入、標準輸出、標準錯誤輸出

3.2.1 簡介

  • 標準 I/O 預定義 3 個流對象指針, 在應?程序運??動被打開.
    • 標準輸? : 流對象操作的是標準輸?設備, 流對象指針的名稱為 stdin , 對應的?件描述符為 0
    • 標準輸出 : 流對象操作的是標準輸出設備, 流對象指針的名稱為 stdout, 對應的?件描述符為 1
    • 標準錯誤輸出: 流對象操作的是標準錯誤輸出設備, 流對象指針的名稱為 stderr, 對應的?件描述符為 2
  • 對應的 printf , 函數操作的就是 stdout , 由于是默認操作, ?般?需指定具體的流對象參數
  • 當在輸出時需要指定流對象的類型時, 則需要使? fprintf 函數

3.2.2 fprintf函數

  • 函數原型

    int fprintf(FILE *stream, const char *format, …);
    
  • 函數功能

    將格式化數據寫?到指定?件中。

  • 函數參數

    stream : 流對象指針
    format : 格式字符串
    
  • 示例 : 通過 stdout 與 stderr 進?輸出

    int main(void)
    {fprintf(stdout,"Linux std io .\n");fprintf(stderr,"can't open file.\n"); while(1){}return 0;
    }
    • 注意: 在上述程序中, 將 ‘\n’ 去掉之后, 在添加?個死循環后, 則程序運?的結果則不同, 這?是 與標準 I/O 的緩沖區有關系.

3.2.3 文件緩沖

  • 文件緩沖系統
    • 緩沖文件系統
      • 盡量減少使? read/write 的調?次數, 來提?效率, 每次進?系統調?都會涉及到從 ?戶空間到內核空間的切換以及內核進?系統調?所產?的開銷
      • 系統?動的在內存中為每?個正在使?的?件開辟?個緩沖區,從內存向磁盤輸出數據必須先 送到內存緩沖區,裝滿緩沖區在?起送到磁盤中去.
      • 從磁盤中讀數據,則?次從磁盤?件將?批數據讀?到內存緩沖區中,然后再從緩沖區逐個的 將數據送到程序的數據區

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 標準 I/O 的緩存??為 8192, 在系統中定義如下 (stdio.h):

    #define BUFSIZE 8192
    
  • ?般標準 I/O 的分類為:

  • 全緩存 : 當相應的緩沖區已經裝滿數據時, 才進??次 I/O 操作

  • ?緩存 : 當相應的緩沖區存儲??時,則進??次 I/O 操作, stdout 就是?緩存

  • 不緩存 : 直接進? I/O 操作, 不進?緩存, stderr 就是不緩存

3.2.4 緩沖區強制刷新

  • ?般情況下, 程序在結束時會 ?動刷新緩沖區, 但是當程序還未結束時, 刷新緩沖區則需要調? fflush() 函數

    • 函數原型

      int fflush(FILE *stream);
      
    • 函數功能

      強制刷新緩沖區。

    • 函數參數

      stream:流對象指針
      
    • 函數返回值

      成功:返回0
      失敗:返回-1
      
  • 示例 : 使? fflush 函數刷新緩沖區的數據

    #include <stdio.h>
    int main(void)
    {printf("hello.");fflush(stdout);while(1){}return 0;
    }
    
  • 練習 : 使? fprintf 函數 “Hello,Linux io” 到 標準輸出,并使? fflush 函數進?強制刷新.

    #include <stdio.h>int main(int argc, const char *argv[])
    {fprintf(stdout, "Hello, Linux io");fflush(stdout);	while(1){}return 0;
    }

3.3 Linux標準io-fopen/fclose

3.3.1 fopen函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數原型

     FILE *fopen(const char *pathname, const char *mode);
    
  • 函數功能

    打開?件,并獲取流對象指針.

  • 函數參數

    • pathname : 路徑名
    • mode : 打開模式
      • r或rb:以只讀方式打開?件,前提是該?件必須存在
      • r+或r+b:以可讀可寫方式打開?件,前提是該?件必須存在
      • w或wb:以只寫方式打開?件,若?件存在則?件?度清為 0, 即會清空?件以前內容。若?件不存在則創建該?件.
      • w+或w+b或wb+:以可讀可寫方式打開?件,若?件存在則?件?度清為零,即會清空?件以前內容, 若?件不存在則創建該?件.
      • a或ab:以只寫與追加的方式打開文件,若?件不存在,則會新建該?件, 如果?件存在,寫?的數據會被加到?件尾,即?件原先的內容會被保留。
  • 函數返回值

    • 成功:返回?件指針
    • 失敗:返回 NULL, 并設置 errno

3.3.2 fclose函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數原型

    int fclose(FILE *stream);
    
  • 函數功能

    關閉已經打開的?件.

  • 函數參數

    stream : ?件指針
    
  • 示例 : 以讀寫?式打開?件 test.txt,如果該?件不存在,則創建. 如果該?件已經存在,則?度截短為 0.

    int main(int argc, const char *argv[])
    {if(argc != 2){fprintf(stderr, "usage <%s> <pathname>\n", argv[1]);return -1;}FILE* fd = NULL;fd = fopen(argv[1], "w+");if(fd == NULL){fprintf(stderr, "fopen()\n");return -1;}return 0;
    }
    

3.4 Linux標準io-fgetc/fputc

3.4.1 fgetc函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數功能

    從文件中讀取一個字符。

  • 函數原型

    int fgetc(FILE* stream);
    
  • 函數參數

    stream:文件指針
    
  • 函數返回值

    • 成功:返回所讀到字符的ASCII碼
    • 失敗:返回EOF

3.4.2 fputc函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數功能

    輸出一個字符到文件中;

  • 函數原型

    int fputc(int c, FILE* stream);
    
  • 函數參數

    c:待寫?的字符
    stream:文件指針
    
  • 函數返回值

    • 成功:返回寫入字符的ASCII碼
    • 失敗:返回EOF,并設置errno
  • 示例 : 實現 cat 命令功能, 將?件中的數據顯示到 stdout 上.

    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc,char *argv[])
    {FILE *fp = NULL;if (argc != 2){   fprintf(stderr,"Usage : ./a.out <filename>\n"); exit(-1);}   fp = fopen(argv[1],"r");if (fp == NULL){   fprintf(stderr,"fopen failed.\n");  exit(-1);}   char ch; for (;;){ch = fgetc(fp);if (ch == EOF)break;fputc(ch,stdout);}fclose(fp);return 0;
    }
    

    練習: 使? fgetc 與 fputc 實現?件復制功能

    #include <stdio.h>int main(int argc, const char *argv[])
    {FILE *src_fd = NULL, *des_fd = NULL;char ch;if(argc != 3){fprintf(stderr, "usage <%s> <pathname>\n", argv[0]);return -1;}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen()\n");return -1;}while((ch = fgetc(src_fd)) != EOF){fputc(ch, des_fd);}fclose(src_fd);fclose(des_fd);return 0;
    }
    

3.5 Linux標準io-fgets/fputs

3.5.1 fgets函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數功能

    從文件中讀取一行數據到緩沖區中。

  • 函數原型

    char *fgets(char *s, int size, FILE *stream);
    
  • 函數參數

    s : 緩沖區地址
    size : 最?可讀取??
    stream : ?件指針
    
  • 函數返回值

    成功 : 返回緩沖區的地址, 當讀到?件尾時,會返回 NULL
    失敗 : 返回 NULL
    
  • 使?注意點

    • 會將’\n’ 存儲到 buffer 中
    • 會?動在 buffer 的末尾添加’\0’
    • 如果???于 size, 讀取到??就返回
    • 如果 size ?于??, 讀取 size 返回

3.5.2 fputs函數

  • 函數頭文件

    #include <stdio.h>
    
  • 函數功能

    將一行文本數據寫入到文件中。

  • 函數原型

    int fputs(const char *s, FILE *stream);
    
  • 函數參數

    s:緩沖區地址
    stream:文件指針
    
  • 函數返回值

    成功:返回?個?負數
    失敗:返回 EOF
    
  • 示例:使? fgets 與 fputs 輸出?件內容到 stdout 上

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    int main(int argc,char *argv[])
    {FILE *fp = NULL;char buffer[64];fp = fopen(argv[1],"r");if (fp == NULL){ fprintf(stderr,"cat't open file");exit(-1);} while(fgets(buffer,64,fp) != NULL){ fputs(buffer,stdout); } fclose(fp);return 0;
    }
    
  • 練習:使? fputs 與 fgets 來復制?件

    #include <stdio.h>
    #include <stdlib.h>int main(int argc, const char *argv[])
    {FILE *src_fd = NULL, *des_fd = NULL;char buffer[64] = {0};int buffer_size = sizeof(buffer);if(argc != 3){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);return -1;}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen()\n");exit(-1);}while((fgets(buffer, buffer_size, src_fd)) != NULL){fputs(buffer, des_fd);}fclose(src_fd);fclose(des_fd);return 0;
    }
    

3.6 Linux標準io-格式化輸入輸出與時間獲取

當遇到典型的格式化數據進?處理時, 就需要相應?于格式化輸? / 輸出的函數來完成, ?如?期就是典型的具有格式的數據

?期數據 : 2022 年 10 ? 22 ?

地址數據 : 湖北省武漢市…

3.6.1 格式化輸出函數

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • printf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      輸出信息到標準輸出。

    • 函數原型

      int printf(const char *format,);
      
    • 函數返回值

      實際輸出的字節數。

  • fprintf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      將格式化數據輸出到文件。

    • 函數原型

      int fprintf(FILE *stream, const char *format,);
      
    • 函數參數

      stream : 流對象指針
      format : 格式字符串
      
    • 函數返回值

      實際輸出的字節數。

  • sprintf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      將格式化數據輸出到字符串緩沖區中。

    • 函數原型

      int sprintf(char *str, const char *format,);
      
    • 函數參數

      str : 字符串緩沖區地址
      format : 格式字符串地址
      
    • 函數返回值

      實際輸出的字節數。

3.6.2 格式化輸入函數

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • scanf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      從標準輸?讀取格式化數據到緩沖區中。

    • 函數原型

      int scanf(const char *format,);
      
    • 函數參數

      format : 格式字符串地址
      
    • 函數返回值

      實際讀取的字節數。

  • fscanf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      從?件中讀取格式化數據。

    • 函數原型

      int fscanf(FILE *stream, const char *format,);
      
    • 函數參數

      stream : 流對象指針
      format : 格式字符串地址
      
    • 函數返回值

      實際讀取的字節數。

    • 例:格式化輸出

      #include <stdio.h>
      #include <stdlib.h>
      #include <errno.h>
      #include <string.h>
      int main(int argc,char *argv[])
      {FILE *fp = NULL; fp = fopen(argv[1],"w");if ( fp == NULL){ fprintf(stderr,"can't open file.\n");return -1;} int numa = 10; float numb = 1.23456;char *str = "Hello";char buffer[64];fprintf(fp,"%d-%f-%s",numa,numb,str);sprintf(buffer,"%d-%f-%s",numa,numb,str);puts(buffer);fclose(fp);return 0;
      }
      
  • sscanf函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      從字符串讀取格式化數據。

    • 函數原型

      int sscanf(const char *str, const char *format,);
      
    • 函數參數

      str : 字符串地址
      format : 格式字符串地址
      
    • 函數返回值

      實際讀取的字節數。

    • 例:格式化輸入

      #include <stdio.h>
      #include <stdlib.h>
      int main(int argc,char *argv[])
      {FILE *fp = NULL; int numa = 0,numb = 0,numc = 0; char buffer[64] = "10-20-30";if (argc != 2){ fprintf(stderr,"Usage : %s <pathname> \n",argv[0]);return -1; } fp = fopen(argv[1],"r");if (fp == NULL){perror("Error fopen(): ");return -1; } fscanf(fp,"%d-%d-%d",&numa,&numb,&numc);printf("numa = %d,numb = %d,numc = %d\n",numa,numb,numc);numa = 0,numb = 0,numc = 0;sscanf(buffer,"%d-%d-%d",&numa,&numb,&numc);printf("numa = %d,numb = %d,numc = %d\n",numa,numb,numc);fclose(fp);return 0;
      }
      

3.6.3 獲取系統時間

  • 在 Linux 中獲取主要需要以下兩個步驟

    • Step 1 : 通過 time() 函數獲取從 1970 年?今的秒數

    • Step 2 : 通過 localtime() 或者 ctime() 函數

  • time函數

    • 函數頭文件

      #include <time.h>
      
    • 函數功能

      獲取從 1970-1-1 ?今的時間秒數 (時間戳)。

    • 函數原型

      time_t time(time_t *tloc);
      
    • 函數參數

      tloc:輸出參數,存儲時間變量的指針。
    • 函數返回值

      如果參數為空,則返回當前時間距1970年1月1日00:00點 UTC的秒數;
      如果參數不為空,此時返回值和參數都為當前時間距1970年1月1日00:00點 UTC的秒數。
      
    • 注意:

      UTC就是一個全世界都用的“標準時間”。這個時間是基于非常準確的原子鐘來計算的,所以非常準確。
      UTC就像一把全球統一的“尺子”,用來量時間。這樣,不論你在世界的哪個角落,只要提到UTC時間,大家都知道現在是幾點。
      當然,每個地方因為日出日落的時間不同,所以會在UTC的基礎上加或者減幾個小時,形成自己的地方時間。
      但UTC時間就像一個基準,幫助大家更好地理解和溝通時間。
      
  • localtime函數

    • 函數頭文件

      #include <time.h>
      
    • 函數功能

      將時間戳轉換成本地時間, 并存儲到 struct tm 結構體變量中。

    • 函數原型

      struct tm *localtime(const time_t *timep);
      
    • 函數參數

      timep:這是一個指向 time_t 類型變量的指針,該變量包含了要轉換的時間。
      
    • 函數返回值

      返回 struct tm 結構體指針
      

      struct tm 的定義大致如下:

      struct tm {int tm_sec;    /* Seconds (0-60) */int tm_min;    /* Minutes (0-59) */int tm_hour;   /* Hours (0-23) */int tm_mday;   /* Day of the month (1-31) */int tm_mon;    /* Month (0-11) */int tm_year;   /* Year - 1900 */int tm_wday;   /* Day of the week (0-6, Sunday = 0) */int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */int tm_isdst;  /* Daylight saving time */
      };
      
    • 獲取當前時間并轉換本地時間,以 %d-%d-%d %d::%d::%d 進?打印

      #include <stdio.h>
      #include <time.h>
      int main(void)
      {time_t t;struct tm *p_datetime;t = time(NULL);p_datetime = localtime(&t);printf(" %d-%d-%d %d::%d::%d\n",p_datetime->tm_year + 1900,p_datetime->tm_mon + 1,p_datetime->tm_mday,p_datetime->tm_hour,p_datetime->tm_min ,p_datetime->tm_sec );return 0;
      }
      

      練習 :

      獲取系統時間,按照 <2022-5-8 23::15:00> 格式寫?到?件中

      #include <stdio.h>
      #include <time.h>
      #include <stdlib.h>
      int main(int argc, const char *argv[])
      {time_t sec = time(NULL);struct tm * date_time_p = localtime(&sec);FILE *fd = NULL;if(argc != 2){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);return -1;}fd = fopen(argv[1], "w+");if(fd == NULL){fprintf(stderr, "fopen()\n");exit(-1);}fprintf(fd, "<%d-%d-%d::%d:%d:%d>\n", date_time_p->tm_year + 1900,date_time_p->tm_mon + 1,date_time_p->tm_mday,date_time_p->tm_hour,date_time_p->tm_min,date_time_p->tm_sec);return 0;
      }

3.7.Linux標準io-?進制讀寫與?件定位

3.7.1 二進制文件讀寫

在標準 I/O 中, ?于進??進制?件進?讀寫時需要調? fread 與 fwrite。

  • fread函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      從?進制?件中讀取數據到緩沖區
      
    • 函數原型

      size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 函數參數

      ptr : 緩沖區地址
      size : 讀取每個數據塊的??
      nmemb : 讀取數據對象的個數
      stream : ?件指針
      
    • 函數返回值

      成功 : 返回實際讀取的數據對象的個數
      失敗: 當到達?件尾或者發?錯誤,返回較?的數據對象個數或者 0
      
  • fwrite函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      將緩沖區中的數據寫?到?件中 
      
    • 函數原型

      size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 函數參數

      ptr : 緩沖區地址
      size : 寫入的每個數據塊的??
      nmemb : 寫入的數據對象的個數
      stream : ?件指針
      
    • 函數返回值

      成功 : 返回實際寫入的數據對象的個數
      失敗: 當到達?件尾或者發?錯誤,返回較?的數據對象個數或者 0
      
    • 示例 : 使? fwrite 存儲?個浮點數組的數據到?件中。

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <time.h>
      #include <errno.h>
      int main(int argc,char * argv[])
      {FILE *fp = NULL;fp = fopen(argv[1], "w+");if (fp == NULL){fprintf(stderr,"can't open file.");exit(-1);}float numbers[5] = {1.1,1.2,1.3,1.4,1.5};size_t nwbytes = 0 ;nwbytes = fwrite(numbers,sizeof(float),5,fp);if (nwbytes != 5){fprintf(stderr,"fwrite():%s\n",strerror(errno));exit(-1);}rewind(fp);size_t nrbytes = 0;float rnumbers[5] = {0.0};nrbytes = fread(rnumbers,sizeof(float),5,fp);if (nrbytes != 5){fprintf(stderr,"fread():%s\n",strerror(errno));exit(-1);}int i;for (i = 0;i < 5;i++){printf(" %f ",rnumbers[i]);}putchar('\n');fclose(fp);return 0;
      }
      

3.7.2 文件定位

在對?件流進?操作時, ?般情況下都是順序操作, 但如果定位到?件流某?個地?進?操作,則需要使? fseek 函數進??件流的定位。

  • fseek函數

    • 函數頭文件

      #include <stdio.h>
      
    • 函數功能

      對?件進?定位
      
    • 函數原型

      int fseek(FILE *stream, long offset, int whence);
      
    • 函數參數

      • stream : ?件指針
      • offset : 偏移量
      • whence: 偏移相對位置
        • SEEK_SET :相對于?件頭
        • SEEK_CUR : 相對于?件當前位置
        • SEEK_END : 相對于?件尾
    • 函數返回值

      成功: 返回設置后的偏移位置
      失敗:返回 -1, 并設置 errno
      
    • 示例 : 使? fseek 函數進?數據流的定位

      int main(int argc,char * argv[])
      {FILE *fp = NULL;char temp;fp = fopen(argv[1],"r");if (fp == NULL){fprintf(stderr,"open failed.");return -1;}fseek(fp,5,SEEK_SET);temp = fgetc(fp);printf("temp = %c\n",temp);fclose(fp);return 0;
      }
      
    • 練習 : 使??進制操作接? fread 與 fwrite 復制?張圖?

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <errno.h>
      #define BUFFER_SIZE 1024
      int main(int argc, const char *argv[])
      {FILE * src_fd = NULL, * des_fd = NULL;char buffer[BUFFER_SIZE] = {0};int nrbytes = 0, nwbytes = 0;if(argc != 3){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);exit(-1);}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen():%s\n", strerror(errno));exit(-1);}while((nrbytes = fread(buffer, 1, BUFFER_SIZE, src_fd)) > 0){fwrite(buffer, 1, nrbytes, des_fd);}fclose(src_fd);fclose(des_fd);return 0;
      }

4.靜態庫與動態庫的原理與制作

4.1 程序的編譯過程

  • 程序在編譯時分為多個階段

    • 預處理

      • 處理所有預處理命名,包括宏定義、條件編譯指令、?件包含指令;
    • 編譯

      • 進?詞法分析、語法分析、語義分析后,將代碼翻譯成匯編指令;
    • 匯編

      • 將匯編指令翻譯成機器指令,也就是?進制,形成?標?件;
    • 鏈接

      • 將多個?標?件進?鏈接,得到?個程序最后的執??件。

      外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 編譯過程演示示例,假定源?件名為 hello.c

    • Step 1:預處理

      gcc -E hello.c -o hello.i
      
    • Step 2:編譯

      gcc -S hello.i -o hello.S
      
    • Step 3:匯編

      gcc -c hello.S -o hello.o
      
    • Step 4:鏈接

      gcc hello.o -o hello
      

4.2 靜態庫

4.2.1 函數庫

  • 函數庫是實現了某?類功能的若?個函數的集合。
  • 函數庫可以編譯獨?的?進制?件,在進?步制作成靜態庫與動態庫進?鏈接使?。

4.2.2 靜態庫的文件格式

  • 靜態庫是函數庫?進制?件的?種形式, 在 windows 與 Linux 下對應的?件類型。
    • Windows:name.lib
    • Linux:libname.a

4.2.3 靜態庫的特點

  • 當編譯器鏈接靜態庫的時候,如果在可執??件中有調?靜態庫的函數接?,則會將靜態庫拷?到可執??件中;
  • 由于可執??件中有靜態庫中函數接?的實現代碼,運?的時候不需要靜態庫;
  • 由于鏈接的時候,需要將庫函數接?實現代碼拷?到可執??件中,所以?成。

4.2.4 靜態庫的制作

  • 靜態庫的制作

    • 將file.c編譯成file.o

      gcc -c file.c -o file.o
      
    • 將file.o生成靜態庫

      ar -rs libfile.a file.o
      
    • ar 命令?于制作靜態庫的命令, 可以使??些常?的選項

      -s:將?標?件的索引符號添加到庫中;
      -r : 在庫中更新?件或者添加新的?件。
      

4.2.5 靜態庫的鏈接

  • 靜態庫在鏈接使?時需要指定頭?件的位置與靜態庫的位置

    • -I:指定頭?件
    • -L:指定庫的位置
    • -l:指定鏈接的庫的名字
    gcc -I <頭?件路徑> -L < 庫的路徑 > -l < 靜態庫的名字 > -o < 可執??件名 >
    
  • gcc 編譯器默認搜索頭?件與庫?件的路徑

    /usr/include 為頭?件默認路徑
    /usr/lib 與 /lib 為庫的默認路徑
    
  • 靜態庫的優點與缺點

    • 優點
      • 可執?程序在執?的時候,不需要加載動態庫,可以直接運?;
    • 缺點
      • 多個程序鏈接靜態庫的時候,需要拷?多份靜態庫的代碼,占?的內存較多;
  • 練習 : 設計?個?于進?算術運算的庫 add.h 與 add.c ,然后制作成靜態庫進?鏈接

    • 第一步:寫程序

      add.c

      int add(int a, int b)
      {return a + b;
      }
      

      add.h

      #ifndef __ADD_H__
      #define __ADD_H__
      extern int add(int a, int b);
      #endif
      

      main.c

      #include <stdio.h>
      #include "add.h"
      int main(int argc, const char *argv[])
      {printf("%d\n", add(10, 20));return 0;
      }
      
    • 第二步:寫命令

      gcc -c add.c -o add.o
      ar -rs libadd.a add.o
      gcc -I . -L .  main.c -l add -o exec
      

4.3 動態庫

4.3.1 動態庫的文件格式

  • 動態庫的文件格式如下:
    • Windows:name.dll
    • Linux:libname.so

4.3.2 動態庫的特點

  • 當編譯器鏈接動態庫的時候,會在可執??件的頭信息中記錄庫的名字,便于在操作系統執?這個可執行文件時, 讓操作系統去加載對應的動態庫。
  • 當操作系統執?可執??件時候,會先讀取可執??件的頭信息,然后加載頭信息中記錄的動態庫到內存中運?。
  • 當可執??件調?動態庫中的函數時,則需要加載動態庫到內存中。

4.3.3 動態庫的制作

  • 動態庫的制作過程如下:

    • 將 file.c 編譯成 file.o

      gcc -c file.c -o file.o
      
    • 將file.o生成動態庫

      gcc -shared file.o -o libfile.so
      

4.3.4 動態庫的鏈接

  • 動態庫庫在鏈接使?時需要指定頭?件的位置與動態庫庫的位置

    • -I:指定頭?件

    • -L:指定庫的位置

    • -l:指定鏈接的庫的名字

    gcc -I <頭?件路徑> -L < 庫的路徑 > -l < 動態庫的名字 > < 源?件 >-o < 可執??件名 >
    
  • gcc 編譯器默認搜索頭?件與庫?件的路徑

    • /usr/include 為頭?件默認路徑

    • /usr/lib 與 /lib 為庫的默認路徑

  • 與靜態庫不同的是 動態庫在鏈接成功之后,還需要加載動態庫到內存中, 編譯與加載是兩個不同的動作

    • 編譯器在編譯時并沒有將動態庫中的函數拷?到可執?程序中,只是記錄動態庫的名字;

    • 在程序運?中調?到動態苦衷的函數時時,則需要將動態庫加載到內存中;

    • 動態庫默認加載動態庫的路徑與鏈接動態庫的默認路徑是相同的,都是 /lib 與 /usr/lib;

    • 當默認路徑下沒有時,則會到 LD_LIBRARY_PATH 環境變量下去找;

    • 可以通過 LD_LIBRARY_PATH 來設置動態庫的路徑。

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: ??庫所在的路徑
    
  • 練習: 將前?次練習對應的 算術運算庫 (add.h/add.c) 制作成動態庫進?鏈接, 并測試

    gcc -c add.c -o add.o
    gcc -I . -L . main.c -l add -o exec
    export LD_LIBRARY_PATH=.
    ./exec
    

5.項目-實現一個基礎的shell程序

5.1 項目簡介與框架設計

5.1.1 項目簡介

  • 實現?個基礎的 shell 程序,主要完成兩個命令的功能 cp 和 ls
    • cp 命令主要實現:
      • ?件復制
      • ?錄復制
    • ls 命令主要實現:
      • ls -l 命令的功能

5.1.2 項目框架設計

  • 在框架設計上,采?模塊化設計思想,并具備?定的可擴展性, 具體框架如下:

    外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

    • cmd_handle 模塊: ?于解析命令相關信息,并進?命令的分發執?

    • cmd_ls 模塊 : ?于執? ls 命令

    • cmd_cp 模塊 : ?于執? cp 命令

    • cmd_xxx 模塊 : ?于擴展

5.1.3 基本框架搭建

  • step1:根據框架規劃創建相關源文件

    模塊源文件
    命令處理中?模塊cmd_handle.c、cmd_handle.h
    ls 命令模塊cmd_ls.c、cmd_ls.h
    cp 命令模塊cmd_cp.c、cmd_cp.h
    ?程管理Makefile
    主函數main.c
  • step2:創建Makefile用于管理工程

    OBJS := main.o cmd_ls.o cmd_cp.o cmd_handle.o 
    TARGET := tinyshell 
    $(TARGET): $(OBJS) @gcc $^ -o $@ @echo "Done."
    %.o:%.c @gcc -c $< -o $@ 
    clean: rm -rf *.o $(TARGET)
    
    • := 表示當前位置所賦的值,?不是整個 Makefile 展開之后的值,= 是整個 Makefile 展開之后的所賦的值

      • = 號示例

         x = fooy = $(x) barx = xyz
        
        • 上述示例中的 y 的值為 xyz bar
      • := 號示例

         x := fooy := $(x) barx := xyz
        
        • 上述示例中的 y 的值為 foo bar
    • $(TARGET) : 表示獲取 TARGET 變量的值

    • %.o : %.c :

      • % 表示通配符
      • %.o : ?于匹配任意 .o ?件, 如 cmd_handle.o ,cmd_ls.o ,…
      • %.c : ?于匹配任意 .c ?件, 如 cmd_handle.c ,cmd_ls.c ,…
  • step3:在main.c編寫基本的main函數

    int main()
    {return 0;
    }
    
  • step4:編譯測試

    在命令?輸? make 命令進?測試, 顯示 Done , 則表示編譯通過

5.1.4 項目主循環實現

  • 項?的主循環主要完成的功能:

    • step1:循環獲取?戶輸? main.c

      #include <stdio.h>
      #include <string.h>#define SZ_CMD 64int main(void)
      {char command[SZ_CMD] = {0}; for(;;){printf("TinyShell > ");fgets(command,SZ_CMD,stdin); command[strlen(command) - 1] = '\0'; if (strncmp(command,"quit",4) == 0){ printf("GoodBye\n");break;}cmd_execute(command); } return 0;
      }
      
    • step 2 : 調? cmd_handle 的 cmd_execute 接?執?相應的命令 cmd_handle.h cmd_handle.c

      cmd_handle.h

      #ifndef __CMD_HANDLE_H_
      #define __CMD_HANDLE_H_
      #define DEBUG
      extern int cmd_execute(char *cmd_str); 
      #endif
      

      cmd_handle.c

      int cmd_execute( char *cmd_str)
      {
      #ifdef DEBUGprintf("[DEBUG] : cmd string : < %s >\n",cmd_str);
      #endifreturn 0;
      }
      
    • step 3 : 編譯并執??程

5.2 命令處理框架設計——解析命令

5.2.1 解析命令與分發命令基本思路

  • 輸?的命令是?個完整字符串,?如復制 “cp test.txt test1.txt” , 在實際實現業務邏輯時需要進?拆分
  • 具體在解析字符串的步驟如下:
    • step 1 : 設計?定義的數據結構存儲拆分之后的命令信息
    • step 2 : 使? strtok 函數對命令字符串進?拆分, 并存儲到?定義數據結構中
    • step 3 : 按照命令名字分發到具體模塊中執?

5.2.2 自定義數據結構設計

  • 數據結構定義

    • 對于解析之后的字符串,需要保存到?定義的數據結構中

      • 命令名稱
      • 參數個數
      • 參數列表
    • 具體的數據結構設計如下:

      #define SZ_NAME 8	// 命令名稱的最大長度
      #define SZ_ARG 32	// 每個參數的最大長度
      #define SZ_COUNT 2 	// 參數最大個數
      #include <stdio.h>
      #include <string.h>
      #define DEBUG 
      typedef struct command
      {char cmd_name[SZ_NAME]; 				// 命令名稱char cmd_arg_list[SZ_COUNT][SZ_ARG]; 	// 參數int cmd_arg_count; 						// 參數個數
      }cmd_t;
      
  • 數據結構初始化

    • 數據結構初始化 調? init_command_struct 函數, 具體實現如下:

      void init_command_struct(cmd_t *pcmd) 
      { int i; memset(pcmd->cmd_name,0,SZ_NAME); /*頭文件:#include <string.h>函數原型:void *memset(void *str, int c, size_t n) 功能:用于將一段內存區域設置為指定的值。memset() 函數將指定的值 c 復制到 str 所指向的內存區域的前 n 個字節中,*/for (i = 0;i < SZ_COUNT;i++){ memset(pcmd->cmd_arg_list[i],0,SZ_ARG); } pcmd->cmd_arg_count = 0; 
      }
      
  • 數據結構調試打印

    • 命令數據結構的調試打印 調? print_command_info 函數,具體實現如下:

      void print_command_info(cmd_t *pcmd)
      {int i;printf("==================\n");printf("[DEBUG] cmd name : < %s >\n",pcmd->cmd_name);printf("[DEBUG] cmd arg count : < %d >\n",pcmd->cmd_arg_count);printf("[DEBUG] cmd arg list : ");for (i = 0;i < pcmd->cmd_arg_count;i++){printf(" %s ",pcmd->cmd_arg_list[i]);}printf("\n==================\n");
      }
      
  • 數據結構初始化與測試

    • 在 cmd_execute 函數中,定義命令數據結構,并進?初始化后,并進?調試

      int cmd_execute(char *cmd_str)
      {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); #ifdef DEBUG print_command_info(&command); 
      #endifreturn 0;
      }
      

5.2.3 命令解析

  • 字符串拆分函數strtok

    • 命令的解析需要調?字符串處理函數 strtok 進?拆分

    • strtok 函數具體信息如下:

      • 函數頭文件

        #include <string.h>
        
      • 函數原型

        char *strtok(char *str, const char *delim);
        
      • 函數功能

        根據指定的分割字符串進?分割
        
      • 函數參數

        str : 分割字符串的地址
        delim : 分割符
        
      • 函數返回值

        成功 : 返回分割后字符串?地址
        失敗 : 返回 NULL
        
    • 函數注意事項:

      第?次調?時,需要指定字符串的地址
      第?次調?時, 第?個參數可以填 NULL
      
    • strtok函數示例:

      #include <stdio.h>
      #include <string.h>
      int main(void)
      {char str[] = "ABC 123 XYZ";char *first = NULL;char *other = NULL;first = strtok(str," ");printf(" first : %s\n",first);while((other = strtok(NULL," "))){printf(" other : %s\n",other);} return 0;
      }/*
      輸出結果為 :ABC 123 XYZ
      */
      

      命令字符串通過 strtok 函數進?拆分后需要存儲到?定義的數據結構

  • 命令參數分拆與存儲

    • 命令字符串通過 strtok 函數進?拆分后需要存儲到?定義的數據結構

      int cmd_parse(char *cmd_str,cmd_t *pcmd)
      {char *p_cmd_name = NULL;char *p_cmd_arg = NULL;int index = 0;if (cmd_str == NULL || pcmd == NULL)return -1;p_cmd_name = strtok(cmd_str," "); 
      #ifdef DEBUGprintf("[DEBUG]: cmd_name : %s\n",p_cmd_name);
      #endifstrcpy(pcmd->cmd_name,p_cmd_name); for(;;){p_cmd_arg = strtok(NULL," "); if (p_cmd_arg == NULL)break;strcpy(pcmd->cmd_arg_list[index++],p_cmd_arg); }pcmd->cmd_arg_count = index; 
      #ifdef DEBUGprint_command_info(pcmd); 
      #endifreturn 0;
      }
      
    • 在實現了 cmd_parse 函數后,在 cmd_execute 函數中進?調?, 具體如下:

      int cmd_execute(char *cmd_str)
      {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1;
      #ifdef DEBUG print_command_info(&command);
      #endifreturn 0;
      }
      

5.2.4 分發執行

  • 當命令?解析完成之后,則需要進?具體分發到各個模塊具體執?, 這?調? cmd_dispatch函數, 具體實現如下:

    int cmd_dispatch(cmd_t *pcmd)
    {if (pcmd == NULL)return -1;if (strcmp(pcmd->cmd_name,"ls") == 0){}else if (strcmp(pcmd->cmd_name,"cp") == 0){}return 0;
    }
    
  • 在 cmd_execute 函數中調? cmd_dispatch 函數

    int cmd_execute( char *cmd_str)
    {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1;
    #ifdef DEBUG print_command_info(&command); 
    #endifret = cmd_dispatch(&command);if (ret == -1)return -1;return 0;
    }
    

5.3 CP命令設計與實現

5.2.1 需求分析

  • 完成?個?錄的復制,具體要求如下:

    • 實現?件復制

      cp 1.txt 2.txt

    • 實現?錄復制

      cp src_dir dest_dir

5.2.2 思路分析

  • 總體思路

    • 根據?件類型進?判斷,如果是普通?件,則直接進?復制, 如果是?錄,則遞歸復制?錄。
  • 基本思路如下:

    • 判斷?件類型
      • 是普通?件, 則直接進?復制
      • 是?錄,則遞歸進??錄復制
    • 復制目錄
      • 在?標路徑創建新的同名?錄
      • 打開?錄
      • 遍歷?錄
        • 獲取?件名,并合成源?錄絕對路徑以及?標?錄絕對路徑
        • 根據路徑判斷源?件類型
          • 是?件,則直接進?復制
          • 是?錄,則繼續進?遞歸復制
    image-20240508195953360

5.2.3 框架設計

  • 命令執行接口設計
    • cp 的命令的總的??函數為 cmd_cp_execute 函數, 具體邏輯如下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 文件信息數據結構定義
    • 復制文件的相關信息的結構體定義如下:

)
return -1;
p_cmd_name = strtok(cmd_str," “);
#ifdef DEBUG
printf(”[DEBUG]: cmd_name : %s\n",p_cmd_name);
#endif
strcpy(pcmd->cmd_name,p_cmd_name);
for(;😉
{
p_cmd_arg = strtok(NULL," ");
if (p_cmd_arg == NULL)
break;
strcpy(pcmd->cmd_arg_list[index++],p_cmd_arg);
}
pcmd->cmd_arg_count = index;
#ifdef DEBUG
print_command_info(pcmd);
#endif
return 0;
}
```

  • 在實現了 cmd_parse 函數后,在 cmd_execute 函數中進?調?, 具體如下:

    int cmd_execute(char *cmd_str)
    {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1;
    #ifdef DEBUG print_command_info(&command);
    #endifreturn 0;
    }
    

5.2.4 分發執行

  • 當命令?解析完成之后,則需要進?具體分發到各個模塊具體執?, 這?調? cmd_dispatch函數, 具體實現如下:

    int cmd_dispatch(cmd_t *pcmd)
    {if (pcmd == NULL)return -1;if (strcmp(pcmd->cmd_name,"ls") == 0){}else if (strcmp(pcmd->cmd_name,"cp") == 0){}return 0;
    }
    
  • 在 cmd_execute 函數中調? cmd_dispatch 函數

    int cmd_execute( char *cmd_str)
    {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1;
    #ifdef DEBUG print_command_info(&command); 
    #endifret = cmd_dispatch(&command);if (ret == -1)return -1;return 0;
    }
    

5.3 CP命令設計與實現

5.2.1 需求分析

  • 完成?個?錄的復制,具體要求如下:

    • 實現?件復制

      cp 1.txt 2.txt

    • 實現?錄復制

      cp src_dir dest_dir

5.2.2 思路分析

  • 總體思路

    • 根據?件類型進?判斷,如果是普通?件,則直接進?復制, 如果是?錄,則遞歸復制?錄。
  • 基本思路如下:

    • 判斷?件類型
      • 是普通?件, 則直接進?復制
      • 是?錄,則遞歸進??錄復制
    • 復制目錄
      • 在?標路徑創建新的同名?錄
      • 打開?錄
      • 遍歷?錄
        • 獲取?件名,并合成源?錄絕對路徑以及?標?錄絕對路徑
        • 根據路徑判斷源?件類型
          • 是?件,則直接進?復制
          • 是?錄,則繼續進?遞歸復制
    image-20240508195953360

5.2.3 框架設計

  • 命令執行接口設計
    • cp 的命令的總的??函數為 cmd_cp_execute 函數, 具體邏輯如下:

[外鏈圖片轉存中…(img-SOp0sRMG-1715490401070)]

  • 文件信息數據結構定義
    • 復制文件的相關信息的結構體定義如下:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10673.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10673.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10673.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

FANUC機器人基本保養概述

對于工業機器人來說&#xff0c;定期保養機器人可以延長機器人的使用壽命。對于FANUC機器人來說&#xff0c;FANUC機器人的常規保養周期可以分為日常、三個月、六個月、一年、兩年、三年。以下是FANUC機器人的基本保養周期概覽&#xff1a; 在實際生產應用中&#xff0c;可以參…

具身智能論文

目錄 1. PoSE: Suppressing Perceptual Noise in Embodied Agents for Enhanced Semantic Navigation2. Embodied Intelligence: Bionic Robot Controller Integrating Environment Perception, Autonomous Planning, and Motion Control3. Can an Embodied Agent Find Your “…

7.STL_string(詳細)

1. 什么是STL STL(standard template libaray-標準模板庫)&#xff1a;是C標準庫的重要組成部分&#xff0c;不僅是一個可復用的組件庫&#xff0c;而且 是一個包羅數據結構與算法的軟件框架。 2. STL的版本 原始版本 Alexander Stepanov、Meng Lee 在惠普實驗室完成的原始版…

maven遠程倉庫訪問順序

首先需要了解一下各個配置文件&#xff0c;主要分為三類&#xff1a; 全局配置文件(${maven.home}/conf/settings.xml)&#xff0c;maven安裝路徑下的/conf/settings.xml用戶配置文件(%USER_HOME%/.m2/settings.xml)&#xff0c;windows用戶文件夾下項目配置文件&#xff1a;p…

C/C++ 入門(10)list類(STL)

個人主頁&#xff1a;仍有未知等待探索-CSDN博客 專題分欄&#xff1a;C 歡迎來指教&#xff01; 目錄 一、標準庫中的list 1、了解 2、常用接口說明 a.常見的構造函數 b.迭代器 c. Capacity?編輯 d.Element access e.Modifiers 二、實現 1、框架 a.節點 b.迭代器 …

簡單易懂的Java Queue入門教程!

哈嘍&#xff0c;各位小伙伴們&#xff0c;你們好呀&#xff0c;我是喵手。運營社區&#xff1a;C站/掘金/騰訊云&#xff1b;歡迎大家常來逛逛 今天我要給大家分享一些自己日常學習到的一些知識點&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相學習&#xff0c;一…

如何建設智慧黨校

隨著信息技術的飛速展開&#xff0c;特別是近年移動互聯網技術&#xff0c;物聯網技術&#xff0c;人工智能技術&#xff0c;大數據數據的深入展開&#xff0c;我國快速的進入信息化社會&#xff0c;信息化對各行各業的改造越來越深入&#xff0c;任何職業&#xff0c;任何安排…

SSM【Spring SpringMVC Mybatis】—— Spring(一)

目錄 1、初識Spring 1.1 Spring簡介 1.2 搭建Spring框架步驟 1.3 Spring特性 1.5 bean標簽詳解 2、SpringIOC底層實現 2.1 BeanFactory與ApplicationContexet 2.2 圖解IOC類的結構 3、Spring依賴注入數值問題【重點】 3.1 字面量數值 3.2 CDATA區 3.3 外部已聲明be…

淺談ArrayList和LinkedList的區別

ArrayList和LinkedList在Java中都是常用的List接口的實現類&#xff0c;但它們之間存在一些顯著的區別。 實現方式&#xff1a; ArrayList&#xff1a;基于數組實現。內部使用一個動態數組來存儲元素&#xff0c;這意味著可以通過索引快速訪問元素&#xff0c;時間復雜度為O(1)…

算法學習筆記(Nim游戲)

N i m Nim Nim游戲 n n n堆物品&#xff0c;每堆有 a i a_i ai?個&#xff0c;每個玩家輪流取走任意一堆的任意個物品&#xff0c;但不能不取&#xff0c;取走最后一個物品的人獲勝。 N i m Nim Nim游戲是一種經典的公平組合游戲。現在對它進行分析。 首先定義兩個博弈中的狀…

【Chisel】chisel中怎么處理類似verilog的可變位寬和parameter

在 Chisel 中處理可變位寬和參數的方式與 Verilog 有一些不同&#xff0c;因為 Chisel 是建立在 Scala 語言之上的。以下是如何在 Chisel 中處理這些概念的方法&#xff1a; 參數化&#xff08;Parameters&#xff09; 在 Chisel 中&#xff0c;參數化是通過在模塊構造函數中定…

VUE使用餓了么的上傳組件時實現圖片預覽

創作靈感 最近在寫項目時&#xff0c;遇到了上傳頭像的需求&#xff0c;我使用的是element組件中的upload組件。但是在使用時&#xff0c;我需要實現預覽、手動上傳頭像等功能。然而在使用餓了么組件時&#xff0c;這些功能還是需要我們自己去手動實現的&#xff0c;在手動實現…

Linux makefile進度條

語法 在依賴方法前面加上就不會顯示這一行的命令 注意 1.make 會在當前目錄下找名為“makefile” 或者 “Makefile” 的文件 2.為了生成第一依賴文件&#xff0c;如果依賴文件列表有文件不存在&#xff0c;則會到下面的依賴關系中查找 3..PHONY修飾的依賴文件總是被執行的 …

Redis——RDB、AOF和混合持久化機制

Redis提供了三種持久化機制來確保數據的持久保存&#xff0c;分別是RDB&#xff08;Redis DataBase&#xff09;、AOF&#xff08;Append Only File&#xff09;和混合持久化。 RDB&#xff08;Redis DataBase&#xff09; RDB持久化機制是將Redis在內存中的數據保存到磁盤上的…

xss-lab 1-18關payload

Less-1 ?name<script>alert()</script> Less-2 "><script>alert()</script> "οnclick"alert() " οnfοcus"alert() " οnblur"alert() Less-3 οnfοcusalert() οnbluralert() οnfοcusjavascript:aler…

Spring AopUtils深度解析:從入門到精通的全方位指南

1. 概述 AopUtils是Spring框架中的一個工具類&#xff0c;主要用于處理AOP&#xff08;面向切面編程&#xff09;相關的操作。它提供了一系列靜態方法&#xff0c;幫助開發者更方便地處理AOP中的對象、代理以及通知&#xff08;Advice&#xff09;等。 2. 用途 AopUtils的主要…

操作系統原理與系統——實驗十三多道批處理作業調度(作業可移動)

關鍵代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct data{int hour;//當前小時int min;//當前分鐘 }time; struct node{char name[20];//進程名time arrive;//到達就緒隊列時間int zx;//執行時間(預期時間)int size;int ta…

Polygon市值機器人

隨著區塊鏈技術的蓬勃發展和數字貨幣市場的日益繁榮&#xff0c;投資者們對于如何精準把握市場動態、實現資產穩健增長的需求愈發迫切。在這個背景下&#xff08;市值管理飛//機//aishutuyu&#xff09;&#xff0c;Polygon市值機器人應運而生&#xff0c;作為一款基于Polygon公…

LeetCode 第397場周賽個人題解

目錄 100296. 兩個字符串的排列差 原題鏈接 思路分析 AC代碼 100274. 從魔法師身上吸取的最大能量 原題鏈接 思路分析 AC代碼 100281. 矩陣中的最大得分 原題鏈接 思路分析 AC代碼 100312. 找出分數最低的排列 原題鏈接 思路分析 AC代碼 100296. 兩個字符串的排…

timerfd加epoll封裝定時器

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 1、用timerfd加epoll封裝定時器的優點2、代碼實現 1、用timerfd加epoll封裝定時器的優點 定時器為什么需要timerfd 在設計定時器時&#xff0c;我們首先想到的就是…