首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言判斷檔案/目錄是否存在的三種方法:access、fopen、stat 完整對比

     編輯:關於C語言

核心需求

在 C 語言中,「判斷檔案是否存在」看似簡單,實則有**檔案 vs 目錄**、**權限檢查 vs 純存在性**、**跨平台相容性**等細微差別。本文系統梳理三大主流方法,附帶錯誤碼對照與最佳實踐建議。

方法一:access() —— POSIX 標準,功能最全

#include <unistd.h>  // Linux/macOS
// #include <io.h>     // Windows (MSVC)

int access(const char *pathname, int mode);

mode 參數詳解(可按位或組合)

宏常數數值檢查內容適用場景
F_OK0**純存在性**(檔案或目錄)最常用,不關心權限
R_OK4讀取權限需確認可讀才打開
W_OK2寫入權限需確認可寫才打開
X_OK1執行/搜尋權限檢查可執行檔/目錄可進入

返回值與錯誤碼

  • **成功(存在且滿足 mode)**:返回 0
  • **失敗**:返回 -1,並設定 `errno`

常見 `errno`:

錯誤碼含義典型原因
ENOENT檔案/目錄不存在路徑錯誤、尚未建立
EACCES權限不足無讀取目錄權限、檔案權限不符 mode
ENAMETOOLONG路徑過長超過 `PATH_MAX`
ELOOP符號連結過多遞迴符號連結
EROFS唯讀檔案系統檢查 `W_OK` 時觸發

實戰範例

#include <stdio.h>
#include <unistd.h>  // Windows 用 io.h

int main() {
    const char *path = "/etc/passwd";
    
    // 1. 純存在性檢查
    if (access(path, F_OK) == 0) {
        printf("存在: %s\n", path);
    } else {
        printf("不存在: %s (errno=%d: %s)\n", path, errno, strerror(errno));
    }
    
    // 2. 同時檢查讀寫權限
    if (access(path, R_OK | W_OK) == 0) {
        printf("可讀可寫\n");
    }
    
    return 0;
}

⚠️ 經典陷阱:TOCTOU 競態條件

Time-Of-Check to Time-Of-Use:

if (access("config.ini", W_OK) == 0) {
    // 攻擊者在這裡刪除/替換檔案、修改權限
    FILE *fp = fopen("config.ini", "w");  // 可能失敗或寫入錯誤檔案
}

對策: **不要用 access 預檢權限再 fopen**。直接嘗試 `fopen`,失敗再處理錯誤。`access` 僅適合「純存在性檢查」或「非關鍵路徑的權限預判」。


方法二:fopen() —— 簡單直接,但有副作用

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

適用模式與副作用對比

mode檔案不存在時檔案存在時適合判斷存在性?
"r" / "rb"返回 NULL ✓返回檔案指標 ✓**是**(唯讀,無副作用)
"w" / "wb"**建立新檔** ✗**截斷為 0** ✗**否**(破壞性)
"a" / "ab"**建立新檔** ✗定位到檔尾 ✗**否**(建立副作用)
"r+" / "rb+"返回 NULL ✓可讀寫,不截斷 ✓可用,但需關閉

實戰範例

int file_exists(const char *path) {
    FILE *fp = fopen(path, "rb");  // 唯讀二進制,最安全
    if (fp) {
        fclose(fp);
        return 1;
    }
    return 0;  // 可能是不存在、無權限、是目錄等
}

局限性

  • **無法區分「不存在」vs「無權限」vs「是目錄」** —— 統統返回 NULL。
  • **無法檢查目錄** —— `fopen` 目錄在 Linux 返回 NULL(`errno=EISDIR`),Windows 可能成功返回指標但讀取失敗。
  • **開啟/關閉檔案有系統呼叫開銷**,頻繁呼叫效率較低。

方法三:stat() / _stat() —— 取得完整元資料,功能最強

#include <sys/stat.h>
// Linux/macOS: int stat(const char *path, struct stat *buf);
// Windows:   int _stat(const char *path, struct _stat *buf);

核心優勢

  • **一次呼叫獲取所有資訊**:存在性、類型(檔案/目錄/符號連結/裝置)、權限、大小、時間戳、inode、連結數等。
  • **可區分檔案類型**:`S_ISREG()`、`S_ISDIR()`、`S_ISLNK()` 等巨集。
  • **可穿透/不穿透符號連結**:`stat()` 穿透(取得目標屬性),`lstat()` 不穿透(取得連結本身屬性)。

實戰範例:判斷是否為一般檔案且存在

#include <sys/stat.h>
#include <stdio.h>

int is_regular_file(const char *path) {
    struct stat st;
    if (stat(path, &st) != 0) return 0;  // 不存在或無權限訪問
    return S_ISREG(st.st_mode);          // 是否為一般檔案
}

int main() {
    printf("/etc/passwd: %s\n", is_regular_file("/etc/passwd") ? "一般檔案" : "其他/不存在");
    printf("/etc: %s\n", is_regular_file("/etc") ? "一般檔案" : "目錄/其他");
    printf("/nonexist: %s\n", is_regular_file("/nonexist") ? "一般檔案" : "不存在");
    return 0;
}

三方法綜合對比與選型指南

維度access()fopen("rb")stat()
**標準**POSIX.1C89 (ISO C)POSIX.1
**跨平台**Linux/macOS/Windows(io.h)**全平台**Linux/macOS/Windows(_stat)
**純存在性檢查**✓ 最佳✓ 可用✓ 可用
**權限檢查**✓ 詳細 (R/W/X)✗ 無法區分✓ 透過 st_mode
**檔案類型判斷**✗✗✓ 完整 (S_IS*)
**目錄檢查**✓ (F_OK)✗✓ (S_ISDIR)
**副作用**無無 (用 "rb")無
**TOCTOU 風險**高 (權限檢查時)低 (直接嘗試)中
**效能**較快較慢 (開關檔)中等

最佳實踐建議

  1. 只需判斷「存在不存在」 → **`access(path, F_OK) == 0`** 最語意清晰、效率高。
  2. 即將打開檔案讀寫 → **直接 `fopen`**,失敗檢查 `errno`。避免 `access` + `fopen` 雙重呼叫與競態。
  3. 需要知道「是檔案還是目錄」、「大小」、「修改時間」 → **`stat()`** 一次到位。
  4. 跨平台專案 → 封裝平台差異:
// 跨平台封裝範例
#ifdef _WIN32
#include <io.h>
#define F_OK 0
#define access _access
#define stat _stat
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#else
#include <unistd.h>
#include <sys/stat.h>
#endif

bool file_exists(const char *path) { return access(path, F_OK) == 0; }
bool is_regular_file(const char *path) { struct stat st; return stat(path, &st)==0 && S_ISREG(st.st_mode); }

總結: 「判斷檔案存在」沒有銀彈,關鍵在於**後續意圖**。單純存在性用 `access(F_OK)`;即將操作檔案直接 `fopen` 處理錯誤;需要豐富屬性用 `stat()`。切記:**access 預檢權限再操作是反模式**,除非你在寫「檢查工具」而非「業務邏輯」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved