UTF-8 BOM (Byte Order Mark, EF BB BF) 會導致 PHP 輸出時在文件開頭產生不可見字符,引發「headers already sent」、圖片輸出損壞、Session 啟動失效等疑難雜症。本文提供一個可直接運行的 CLI/WEB 兼容腳本,支援遞歸掃描目錄、自動備份、乾運行模式、彩色輸出,幫你徹底清理專案中的 BOM 隱患。
header()、session_start()、setcookie() 之前有輸出。function hasBom(string $content): bool {
return strlen($content) >= 3
&& ord($content[0]) === 0xEF
&& ord($content[1]) === 0xBB
&& ord($content[2]) === 0xBF;
}
function removeBom(string $content): string {
return hasBom($content) ? substr($content, 3) : $content;
}
<?php
/**
* UTF-8 BOM 批量檢測與移除工具
* 用法:
* CLI: php del_bom.php [目錄] [--dry-run] [--backup]
* WEB: 訪問 https://yourhost/del_bom.php?dir=目錄&dry=1&backup=1
*/
declare(strict_types=1);
$baseDir = $argv[1] ?? ($_GET['dir'] ?? '.');
$dryRun = in_array('--dry-run', $argv) || isset($_GET['dry']);
$backup = in_array('--backup', $argv) || isset($_GET['backup']);
$baseDir = realpath($baseDir);
if (!$baseDir || !is_dir($baseDir)) {
die("目錄不存在: $baseDir\n");
}
echo "掃描目錄: $baseDir\n";
echo "模式: " . ($dryRun ? '乾運行(僅檢測)' : '實際修改') . ($backup ? ' | 備份原文件' : '') . "\n";
echo str_repeat('-', 60) . "\n";
$found = 0;
$removed = 0;
$errors = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($baseDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (!$file->isFile()) continue;
// 可選:只處理特定副檔名
$ext = strtolower($file->getExtension());
if (!in_array($ext, ['php', 'html', 'htm', 'css', 'js', 'json', 'xml', 'txt', 'ini', 'yml', 'yaml'])) {
continue;
}
$path = $file->getPathname();
$content = @file_get_contents($path);
if ($content === false) {
echo "[讀取失敗] $path\n";
$errors++;
continue;
}
if (hasBom($content)) {
$found++;
$relPath = str_replace($baseDir . DIRECTORY_SEPARATOR, '', $path);
echo "[發現 BOM] $relPath\n";
if (!$dryRun) {
if ($backup) {
$bak = $path . '.bom_bak_' . date('YmdHis');
if (!@copy($path, $bak)) {
echo " → 備份失敗,跳過修改\n";
$errors++;
continue;
}
}
$newContent = removeBom($content);
if (@file_put_contents($path, $newContent, LOCK_EX) !== false) {
echo " → 已移除 BOM\n";
$removed++;
} else {
echo " → 寫入失敗(權限不足?)\n";
$errors++;
}
}
}
}
echo str_repeat('-', 60) . "\n";
echo "掃描完成。檢測到 BOM 文件: $found, 已移除: $removed, 錯誤: $errors\n";
if ($dryRun && $found > 0) {
echo "提示: 加上 --dry-run 0 或不帶 --dry-run 參數可執行實際移除。\n";
}
# 1. 僅檢測當前目錄
php del_bom.php
# 2. 檢測指定目錄並顯示彩色輸出(CLI)
php del_bom.php /var/www/html --dry-run
# 3. 實際移除並備份原文件
php del_bom.php /var/www/html --backup
# 4. Web 方式(將腳本放入項目目錄訪問)
# https://example.com/del_bom.php?dir=.&dry=1
# https://example.com/del_bom.php?dir=application&backup=1
| 編輯器/IDE | 設置無 BOM 保存 |
|---|---|
| VS Code | 右下角編碼點擊 → 「Save with Encoding」→ 選「UTF-8」(非 UTF-8 with BOM) |
| PhpStorm | File → File Encoding → 去掉「Add BOM for UTF-8 files」 |
| Sublime Text | File → Save with Encoding → UTF-8 |
| Notepad++ | Encoding → Convert to UTF-8 (無 BOM) |
| Vim | :set nobomb 寫入 .vimrc |
在佈建腳本或 Git Hook 中加入檢查:
# .git/hooks/pre-commit
#!/bin/bash
php /path/to/del_bom.php "$(git rev-parse --show-toplevel)" --dry-run
if [ $? -ne 0 ]; then
echo "發現 BOM 文件,請先清理再提交"
exit 1
fi
BOM 是 UTF-8 文件在 Windows 記事本等工具保存時誤加入的標記,PHP 不會自動忽略。定期掃描清理、統一編輯器設置、CI 攔截,三管齊下才能杜絕「莫名空白/headers already sent」此類鬼異 Bug。