.html
.linux
.面試
.shell
.編程
目錄vim
(一) 一塊兒學 Unix 環境高級編程 (APUE) 之 標準IO數組
(二) 一塊兒學 Unix 環境高級編程 (APUE) 之 文件 IO安全
(三) 一塊兒學 Unix 環境高級編程 (APUE) 之 文件和目錄網絡
(四) 一塊兒學 Unix 環境高級編程 (APUE) 之 系統數據文件和信息多線程
(五) 一塊兒學 Unix 環境高級編程 (APUE) 之 進程環境
(六) 一塊兒學 Unix 環境高級編程 (APUE) 之 進程控制
(七) 一塊兒學 Unix 環境高級編程 (APUE) 之 進程關係 和 守護進程
(八) 一塊兒學 Unix 環境高級編程 (APUE) 之 信號
(九) 一塊兒學 Unix 環境高級編程 (APUE) 之 線程
(十) 一塊兒學 Unix 環境高級編程 (APUE) 之 線程控制
(十一) 一塊兒學 Unix 環境高級編程 (APUE) 之 高級 IO
(十二) 一塊兒學 Unix 環境高級編程 (APUE) 之 進程間通訊(IPC)
(十三) [終篇] 一塊兒學 Unix 環境高級編程 (APUE) 之 網絡 IPC:套接字
最近在學習 APUE,因此順便將每日所學記錄下來,一方面爲了鞏固學習的知識,另外一方面也爲一樣在學習APUE的童鞋們提供一份參考。
本系列博文均根據學習《UNIX環境高級編程》一書總結而來,若有錯誤請多多指教。
APUE主要討論了三部份內容:文件IO、併發、進程間通訊。
文件IO:
標準IO:優勢是可移植性高,缺點是性能比系統 IO 差,且功能沒有系統 IO 豐富。
系統IO:由於是內核直接提供的系統調用函數,因此性能比標準 IO 高,可是可移植性比標準 IO 差。
併發:
信號 + 多進程;
多線程;
進程間通訊:
FIFO:管道;
System V:又稱爲 XSI,支持如下三種方式:
msg:消息隊列;
sem:信號量;
shm:共享存儲;
Socket:套接字(網絡通訊);
本系列博文就是圍繞着這些內容進行學習和總結出來的,可是APUE一書講述的主要是 Unix 環境,而 LZ 用的是 Linux 環境,因此本系列博文的全部內容都是基於 Linux 環境的,僅供各位童鞋參考。LZ 儘可能多講原理,少講函數的具體使用,在使用 LZ 提到的函數的時候,如與各位開發環境的 man 手冊衝突,則以 man 手冊爲準。
你們在學習的過程當中必定要使用 Linux 系統,每學習到一個新的函數或知識點的時候要本身運行起來。
從 LZ 的經驗來看,僅僅看 LZ 寫的文字和你本身手動作實驗得出的結果是不一樣的。只看不練的話跟沒看過同樣,什麼都是看着簡單,寫起來才知道哪裏有坑。
若是你由於工做等關係實在沒法在電腦上安裝 Linux 系統,那麼在虛擬機裏安裝一個 Linux 也是很必要的。
若是你真的想學好 APUE,那麼最好使用真正的 Linux。APUE 就是讓你在 Linux 下面寫程序用的,只有使用它才能體會到你平時使用的各類程序和命令是如何實現的,只有是使用它你才知道你須要什麼程序,你本身能夠寫什麼程序。邊使用邊學習你才能體會到 Linux 的種種設計思想,才能融入到整個 *nix 你們庭來。
廢話先寫到這裏。預祝你們學習成功,記住,堅持就是勝利。
==============================華麗的分割線==============================
好了,經過簡單的介紹相信你們對 APUE 的結構已經有了一個大體的瞭解了,接下來就開始今天的正題:文件 IO。
前面提到了標準 IO(STDIO)和系統 IO(SYSIO),那麼這裏整理一下它們的差異。
類型 | 可移植性 | 實時性 | 吞吐量 | 功能 |
STDIO | 高 | 低 | 高 | 受限 |
SYSIO | 低 | 高 | 低 | 自由 |
這裏我一個一個的解釋表格中的每一項,表格中的每一項都是二者之間相對而言,使用哪一種 IO 並無絕對的好壞之分,要根據實際的需求來決定應該使用哪一個。
可移植性:
標準 IO 是 C89 支持的函數,因此使用了標準 IO 的程序不管在 Linux 平臺仍是換成了 Windows 平臺,不用修改代碼是能夠直接編譯運行的。
而系統 IO 是由內核直接提供的函數庫實現的,不一樣的操做系統平臺上提供的 IO 操做接口是不一樣的,因此想要移植使用了系統 IO 的程序,必須按照目標平臺的 IO 庫修改程序並從新調試。
因此你寫的程序未來可能在不一樣的平臺上運行,那麼最好使用標準 IO 庫;若是你的程序是專門針對於某個平臺而開發的,那麼使用系統 IO 庫可以獲得咱們下面說的其它優點。
實時性和吞吐量:
講這兩個概念以前我先給你們看一段代碼:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main (void) 5 { 6 putchar('a'); 7 write(1, "b", 1); 8 9 putchar('a'); 10 write(1, "b", 1); 11 12 putchar('a'); 13 write(1, "b", 1); 14 15 printf("\n"); 16 17 return 0; 18 }
1 >$ gcc -Wall 1.c 2 >$ ./a.out 3 bbbaaa
輸出的結果爲何不是 ababab 呢,這就是由於標準 IO 具備合併系統調用的功能,putchar(3) 將本應該執行屢次的 write(2) 動做合併成了一步來完成,因此 aaa 是做爲一個字符串打印的,這一點咱們能夠經過 strace(1) 命令跟蹤系統調用來得出結論(下方第7行)。另外因爲 stdout 默認使用的是行緩衝模式(下面會講緩衝),因此對 putchar(3) 的調用並無當即打印出來。
1 >$ strace ./a.out 2 # ... 此處省略 n 行不相關內容 3 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f077a35f000 4 write(1, "b", 1b) = 1 5 write(1, "b", 1b) = 1 6 write(1, "b", 1b) = 1 7 write(1, "aaa\n", 4aaa 8 ) = 4 9 exit_group(0) 10 >$
到這裏咱們仍是沒有說明白爲何標準 IO 吞吐量高,而系統 IO 實時性高。我再舉個簡單的栗子:門衛老大爺負責送信到郵局,他去一次郵局要花費 10 分鐘的時間,而每次最多能送 20 封信,每當信件累計到 20 封的時候他就要動身去郵局了。可是當他收到一封加急的郵件時,就會當即去一趟郵局。系統 IO 就比如每收到一封信時都要去一趟郵局,因此實時性高。而標準 IO 就比如要攢夠 20 封信纔去一趟郵局,因此吞吐量高,由於用戶把信件交到老大爺的手上時就會當即返回,響應速度快,用戶體驗更好。而咱們使用 fflush(3) 之類的函數強制刷新緩衝的時候,就至關因而老大爺收到了一封加急信件須要當即去一趟郵局送信。
至於這裏所說的標準 IO 功能受限,是由於標準 IO 在各個平臺上都是使用系統 IO 封裝的,爲了使它具備通用性,又要考慮底層操做系統各自平臺在實現上的差別,不免在功能上就要做出讓步。
今天講的全部的 IO 操做都是標準 IO,若是是方言我會單獨標識出來。
首先要了解的一個概念是文件位置指針。
當咱們打開一個文件要對它進行讀寫的時候,咱們怎麼能知道要從哪裏開始讀(寫)文件呢?其實標準庫準備了一個工具輔助咱們讀寫文件,它就是文件位置指針。當咱們使用標準庫函數操做文件的時候,它會自動根據文件位置指針找到咱們要操做的位置,也會隨着咱們的讀寫操做而自動修改指向,而不用咱們本身手動記錄和修改文件的操做位置。它使用起來很是方便,以致於你徹底感受不到它的存在,可是爲了更好的理解文件 IO,你必須知道它的做用。
1.fopen(3)
1 fopen - stream open functions 2 3 #include <stdio.h> 4 5 FILE *fopen(const char *path, const char *mode);
這是今天要學習的第一個函數,在操做文件以前,咱們須要經過 fopen() 函數將文件打開,經過這個函數咱們能夠告訴操做系統咱們要操做的是哪一個文件,以及用什麼樣的方式操做這個文件。
參數列表:
path:要操做的文件路徑。
mode:文件的打開方式,這個打開方式一共分爲6種。
r:以只讀的方式打開文件,而且文件位置指針會被定位到文件首。若是要打開的文件不存在則報錯。
r+:以讀寫的方式打開文件,而且文件位置指針會被定位到文件首。若是要打開的文件不存在則報錯。
w:以只寫的方式打開文件,若是文件不存在則建立,若是文件已存在則被截斷爲 0 字節,而且文件位置指針會被定位到文件首。
w+:以讀寫的方式打開文件,若是文件不存在則建立,若是文件已存在則被截斷爲 0 字節,而且文件位置指針會被定位到文件首。
a:以追加的方式打開文件,若是文件不存在則建立,且文件位置指針會被定位到文件最後一個有效字符的後面(EOF,end of the file)。
a+:以讀和追加的方式打開文件,若是文件不存在則建立,且讀文件位置指針會被初始化到文件首,可是老是寫入到最後一個有效字符的後面(EOF,end of the file)。
返回值:
FILE 是一個由標準庫定義的結構體,各位童鞋不要企圖經過手動修改結構體裏的內容來實現文件的操做,必定要經過標準庫函數來操做文件。
這個函數返回一個 FILE 類型的指針,它做爲咱們打開文件的憑據,後面全部對這個文件的操做都須要使用這個指針,並且使用以後必定不要忘記調用 fclose(3) 函數釋放資源。
若是該函數返回了一個指向 NULL 的指針,則表示文件打開失敗了,能夠經過 errno 獲取到具體失敗的緣由。
error 是什麼呢?它是標準 C 中定義的一個整形值,用來表示上次發生的錯誤。你們能夠在頭文件中看看 errno 都定義了哪些值:
>$ vim /usr/include/asm-generic/errno.h
>$ vim /usr/include/asm-generic/errno-base.h
一般系統調用會給咱們我返回一個整形值來表示是否出現了錯誤,當出現了錯誤的時候會設置 errno,經過 errno 咱們就能夠得知出現了什麼錯誤了。
固然,直接給咱們一個數字,咱們本身再從頭文件中查找這個數字表示的意義,而後再打印出來給用戶看,彷佛態麻煩了,沒有什麼簡便的辦法嗎?
別擔憂,其實標準庫已經爲咱們準備好專門的轉換函數了:perror(3) 和 strerror(3)
perror(3) 會自動讀取 errno 幫咱們轉換成對應的文字描述,而且將它們輸出到標準錯誤流中。它的參數是一個字符串,用來讓咱們自定義一些錯誤消息給用戶看,它的輸出格式就是 咱們給傳遞的參數:errno 轉換的描述文字。
strerror(3) 函數也會將 errno 轉換爲文字,不過它不會自動讀取 errno 當前的值,須要咱們把 errno 傳遞給它。它也不會幫咱們輸出到標準輸出中,而是將轉換完的字符串返回給咱們。
若是你們是開發一個前臺應用,通常可使用 perror(3) 函數直接將錯誤輸出給用戶。
若是你們開發的是後臺應用(如守護進程等),那麼通常先使用 strerrno(3) 函數將 errno 轉換爲字符串,而後再把這個字符串傳給日誌系統記錄下來。
你們在使用 errno 這個全局變量的時候要導入 errno.h 頭文件:
#include <errno.h>
在使用 strerror(3) 函數時不要忘記導入 string.h 頭文件,不然會報段錯誤!
#include <string.h>
其實如今的不少 *nix 系統中,errno 早已不是全局變量了,爲了線程安全它已經變成了一個宏定義,這個咱們在後面的博文中介紹線程的時候會討論它。
2.fclose(3)
1 fclose - close a stream 2 3 #include <stdio.h> 4 5 int fclose(FILE *fp);
這個函數是與 fopen(3) 函數對應的,當咱們使用完一個文件以後,須要調用 fclose(3) 函數釋放相關的資源,不然會形成內存泄漏。當一個 FILE 指針被 fclose(3) 函數成功釋放後,這個指針所指向的內容將不能再次被使用,若是須要再次打開文件還須要調用 fopen(3) 函數。
參數列表:
fp:fopen(3) 函數的返回值做爲參數傳入便可。
3.fgets(3)
1 fgets - input of strings 2 3 #include <stdio.h> 4 5 int fgetc(FILE *stream); 6 7 char *fgets(char *s, int size, FILE *stream);
從輸入流 stream 中讀取一個字符串回填到 s 所指向的空間。
這裏出現了一個 stream 的概念,這個 stream 是什麼呢,它被成爲「流」,其實就是操做系統對於能夠像文件同樣操做的東西的一種抽象。它並不是像天然界的小河流水同樣潺潺細流,而一般是要麼沒有數據,要麼一會兒來一坨數據。固然 stream 也未必必定就是文件,好比系統爲每一個進程默認打開的三個 stream:stdin、stdout、stderr,它們自己就不是文件,就是與文件有着相同的操做方式,因此一樣被抽象成了「流」。
這個函數並無解決 gets(3) 函數可能會致使的數組越界問題,而是經過犧牲了獲取數據的正確性來保證程序不會出現數組越界的錯誤,其實是掩蓋了 gets(3) 的問題。
該函數遇到以下四種狀況會返回:
1.當讀入的數據量達到 size - 1 時;
2.當讀取的字符遇到 \n 時;
3.當讀取的字符遇到 EOF 時;
4.當讀取遇到錯誤時;
而且它會在讀取到的數據的最後面添加一個 \0 到 s 中。
返回值:
成功時返回 s。
返回 NULL 時表示出現了錯誤或者讀到了 strem 的末尾(EOF)。
4.fread(3)、fwrite(3)
1 fread, fwrite - binary stream input/output 2 3 #include <stdio.h> 4 5 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 6 7 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
這兩個函數使用得最頻繁,用來讀寫 stream,一般是用來讀寫文件。
參數列表:
ptr:fread(3) 將從 stream 中讀取出來的數據回填到 ptr 所指向的位置;fwrite(3) 則將從 ptr 所只想的位置讀取數據寫入到 stream 中;
size:要讀取的每一個對象所佔用的字節數;
nmemb:要讀取出多少個對象;
stream:數據來源或去向;
返回值:
注意這兩個函數的返回值表示的是成功讀(寫)的對象的個數,而不是字節數!
例如:
read(buf, 1, 10, fp); // 讀取 10 個對象,每一個對象 1 個字節
read(buf, 10, 1, fp); // 讀取 1 個對象,每一個對象 10 個字節
當數據量充足的時候,這兩種方式是沒有區別的。
可是!!當數據量少於 size 個字節的整倍數時,第二種方法的的最後一個對象會讀取失敗。好比數據只有 45 個字節,那麼第二種方法的返回值爲 4,由於它只能成功讀取 4 個對象。
因此一般第一種方式讀寫數據使用得比較廣泛。
5.atoi(3)
1 atoi, atol, atoll, atoq - convert a string to an integer 2 3 #include <stdlib.h> 4 5 int atoi(const char *nptr); 6 long atol(const char *nptr); 7 long long atoll(const char *nptr); 8 long long atoq(const char *nptr);
atoi(3) 函數族在這裏提一下,主要是爲了下面的 printf(3) 函數族作一個鋪墊。
這些函數的做用是方便的將一個字符串形式的數字轉換爲對應的數字類型的數字。
上面這句話可能有點坳口,給你看個例子就懂了,下面是僞代碼。
1 char *str = "123abc456"; 2 int i = 0; 3 i = atoi(str);
i 的結果會變成 123。這些函數會轉換一個字符串中地一個非有效數字前面的數字。若是很不幸這個字符串中的第一個字符就不是一個有效數字時,那麼它們會返回 0。
6.printf(3)
1 printf, fprintf, sprintf, snprintf - formatted output conversion 2 3 #include <stdio.h> 4 5 int printf(const char *format, ...); 6 int fprintf(FILE *stream, const char *format, ...); 7 int sprintf(char *str, const char *format, ...); 8 int snprintf(char *str, size_t size, const char *format, ...);
printf(3) 函數你們必定不會陌生了,應該從寫 Hello World! 的時候就接觸到了的吧,因此我也很少介紹了,主要介紹兩個內容。
一個是面試常考的一個問題,用了這麼久的 printf(3) 函數,你們有沒有注意過它的返回值表示什麼呢?
printf(3) 的返回值表示成功打印的有效字符數量,不包括 \0。
另外一個要說的就是剛纔咱們提到了 atoi(3) 函數族,它們負責將字符串轉換爲數字,那麼有沒有什麼函數能夠將數字轉換爲字符串呢,其實經過 sprintf(3) 或 snprintf(3) 就能夠了。
有了這兩個函數,不只能夠方便的將數字轉換爲字符串,還能夠將多個字符串任意拼接爲一個完整的字符串。
這裏直接講解一下 snprintf(3) 函數。
參數列表:
str:拼接以後的結果會回填到這個指針所指向的位置;
size:size - 1 爲回填到 str 中的最大長度,數據超過這個長度的部分則會被捨棄,而後會在拼接的字符串的尾部追加 \0;
format:格式化字符串,用法與 printf(3) 相同,這裏再也不贅述;
...:格式化字符串的參數,用法與 printf(3) 相同;
這個函數與 fputs(3) 同樣,只是掩蓋了 sprintf(3) 可能會致使的數組越界問題,經過犧牲數據的正確性來保證程序不會出現數組越界的錯誤。
7.scanf(3)
1 scanf, fscanf, sscanf - input format conversion 2 3 #include <stdio.h> 4 5 int scanf(const char *format, ...); 6 int fscanf(FILE *stream, const char *format, ...); 7 int sscanf(const char *str, const char *format, ...);
scanf(3) 函數族相信也不用過多的介紹了,這裏惟一要強調的就是:scanf(3) 函數支持多種格式化參數,惟獨 %s 是不能安全使用的,可能會致使數組越界,因此當須要接收用戶輸入的時候可使用 fgets(3) 等函數來替代。
8.fseek(3)
1 fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream 2 3 #include <stdio.h> 4 5 int fseek(FILE *stream, long offset, int whence); 6 7 long ftell(FILE *stream); 8 9 void rewind(FILE *stream);
fseek(3) 函數族的函數用來控制和獲取文件位置指針所在的位置,從而可以使咱們靈活的讀寫文件。
介紹一下 fseek(3) 函數的參數列表:
stream:這個已經不須要多介紹了吧,就是準備修改文件位置指針的文件流;
offset:基於 whence 參數的偏移量;
whence:相對於文件的哪裏;有三個宏定義能夠做爲它的參數:SEEK_SET(文件首), SEEK_CUR(當前位置), or SEEK_END(文件尾);
返回值:
成功返回 0;失敗返回 -1,而且會設置 errno。
單獨看參數列表也許你還有所疑惑,那麼我寫點簡單的僞代碼做爲例子:
1 fseek(fp, -10, SEEK_CUR); // 從當前位置向前偏移10個字節。 2 fseek(fp, 2GB, SEEK_SET); // 能夠製造一個空洞文件,如迅雷剛開始下載時產生的文件。
ftell(3) 函數以字節爲單位得到文件指針的位置。
fseek(fp, 0, SEEK_END) + ftell(3) 能夠計算出文件總字節大小。
還有一個值得你們注意的問題:
fseek(3) 和 ftell(3) 的參數和返回值使用了 long,因此取值範圍爲 -2GB ~ (2GB-1),而 ftell(3) 只能表示 2G-1 以內的文件大小,因此可使用 fseeko(3) 和 ftello(3) 函數替代它們,但它們只是方言(SUSv2, POSIX.1-2001.)。
因爲這兩個函數比較古老,因此設計的時候認爲 +-2GB 的取值範圍已經足夠用了,而沒有意識到科技發展如此迅速的今天,2GB 大小的文件已經徹底不能知足實際的需求了。
rewind(3) 函數將文件位置指針移動到文件起始位置,至關於:
1 (void) fseek(stream, 0L, SEEK_SET)
9.getline(3)
1 getline - delimited string input 2 3 #include <stdio.h> 4 5 ssize_t getline(char **lineptr, size_t *n, FILE *stream); 6 7 Feature Test Macro Requirements for glibc (see feature_test_macros(7)): 8 9 getline(): 10 Since glibc 2.10: 11 _POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700 12 Before glibc 2.10: 13 _GNU_SOURCE
這個函數是一個很是好用的函數,它能幫助咱們一次獲取一行數據,而不管這個數據有多長。
參數列表:
lineptr:一個一級指針的地址,它會將讀取到的數據填寫到一級指針指向的位置,並將>該位置回填到該參數中。指針初始必須置爲NULL,該函數會根據指針是否爲 NULL 來決定是否須要分配新的內存。
n:是由該函數回填的申請的內存緩衝區的總大小,長度初始必須置爲0。
雖然很好用,可是各位童鞋別高興得太早了,該函數僅支持 GNU 標準,因此是方言,你們仍是本身封裝一個備用吧。
另外,想要使用這個函數必須在編譯的時候指定 -D_GNU_SOURCE 參數:
1 $> gcc -D_GNU_SOURCE
固然若是不想在編譯的時候添加參數,也能夠在引用頭文件以前 #define _GNU_SOURCE,只是比較醜陋而已。
還有一個辦法,是在 makefile 中配置 CFLAGS += -D_GNU_SOURCE,這樣即省去了編譯時手動寫參數的麻煩,也避免了代碼中的醜陋。
10.fflush(3)
1 fflush - flush a stream 2 3 #include <stdio.h> 4 5 int fflush(FILE *stream);
fflush(3) 函數的做用是刷新緩衝區,提到這個函數就要講講緩衝區了。
緩衝區的做用是爲了合併系統調用,在上面講 STDIO 與 SYSIO 的區別時你們已經看到什麼是合併系統調用了。
Linux 系統中有三種緩衝形式:無緩衝、行緩衝和全緩衝。
無緩衝:須要馬上輸出時使用,例如 stderr;
行緩衝:遇到換行符時進行刷新、緩衝區滿了的時候刷新、強制刷新(fflush(3));而標準輸出(stdout)是行緩衝,由於涉及到終端設備;
全緩衝:只有緩衝區滿了的時候和強制刷新(fflush(3))時纔會刷新,這是 Linux 默認的緩衝模式,但終端設備除外,終端設備使用行緩衝模式;
當數據被放入緩衝區的時候是不會經過系統調用(read(3)、write(3))送到內核中的,只有緩衝區被刷新的時候數據纔會經過系統調用進入內核。而刷新緩衝區就是 fflush(3) 函數的做用。
fflush(3) 的參數是具體要刷新的流,當參數爲 NULL 時會刷新全部的輸出流。
好了,時間不早了,今天先寫到這裏。
== 補充 == 2016.02.20 ==============================
有園友提問:函數括號裏的數字表示的是什麼意思?
其實這個數字表示的是該函數在 man 手冊的第幾章裏能夠查詢到。
man 手冊一共有 8 章,每一章保存不同的內容,這八章的內容分別是:
第一章:shell 命令。如:ls、vim,查詢方法:>$ man ls
第二章:系統調用。如:open、close,查詢方法:>$ man 2 open 或 >$ man close。由於第一章也有 open,因此 man 的參數中要加章節號;由於第一章中沒有 close,因此查詢 close 不須要加章節號。
第三章:庫函數。如:printf、fopen,查詢方法:>$ man 3 printf 或 >$ man fopen
第四章:/dev 下的文件。如:zero。
第五章:一些配置文件的格式。如:/etc/shadow,查詢方法:>$ man shadow
第六章:預留給遊戲的,由遊戲本身定義。如:sol。
第七章:附件和變量。如 iso-8859-1。
第八章:只能由 root 執行的系統管理命令。如 mkfs。
由於有些函數的名字會在不一樣的章節出現,這種函數在查詢 man 手冊的時候若是不加上章節號默認會查詢低編號的章節。
好比 printf,若是你輸入的查詢命令是 >$ man printf,那麼打開的並非 printf 標準庫函數的手冊,而是 printf shell 命令的手冊,想要查詢 printf 函數的手冊就須要在 man 命令後面加上該函數所在的章節數,正確的命令是 >$ man 3 printf,這樣就會打開 man 手冊第三章的 printf 函數。
另外,若是想查看每一個章節中都有哪些內容,可使用 >$ man -aw 命令獲得 man 手冊的安裝路徑,而後找到對應的 man 章節的目錄,再用 zcat 命令查看那些 gz 文件。
以上補充內容參考《linux man 手冊各個章節的意義和用法》。