字串中的換行符 (\n、\r、\r\n) 與跳格符 (\t) 在不同作業系統、編輯器、資料庫、網頁顯示中表現迥異,是資料交換、日誌分析、檔案處理的常見坑點。本文系統梳理其歷史來由、跨平台差異、PHP 中的處理方式與最佳實踐。
| 平台 | 換行符 | 十六進位 | 常見場景 |
|---|---|---|---|
| Linux / Unix / macOS (OS X+) | \n (LF) | 0x0A | 伺服器、容器、Git 內部儲存 |
| Windows (DOS 繼承) | \r\n (CRLF) | 0x0D 0x0A | 記事本、Visual Studio、HTTP 標頭 |
| Classic Mac (OS 9-) | \r (CR) | 0x0D | 舊版 Mac 文字檔 |
| HTTP 協定 | \r\n | 0x0D 0x0A | 標頭結束、請求/回應分隔 |
| CSV 標準 (RFC 4180) | \r\n | 0x0D 0x0A | 跨平台資料交換 |
<?php echo '換行: \n 輸出字面 \n'; // 單引號:不解析轉義序列 echo "換行: \n 真正換行"; // 雙引號:解析 \n \r \t 等 echo <<2. 原始碼檔案本身的換行符
PHP 解析器接受任意換行符作為語句分隔。但版本控制 (Git) 建議統一為 LF (
core.autocrlf=input),避免跨平台協作產生雜訊變更。3. 檔案讀寫時的換行符轉換
<?php // 讀取:fgets/fgetcsv 自動處理 \n \r\n \r (PHP 4.3+) // 寫入:建議統一輸出 \n (Unix 風格),由儲存層/傳輸層處理轉換 // 強制轉換為 LF $content = str_replace(["\r\n", "\r"], "\n", $content); // 輸出 HTML 顯示:nl2br() 將 \n \r\n \r 轉
echo nl2br($content); // 輸出 CSV/HTTP:強制 \r\n $csv_line = implode(',', $fields) . "\r\n"; header("Content-Type: text/csv; charset=utf-8"); echo $csv_line; ?>4. 正則表達式匹配換行
<?php // s 修飾符 (PCRE_DOTALL) 讓 . 匹配換行 preg_match('/start.*end/s', $str); // 匹配跨行 preg_match('/^line$/m', $str); // m 修飾符:^$ 匹配行首尾 preg_split('/\r\n|\r|\n/', $str); // 分割所有換行類型 ?>跳格符 \t (Tab, 0x09)
- 水平對齊、欄位分隔 (TSV)、程式碼縮排。
- 顯示寬度取決於編輯器/終端機設定(通常 4 或 8 字元)。
- HTML 中需轉為
或 CSSwhite-space: pre保留視覺寬度。常見互轉場景與代碼
Windows (CRLF) → Unix (LF)
<?php // 批次轉換目錄下所有 .php .txt .css .js $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__)); foreach ($files as $file) { if ($file->isFile() && in_array($file->getExtension(), ['php','txt','css','js','html'])) { $content = file_get_contents($file->getPathname()); if (strpos($content, "\r\n") !== false) { $content = str_replace("\r\n", "\n", $content); file_put_contents($file->getPathname(), $content); echo "Converted: {$file->getPathname()}\n"; } } } ?>資料庫匯出 CSV 強制 CRLF
<?php $fp = fopen('php://output', 'w'); fputcsv($fp, ['ID', 'Name', 'Email'], ',', '"', "\r\n"); // 第 5 參數指定行尾 (PHP 8.1+) // PHP 8.1 前需手動寫入 fwrite($fp, implode(',', ['ID','Name','Email']) . "\r\n"); while ($row = $stmt->fetch(PDO::FETCH_NUM)) { fputcsv($fp, $row, ',', '"', "\r\n"); } fclose($fp); ?>清理雜亂換行符(混雜 \r\n \r \n)
<?php function normalize_line_endings(string $str, string $target = "\n"): string { // 先統一 \r\n → \n,再將孤立 \r → \n $str = str_replace("\r\n", "\n", $str); $str = str_replace("\r", "\n", $str); if ($target !== "\n") { $str = str_replace("\n", $target, $str); } return $str; } // 使用 $clean = normalize_line_endings($raw_input, "\n"); // 統一為 LF $csv = normalize_line_endings($raw_input, "\r\n"); // 統一為 CRLF ?>最佳實踐總結
- 內部處理統一用 LF (\n):PHP 邏輯、資料庫儲存、快取、Session 皆用 LF。
- 輸出層按協定轉換:HTTP 標頭/CSV → CRLF;HTML →
nl2br();JSON/CSV API → LF (現代標準)。- Git 設定
core.autocrlf=input:提交時轉 LF,簽出時不轉,避免 Windows 開發者產生 CRLF 雜訊。- 編輯器設定「換行符 = LF」:VS Code、PhpStorm、Sublime 皆可在狀態列切換。
- 跨平台檔案交換:提供
normalize_line_endings()工具函數,統一轉換後再處理。總結
理解
\n、\r、\r\n、\t的歷史淵源與跨平台差異,並在「內部統一、邊界轉換」原則下處理,即可消除 90% 以上的換行符相關 Bug。PHP 提供nl2br()、fgetcsv()、preg_split()等內建工具,配合自訂normalize_line_endings(),輕鬆應對各種檔案格式與協定需求。