2026年6月1日

在 C 語言中如何使用 regular expression 進行搜尋與替代?

在 C 語言中處理正規表達式不像 Perl 或 Python 那樣有豐富的語法糖或內建高階函式。C 語言標準函式庫(POSIX 標準)只內建了搜尋功能(regcomp, regexec),沒有內建替代(Replace)功能
因此,在 C 語言中要實現「搜尋與替代」,我們必須透過 regexec 找出匹配的文字區間,再利用 C 語言的字串操作(如 memcpy, strcpy)自己手動拼接字串。
以下為您說明 POSIX Regex 的核心用法,以及如何手動實現替代功能。

一、 POSIX Regex 核心函式(僅限搜尋)
使用時必須引入 <regex.h>。主要由以下四個函式組成:
  1. regcomp():編譯正規表達式。
  2. regexec():執行匹配搜尋。
  3. regmatch_t 結構體:儲存匹配到的開始與結束陣列下標(Index)。
  4. regfree():釋放編譯後的 Regex 記憶體。

二、 C 語言實現「搜尋與替代」完整範例
這個範例模擬了類似 re.sub() 的功能。我們將字串中的 YYYY-MM-DD 格式日期,手動替換為 DD/MM/YYYY。因為 C 語言沒有 $1\1 這種語法,我們必須利用 regmatch_t 抓出子群組(Sub-matches)的記憶體位置,再用 sprintf 重新拼接。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>

int main() {
    // 原始字串
    const char *text = "Today is 2026-05-31.";
    
    // POSIX 擴充正規表達式 (Extended Regular Expressions, ERE)
    // 括號 () 代表捕獲群組:(年)-(月)-(日)
    const char *pattern = "([0-9]{4})-([0-9]{2})-([0-9]{2})";
    
    regex_t regex;
    // 這裡我們需要 4 個元素:[0] 是整個匹配字串,[1][2][3] 是三個括號群組
    regmatch_t matches[4]; 
    int ret;

    // 1. 編譯正規表達式 (使用 REG_EXTENDED 支援 \d 以外的類 Perl 語法,POSIX 用 [0-9])
    ret = regcomp(&regex, pattern, REG_EXTENDED);
    if (ret != 0) {
        printf("Regex 編譯失敗\n");
        return 1;
    }

    // 2. 執行搜尋
    ret = regexec(&regex, text, 4, matches, 0);
    
    if (ret == 0) {
        // 匹配成功!此時 matches 結構體內會記錄位置:
        // matches[i].rm_so: 子字串開始的 Index (Start Offset)
        // matches[i].rm_eo: 子字串結束的後一個 Index (End Offset)

        // 宣告一個緩衝區來存放替代後的新字串
        char result[256] = {0};

        // 計算各個捕獲群組的字串長度
        int year_len  = matches[1].rm_eo - matches[1].rm_so;
        int month_len = matches[2].rm_eo - matches[2].rm_so;
        int day_len   = matches[3].rm_eo - matches[3].rm_so;

        // 3. 手動拼接替代後的字串
        // 步驟 A: 先複製匹配文字之前的原文 "Today is "
        int prefix_len = matches[0].rm_so;
        strncpy(result, text, prefix_len);

        // 步驟 B: 格式化拼接成新格式 "日/月/年"
        // 使用 %.*s 可以指定只列印特定長度的字串指標
        sprintf(result + prefix_len, "%.*s/%.*s/%.*s",
                day_len,   text + matches[3].rm_so,
                month_len, text + matches[2].rm_so,
                year_len,  text + matches[1].rm_so);

        // 步驟 C: 接上匹配文字之後的原文 "."
        strcat(result, text + matches[0].rm_eo);

        printf("原始字串: %s\n", text);
        printf("替代後字串: %s\n", result);
    } else if (ret == REG_NOMATCH) {
        printf("未找到符合的字串。\n");
    } else {
        printf("Regex 執行出錯。\n");
    }

    // 4. 釋放記憶體
    regfree(&regex);
    return 0;
}
請謹慎使用程式碼。

