返回文章列表

UNIX 系統目錄與檔案操作及 I/O 流處理

本文探討 UNIX 系統中目錄與檔案操作的底層機制,包含 mkdir()、rmdir()、chdir() 等系統呼叫的使用方法與注意事項。同時,文章也詳細介紹了 UNIX 標準 I/O 函式庫的應用,涵蓋流、FILE 物件、I/O

作業系統 C 語言

UNIX 系統的核心功能之一就是檔案和目錄管理,其仰賴一系列系統呼叫,例如 mkdir()rmdir()chdir(),分別用於建立目錄、移除目錄和更改目前工作目錄。這些系統呼叫提供低階介面,讓程式可以直接與作業系統核心互動,執行檔案系統操作。然而,直接使用系統呼叫較為繁瑣,因此 UNIX 也提供了標準 I/O 函式庫,簡化檔案和裝置的輸入輸出操作。標準 I/O 函式庫以流的概念為基礎,透過 FILE 結構體表示流,並提供一組函式,例如 fopen()fread()fwrite()fclose(),讓開發者能以更簡潔的方式讀寫資料。標準 I/O 函式庫也包含快取機制,能提升 I/O 效率,並提供不同快取模式,例如完全快取、行快取和非快取,以適應不同應用場景的需求。除了檔案操作,UNIX 系統也提供使用者和群組管理功能,相關資訊儲存在 /etc/passwd/etc/group 等系統檔案中,這些檔案記錄了使用者名稱、ID、群組、主目錄和 shell 等重要資訊,是系統安全和使用者管理的基礎。瞭解這些系統呼叫、標準 I/O 函式庫和系統檔案的運作方式,對於開發 UNIX 應用程式至關重要。

UNIX系統的目錄與檔案操作與I/O流處理

段落標題

UNIX系統中,目錄與檔案操作是基本且重要的功能,透過一系列系統呼叫(system calls)來實作。同時,UNIX的標準I/O函式庫(Standard I/O library)提供了便利的流(stream)介面,使得輸入輸出操作更加簡單高效。

mkdir() 系統呼叫

mkdir() 系統呼叫用於建立一個新的目錄。其原型為:

int mkdir(const char *path, mode_t mode);

成功完成時,mkdir() 會傳回 0;否則,它會傳回 -1,並設定全域變數 errno 來表示錯誤。這個系統呼叫會根據 path 指定的路徑來建立新目錄,並根據 mode 引數來設定目錄的許可權。

if (mkdir("/path/to/new/directory", 0755) == -1) {
    perror("Failed to create directory");
    return -1;
}

內容解密:

  • mkdir() 用於建立目錄。
  • path 是指定要建立的目錄路徑。
  • mode 是設定目錄的許可權。
  • 若成功建立目錄,mkdir() 傳回 0;否則傳回 -1。
  • 使用 perror() 函式可以印出錯誤訊息。

rmdir() 系統呼叫

rmdir() 系統呼叫用於移除一個空目錄。其原型為:

int rmdir(const char *path);

成功完成時,rmdir() 會傳回 0;否則,它會傳回 -1,並設定全域變數 errno 來表示錯誤。這個系統呼叫僅能移除空目錄(即只包含 ... 的目錄)。

if (rmdir("/path/to/empty/directory") == -1) {
    perror("Failed to remove directory");
    return -1;
}

內容解密:

  • rmdir() 用於移除空目錄。
  • 若成功移除目錄,rmdir() 傳回 0;否則傳回 -1。
  • 若目錄不為空或其他錯誤情況下,會設定 errno 號以表示錯誤。
  • 使用 perror() 函式可以印出錯誤訊息。

chdir() 系統呼叫

chdir() 系統呼叫用於改變當前工作目錄。其原型為:

int chdir(const char *path);

成功完成時,chdir() 會傳回 0;否則,它會傳回 -1,並設定全域變數 errno 來表示錯誤。

if (chdir("/path/to/directory") == -1) {
    perror("Failed to change directory");
    return -1;
}

內容解密:

  • chdir() 用於改變當前工作目錄。
  • 若成功改變目錄,chdir() 傳回 0;否則傳回 -1。
  • 若路徑無效或其他錯誤情況下,會設定 errno 號以表示錯誤。
  • 使用 perror() 函式可以印出錯誤訊息。

UNIX 標準 I/O 函式庫

UNIX 的標準 I/O 函式庫提供了一個流介面,使得程式能夠方便地進行輸入輸出操作。這些流包括標準輸入、標準輸出和標準錯誤輸出。流是透過 C 語言中的 FILE 資料結構來表示的,這個結構在 <stdio.h> 中宣告。

流與 FILE 物件

流是一種可攜式的讀寫資料方式。流可以是檔案或實體裝置(例如印表機或螢幕)。所有流都由內部的 C 資料結構 FILE 來定義。這個資料結構需要透過指向流的指標來操作。

I/O 快取機制

快取機制用於減少讀寫次數。I/O 快取主要有三種型別:

  • 完全快取(Block Buffered):當流是完全快取時,多個字元會被儲存起來並在填滿快取時一次寫入。通常所有檔案都是完全快取的。
  • 行快取(Line Buffered):當流是行快取時,字元會被儲存直到遇到換行符 (\n) 或從終端機讀取輸入時才寫入。這種模式通常用於終端機輸出。
  • 非快取(Unbuffered):當流是非快取時,資料會立即寫入目的檔案或終端機。例如標準錯誤輸出通常是非快取的。

輸入/輸出緩衝操作

UNIX 提供了多種緩衝操作來控制資料的快取行為。

setbuf() 和 setvbuf()

這些函式用於在開啟流後並且在進行任何其他操作前設定快取模式。

void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title UNIX 系統目錄與檔案操作及 I/O 流處理

package "系統架構" {
    package "前端層" {
        component [使用者介面] as ui
        component [API 客戶端] as client
    }

    package "後端層" {
        component [API 服務] as api
        component [業務邏輯] as logic
        component [資料存取] as dao
    }

    package "資料層" {
        database [主資料庫] as db
        database [快取] as cache
    }
}

ui --> client : 使用者操作
client --> api : HTTP 請求
api --> logic : 處理邏輯
logic --> dao : 資料操作
dao --> db : 持久化
dao --> cache : 快取

note right of api
  RESTful API
  或 GraphQL
end note

@enduml

清除緩衝區

當呼叫 fflush() 函式時,所有給定輸出緩衝區中的資料都會強制寫入到對應的檔案中。如果傳遞 NULL 作為引數,則所有開啟的輸出流都會被清除。

int fflush(FILE *stream);
int fpurge(FILE *stream);

段落標題

在 UNICX 中開啟和管理流是非常重要的一部分。有多種函式可供使用者選擇不同的方式來開啟和管理這些流。

fopen() 函式

FILE *fopen(const char *pathname, const char *mode);

fopen() 函式用於開啟指定路徑的檔案並將其與全緩衝流關聯起來。它還清除流的錯誤和檔案結束指示器。

FILE *file = fopen("/path/to/file", "r");
if (file == NULL) {
    perror("Failed to open file");
    return -1;
}

freopen() 函式

FILE *freopen(const char *pathname, const char *mode, FILE *stream);

freopen() 函式嘗試重新整理並關閉與串流關聯的檔案描述符(如果存在),然後清除錯誤和檔案結束指示器。它開啟一個檔案並將其與給定串流關聯起來。

FILE *newFile = freopen("/path/to/new/file", "w", file);
if (newFile == NULL) {
    perror("Failed to reopen file");
    return -1;
}

fdopen() 函式

FILE *fdopen(int fildes, const char *mode);

fdopen() 函式將一個已經存在的檔案描述符與一個串流關聯起來。

int fd = open("/path/to/file", O_RDONLY);
if (fd == -1) {
    perror("Failed to open file descriptor");
    return -1;
}
FILE *file = fdopen(fd, "r");
if (file == NULL) {
    perror("Failed to associate stream with file descriptor");
    return -1;
}

小段落標題:不同模式開啟串流

以下是不同模式開啟串流:

| 模式 | 說明 | |



-|








-| | "r" | 読模式 | | "w" | 寫模式 | | "a" | 擴充套件模式(追加模式) | | "r+" | 読寫模式 | | "w+" | 読寫模式(重新建立檔案) | | "a+" | 擴充套件模式(追加模式及可讀寫) |

每種模式都有其特定的用途和特性。例如,「w」 模式會建立一個新檔案或覆寫已經存在的檔案,「a」 模式則會在已經存在的檔案末尾追加資料。

每次使用這些函式時都應該注意可能發生的錯誤情況並進行相應處理以確保程式正常執行。透過這些函式以及適當組態我們就能夠很好地管理資料之間以及程式之間以及硬碟之間乃至於網路之間資料往返與互動。

C 語言標準輸入輸出流操作及 UNIX 系統使用者檔案管理

基本流操作模式

在 C 語言中,檔案操作的基本單位是「流」,C 標準函式庫提供了多種方式來開啟和操作這些流。以下是一些常見的流開啟模式:

  • R(讀取模式):只能讀取檔案內容,不允許寫入。
  • r+(讀取與寫入模式):可以讀取和寫入檔案內容,檔案必須已存在。
  • W(寫入模式):寫入檔案時會清空原有內容,如果檔案不存在則建立新檔案。
  • w+(讀取與寫入模式):可以讀取和寫入檔案內容,如果檔案不存在則建立新檔案。
  • a(追加模式):只允許寫入,並且會將新內容追加到檔案結尾。
  • a+(讀取與追加模式):可以讀取和寫入,新內容會追加到檔案結尾。

這些模式各有其限制和適用場景,如表 2.1 和表 2.2 所示:

表 2.1:流開啟模式

| 模式 | 說明 | |


|


















-| | R | 只能讀取檔案內容,不允許寫入。 | | r+ | 可以讀取和寫入檔案內容,檔案必須已存在。 | | W | 寫入檔案時會清空原有內容,如果檔案不存在則建立新檔案。 | | w+ | 可以讀取和寫入檔案內容,如果檔案不存在則建立新檔案。 | | a | 只允許寫入,並且會將新內容追加到檔案結尾。 | | a+ | 可以讀取和寫入,新內容會追加到檔案結尾。 |

表 2.2:流開啟限制

| 檢查專案 | R | w | a | r+ | w+ | a+ | |




-|

–|

–|

–|

–|

–|

–| | 檔案必須已存在 | ✓ | | | | | | | 清空原有內容 | | ✓ | | | ✓ | | | 能夠讀取 | ✓ | | | ✓ | ✓ | ✓ | | 能夠寫入 | | ✓ | ✓ | ✓ | ✓ | ✓ | | 僅能在結尾寫入| | | ✓ | | | ✓ |

關閉流

在完成所有對流的操作後,應該使用 fclose 函式來關閉流並釋放相關資源:

int fclose(FILE *stream);

這個函式會先重新整理所有快取中的資料,然後關閉流並釋放快取區。

輸入輸出操作

一旦流被成功開啟,就可以進行多種形式的輸入輸出操作:

一字元一字元操作

一字元一字元的輸入和輸出函式包括 fgetcgetcgetcharfputcputcputchar

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

這些函式分別用於從流中讀取或寫入單個字元。

一行一行操作

一行一行的輸入和輸出函式包括 fgetsgetsfputsputs

char *fgets(char *str, int size, FILE *stream);
char *gets(char *str);
int fputs(const char *str, FILE *stream);
int puts(const char *str);

這些函式用於從流中讀取或寫入整行字串。

直接 I/O 操作

直接 I/O 操作函式包括 freadfwrite

size_t fread(void *ptr, size_t size, size_t objs, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t objs, FILE *stream);

這些函式用於從流中直接讀取或寫入固定大小的資料區塊。

UNIX 系統使用者檔案管理

在 UNIX 應用程式中,使用者資訊通常儲存在 /etc/passwd/etc/group 等系統檔案中。

/etc/passwd 檔案

/etc/passwd 是一個 ASCII 檔案,包含每個使用者的基本屬性資訊。每個條目由冒號分隔成多個部分:

Name:Password:UserID:GroupID:Gecos:HomeDirectory:Shell

其中各部分的含義如下:

  • Name:使用者登入名稱。
  • Password:密碼的加密版本,通常以 x 指示密碼儲存在 /etc/shadow 中。
  • UserID:使用者唯一的數字 ID。
  • GroupID:使用者主要群組的 ID。
  • Gecos:關於使用者的一般資訊。
  • HomeDirectory:使用者主目錄的完整路徑。
  • Shell:使用者登入後執行的初始程式或 shell。

/etc/group 檔案

/etc/group 是一個 ASCII 檔案,包含系統群組的基本屬性資訊。每個條目也由冒號分隔成多個部分,具體格式和功能與 /etc/passwd 檔案類別似。

UNIX 標準資料檔案介紹

UNIX 中還有一些其他的標準資料檔案用於管理使用者許可權、群組等資訊。這些檔案通常僅能由 root 使用者存取並儲存在 /etc 目錄中。