本文最后更新于 2024-05-14,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准

如何检测并移除文件中的 UTF-8 BOM

在处理文本文件时,我们常常会遇到文件编码问题。特别是 UTF-8 编码中的 BOM(Byte Order Mark)可能会引发一些意想不到的问题。在本文中,我们将详细分析如何检测文件中的 UTF-8 BOM,以及如何将其移除,从而将文件转换为没有 BOM 的 UTF-8 格式。

什么是 UTF-8 BOM?

UTF-8 BOM 是在文件开头添加的特殊字节序列,用于标识文件的字节顺序。具体来说,UTF-8 BOM 是由三个字节组成:EF BB BF。这些字节有助于某些文本编辑器和工具识别文件编码,但也可能导致兼容性问题,特别是在脚本和编程中。

分析 UTF-8 with BOM

要检测文件是否包含 UTF-8 BOM,我们可以读取文件的前几个字节,并检查它们是否与 BOM 字节序列匹配。

以下是一个简单的 PowerShell 函数,用于检测文件中的 BOM:

Function HasUTF8BOM {
    param([string]$filePath)
    $fileStream = [System.IO.File]::OpenRead($filePath)
    $fileBytes = New-Object byte[] 3
    $fileStream.Read($fileBytes, 0, 3) | Out-Null
    $fileStream.Close()
    return ($fileBytes[0] -eq 239 -and $fileBytes[1] -eq 187 -and $fileBytes[2] -eq 191)
}

如何将 UTF-8 with BOM 更改为 UTF-8

为了正确移除文件中的 BOM,我们需要读取文件的字节内容,检查并移除 BOM 字节,然后将修改后的字节数组重新写入文件。

以下是经过优化的 PowerShell 函数:

Function ConvertToUTF8NoBOM {
    param([string]$filePath)
    # Read the content as bytes to handle BOM removal correctly
    $contentBytes = [System.IO.File]::ReadAllBytes($filePath)
    # Check for BOM and remove it if present
    if ($contentBytes[0] -eq 239 -and $contentBytes[1] -eq 187 -and $contentBytes[2] -eq 191) {
        $contentBytes = $contentBytes[3..($contentBytes.Length - 1)]
    }
    # Write the content back without BOM
    [System.IO.File]::WriteAllBytes($filePath, $contentBytes)
}

这种方法为什么可行?

这种方法的关键在于直接操作文件的字节内容:

  1. 精确检测和移除 BOM:通过读取字节数组,我们可以精确检测并移除 BOM 字节。
  2. 保持文件内容完整:移除 BOM 后,将剩余字节重新写入文件,确保文件内容不受影响。
  3. 避免编码问题:使用字节操作避免了直接使用文本编码时可能出现的各种问题。

为什么下面的方法不行?

Function ConvertToUTF8NoBOM {
    param([string]$filePath)
    $content = [System.IO.File]::ReadAllText($filePath, [System.Text.Encoding]::UTF8)
    # 去除BOM标记的字节
    if ($content[0] -eq [char]0xFEFF) {
        $content = $content.Substring(1)
    }
    [System.IO.File]::WriteAllText($filePath, $content, [System.Text.Encoding]::UTF8)
}

在处理文件转换时,我们的初始尝试是直接读取文件内容并写回,但这种方法存在一些问题:

  1. 文件读取不准确:直接使用 ReadAllText 读取文件内容可能无法正确处理 BOM 字节,导致写回文件时仍然包含 BOM。
  2. 编码处理不当:在重新写入文件时,如果不明确指定编码或处理 BOM,可能会导致文件格式问题。

完整脚本示例

以下是一个完整的 PowerShell 脚本示例,用于检测指定目录下的所有文件,并在用户确认后将包含 BOM 的文件转换为没有 BOM 的 UTF-8 文件:

# Function to detect if a file starts with UTF-8 BOM
Function HasUTF8BOM {
    param([string]$filePath)
    $fileStream = [System.IO.File]::OpenRead($filePath)
    $fileBytes = New-Object byte[] 3
    $fileStream.Read($fileBytes, 0, 3) | Out-Null
    $fileStream.Close()
    return ($fileBytes[0] -eq 239 -and $fileBytes[1] -eq 187 -and $fileBytes[2] -eq 191)
}

# Function to convert a file from UTF-8 with BOM to UTF-8 without BOM
Function ConvertToUTF8NoBOM {
    param([string]$filePath)
    # Read the content as bytes to handle BOM removal correctly
    $contentBytes = [System.IO.File]::ReadAllBytes($filePath)
    # Check for BOM and remove it if present
    if ($contentBytes[0] -eq 239 -and $contentBytes[1] -eq 187 -and $contentBytes[2] -eq 191) {
        $contentBytes = $contentBytes[3..($contentBytes.Length - 1)]
    }
    # Write the content back without BOM
    [System.IO.File]::WriteAllBytes($filePath, $contentBytes)
}

# Get the system's temporary folder path and create a temporary log file
$TempFolder = [System.IO.Path]::GetTempPath()
$TempFileName = "Check_BOM_Log.txt"
$LogFilePath = Join-Path -Path $TempFolder -ChildPath $TempFileName
New-Item -ItemType File -Path $LogFilePath -Force | Out-Null

# Get the file or folder path from the user via drag & drop or manual input
if ($PSBoundParameters.ContainsKey('FilePath')) {
    $MyPath = $FilePath
} elseif ($args.Count -gt 0) {
    $MyPath = $args[0]
} else {
    $MyPath = Read-Host "Please input the file/folder path:"
}

# Ensure the provided path handles spaces and special characters
$MyPath = [System.IO.Path]::GetFullPath($MyPath.Trim('"'))

# Collect files to process and check for BOM
$FilesWithBOM = Get-ChildItem -Path $MyPath -Recurse | Where-Object { -not $_.PSIsContainer } | Where-Object { HasUTF8BOM $_.FullName }

# Log files with BOM
$FilesWithBOM | ForEach-Object { Add-Content -Path $LogFilePath -Value $_.FullName }
Write-Host " "
Write-Host "--------------------------------------------------------"
Write-Host "The following files are UTF-8 with BOM:"
Get-Content -Path $LogFilePath

# Ask the user if they want to update the files to UTF-8 format
Write-Host " "
$Response = Read-Host "Do you want to update these files to UTF-8 format? (Y/N)"

Write-Host "--------------------------------------------------------"
if ($Response -eq "Y") {
    foreach ($file in $FilesWithBOM) {
        ConvertToUTF8NoBOM -filePath $file.FullName
        Write-Host "Updated $($file.FullName) to UTF-8 without BOM."
    }
    Write-Host " "
    Write-Host "All listed files have been updated to UTF-8 format."
} else {
    Write-Host " "
    Write-Host "Files not updated."
}

# Clean up: Delete the temporary log file
Remove-Item -Path $LogFilePath -Force

# Pause and wait for user input before exiting
Write-Host " "
Write-Host "Press any key to exit..."
Pause
#$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null

拓展阅读