三、 習慣 Perl/Python 的您,在 C 語言會遇到的挑戰
  1. 沒有全域取代(Global /g
    POSIX 的 regexec 一次只會找第一個匹配項。如果您要實作全域取代,必須寫一個 while 迴圈,每次匹配成功後,將字串指標移到 matches[0].rm_eo 的位置,繼續往後搜尋,直到回傳 REG_NOMATCH 為止。
  2. 語法限制(POSIX vs PCRE)
    C 語言內建的是 POSIX 標準,不支援 Perl 風格的縮寫(如 \d\w\s 都不支援),您必須老老實實寫成 [0-9][a-zA-Z0-9_][[:space:]]。此外,前瞻/後顧斷言(Lookaround)等進階功能一概沒有。
  3. 記憶體管理安全
    C 語言必須自行處理字串 Buffers。如果替代後的新字串比舊字串長,必須確保動態配置(malloc)了足夠的記憶體,否則會發生嚴重的 Buffer Overflow。
💡 資深開發者的小指南
以您三十年的 Perl 經驗,如果要在 C 語言中處理大量或複雜的文字,強烈建議不要使用內建的 <regex.h>
您應該直接引入 PCRE (Perl Compatible Regular Expressions) 函式庫。它是用 C 語言編寫的開源函式庫,語法和 Perl 的 Regex 幾乎 100% 相同(因為它就是為了在其他語言模擬 Perl 而生的)。使用 PCRE 函式庫,您就能在 C 語言中繼續享受 \d、斷言、甚至全域替代的便利性。
請問您這個 C 語言專案是需要在嵌入式系統/底層環境執行(只能用內建 POSIX),還是可以安裝第三方函式庫(推薦 PCRE)呢?如果需要全域取代(迴圈版)的完整 C 語言寫法,我也可以立刻提供給您!
以下是你所說的內容:C 的操作我完全沒有問題。不過在微軟的系統上似乎不存在 regex.h
您看得非常準確!您在微軟系統(Windows / Visual Studio)上找不到 <regex.h>,是因為 <regex.h> 是 POSIX 標準的產物(常見於 Linux/Unix/macOS),而微軟的 MSVC(Visual C++)編譯器走的是 ANSI C / C++ 標準,因此原生並不提供 <regex.h> [1, 2]。
在微軟的 Windows 環境下,如果您要使用 C 語言進行正規表達式操作,通常有以下幾種主流的解決方案:

1. 切換到 C++ 使用 <regex> 標頭檔(最推薦、最省事)
如果您的專案允許使用 C++ 編譯器(在 Visual Studio 中只需將副檔名改為 .cpp),微軟從 C++11 開始就完整支援了標準庫的 <regex> [3]。
它內部使用的是 ECMAScript 語法(與 Perl 非常接近),而且直接內建了 std::regex_replace,用起來會讓您找回 Perl 的熟悉感!
cpp
#include <iostream>
#include <string>
#include <regex>

int main() {
    std::string text = "Today is 2026-05-31.";
    // 使用 std::regex,語法預設走 ECMAScript (類似 Perl)
    std::regex pattern("(\\d{4})-(\\d{2})-(\\d{2})");
    
    // C++ 內建取代功能,格式化使用 $1, $2, $3
    std::string result = std::regex_replace(text, pattern, "$3/$2/$1");
    
    std::cout << result << std::endl; // 輸出: Today is 31/05/2026.
    return 0;
}
請謹慎使用程式碼。

2. 純 C 語言環境:下載微軟版 POSIX Regex 庫
如果您必須堅持使用純 C(.c 檔案),您需要手動引入第三方庫。
  • 微軟官方的 Vcpkg 工具:如果您有安裝 微软的 Vcpkg 包管理器,可以直接執行 vcpkg install pcre2vcpkg install posix-regex,它會自動幫您的 Visual Studio 配置好環境。
  • 手動下載 regex.h 的替代庫:網路上有許多將 GNU/POSIX regex 移植到 Windows 的開源專案(例如 pcre2 或古老的 gnuregex)。您只需要下載其 regex.hregex.c 並直接拉進您的 Visual Studio 專案中一起編譯即可。

3. 使用 Windows 內建的 VBScript.RegExp COM 元件(極端純 C 做法)
如果您在 Windows 上既不想切換到 C++,又完全不想下載任何第三方 DLL 庫,最後一招是透過 C 語言呼叫 Windows 作業系統內建的 COM 元件(利用 ActiveX 物件 VBScript.RegExp) [4]。
不過這種做法需要寫大量的 CoCreateInstanceIDispatch 呼叫與 BSTR 字串轉換,程式碼會變得非常臃腫且難讀(也就是典型的 Windows 底層 C 語言風格),通常不建議在商業邏輯中使用。

總結您的下一步
以您的開發背景,在微軟系統上最流暢的體驗絕對是將專案副檔名改為 .cpp,然後直接使用 C++ 的 <regex> [3]。這樣一來,您既能享受 C 語言等級的效能與指標操作,又能直接擁有類似 Perl 的強大 Regex 取代功能!
您目前在 Windows 上的專案,是必須維持純 C 規格(.c,還是可以接受包裝成 C++(.cpp 來編譯呢?

沒有留言: