使用PowerShell以UTF-8编写文件而不使用BOM

时间:2011-04-08 15:02:39

标签: encoding powershell utf-8 byte-order-mark

Out-File似乎在使用UTF-8时强制使用BOM:

$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath

如何使用PowerShell以UTF-8编写没有BOM的文件?

18 个答案:

答案 0 :(得分:193)

使用.NET的UTF8Encoding类并将$False传递给构造函数似乎有效:

$MyFile = Get-Content $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyFile, $Utf8NoBomEncoding)

答案 1 :(得分:67)

目前正确方式是使用@Roman Kuzmin in comments向@M推荐的解决方案。达德利answer

[IO.File]::WriteAllLines($filename, $content)

(我还通过删除不必要的System命名空间澄清来缩短它 - 默认情况下会自动替换它。)

答案 2 :(得分:37)

我认为这不是UTF,但我发现一个非常简单的解决方案似乎有用......

Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext

对我来说,无论源格式如何,都会产生没有bom文件的utf-8。

答案 3 :(得分:25)

注意:此答案适用于 Windows PowerShell ;相比之下,在跨平台的PowerShell Core 版本中,没有BOM 的UTF-8 默认编码

补充M. Dudley's own simple and pragmatic answer(和ForNeVeR's more concise reformulation):

为方便起见,这里有高级功能Out-FileUtf8NoBom模仿Out-File 的基于管道的替代方案,这意味着:

  • 您可以像管道中的Out-File一样使用它。
  • 不是字符串的输入对象的格式与将它们发送到控制台时的格式相同,就像使用Out-File一样。

示例:

(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath

注意(Get-Content $MyPath)中是否包含(...),它确保在通过管道发送结果之前打开,读取并关闭整个文件。这是必要的,以便能够回写相同的文件(更新到位)。
但是,一般情况下,这种技术不建议有两个原因:(a)整个文件必须适合内存;(b)如果命令中断,数据将丢失。

关于内存使用的说明:

  • M. Dudley's own answer要求首先在内存中构建整个文件内容,这对于大文件可能会有问题。
  • 下面的函数仅对此有所改进:所有输入对象仍然首先被缓冲,但是它们的字符串表示然后被生成并逐个写入输出文件。

Out-FileUtf8NoBom 的源代码(也可用as an MIT-licensed Gist):

<#
.SYNOPSIS
  Outputs to a UTF-8-encoded file *without a BOM* (byte-order mark).

.DESCRIPTION
  Mimics the most important aspects of Out-File:
  * Input objects are sent to Out-String first.
  * -Append allows you to append to an existing file, -NoClobber prevents
    overwriting of an existing file.
  * -Width allows you to specify the line width for the text representations
     of input objects that aren't strings.
  However, it is not a complete implementation of all Out-String parameters:
  * Only a literal output path is supported, and only as a parameter.
  * -Force is not supported.

  Caveat: *All* pipeline input is buffered before writing output starts,
          but the string representations are generated and written to the target
          file one by one.

.NOTES
  The raison d'être for this advanced function is that, as of PowerShell v5,
  Out-File still lacks the ability to write UTF-8 files without a BOM:
  using -Encoding UTF8 invariably prepends a BOM.

#>
function Out-FileUtf8NoBom {

  [CmdletBinding()]
  param(
    [Parameter(Mandatory, Position=0)] [string] $LiteralPath,
    [switch] $Append,
    [switch] $NoClobber,
    [AllowNull()] [int] $Width,
    [Parameter(ValueFromPipeline)] $InputObject
  )

  #requires -version 3

  # Make sure that the .NET framework sees the same working dir. as PS
  # and resolve the input path to a full path.
  [System.IO.Directory]::SetCurrentDirectory($PWD) # Caveat: .NET Core doesn't support [Environment]::CurrentDirectory
  $LiteralPath = [IO.Path]::GetFullPath($LiteralPath)

  # If -NoClobber was specified, throw an exception if the target file already
  # exists.
  if ($NoClobber -and (Test-Path $LiteralPath)) {
    Throw [IO.IOException] "The file '$LiteralPath' already exists."
  }

  # Create a StreamWriter object.
  # Note that we take advantage of the fact that the StreamWriter class by default:
  # - uses UTF-8 encoding
  # - without a BOM.
  $sw = New-Object IO.StreamWriter $LiteralPath, $Append

  $htOutStringArgs = @{}
  if ($Width) {
    $htOutStringArgs += @{ Width = $Width }
  }

  # Note: By not using begin / process / end blocks, we're effectively running
  #       in the end block, which means that all pipeline input has already
  #       been collected in automatic variable $Input.
  #       We must use this approach, because using | Out-String individually
  #       in each iteration of a process block would format each input object
  #       with an indvidual header.
  try {
    $Input | Out-String -Stream @htOutStringArgs | % { $sw.WriteLine($_) }
  } finally {
    $sw.Dispose()
  }

}

答案 4 :(得分:7)

使用Set-Content代替Out-File时,您可以指定编码Byte,可用于将字节数组写入文件。这与不发出BOM的自定义UTF8编码相结合,可以得到所需的结果:

# This variable can be reused
$utf8 = New-Object System.Text.UTF8Encoding $false

$MyFile = Get-Content $MyPath -Raw
Set-Content -Value $utf8.GetBytes($MyFile) -Encoding Byte -Path $MyPath

使用[IO.File]::WriteAllLines()或类似的区别在于它应该可以适用于任何类型的项目和路径,而不仅仅是实际的文件路径。

答案 5 :(得分:4)

此脚本会将没有BOM的UTF-8转换为DIRECTORY1中的所有.txt文件,并将它们输出到DIRECTORY2

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}

答案 6 :(得分:4)

从版本6开始,powershell支持set-contentout-fileUTF8NoBOM编码,甚至将其用作默认编码。

因此在上面的示例中,它应该像这样:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

答案 7 :(得分:2)

重要!:这仅在开头的额外空格或换行符对于您的文件用例没有问题时才有效
(例如,如果它是 SQL 文件、Java 文件或人类可读的文本文件)

可以使用创建一个空(非 UTF8 或 ASCII(UTF8 兼容))文件并附加到它的组合(如果源是一个文件,用 $str 替换 gc $src):

" "    |  out-file  -encoding ASCII  -noNewline  $dest
$str  |  out-file  -encoding UTF8   -append     $dest

单线

根据您的用例替换 $dest$str

$_ofdst = $dest ; " " | out-file -encoding ASCII -noNewline $_ofdst ; $src | out-file -encoding UTF8 -append $_ofdst

作为简单的函数

function Out-File-UTF8-noBOM { param( $str, $dest )
  " "    |  out-file  -encoding ASCII  -noNewline  $dest
  $str  |  out-file  -encoding UTF8   -append     $dest
}

将其与源文件一起使用:

Out-File-UTF8-noBOM  (gc $src),  $dest

与字符串一起使用:

Out-File-UTF8-noBOM  $str,  $dest
  • 可选:继续附加 Out-File

    "more foo bar"  |  Out-File -encoding UTF8 -append  $dest
    

答案 8 :(得分:1)

老问题,新答案:

虽然“旧”powershell 写入 BOM,但新的平台无关变体的行为有所不同:默认为“无 BOM”,可以通过开关进行配置:

<块引用>

-编码

指定目标文件的编码类型。 默认值为 utf8NoBOM。

该参数可接受的值如下:

  • ascii:使用 ASCII(7 位)字符集的编码。
  • bigendianunicode:使用 big-endian 字节顺序以 UTF-16 格式编码。
  • oem:使用 MS-DOS 和控制台程序的默认编码。
  • unicode:使用 little-endian 字节顺序以 UTF-16 格式编码。
  • utf7:以 UTF-7 格式编码。
  • utf8:以 UTF-8 格式编码。
  • utf8BOM:使用字节顺序标记 (BOM) 以 UTF-8 格式编码
  • utf8NoBOM:以 UTF-8 格式编码,无字节顺序标记 (BOM)
  • utf32:以 UTF-32 格式编码。

来源:here 强调我的

答案 9 :(得分:0)

无论出于何种原因,WriteAllLines调用仍在为我生成BOM,使用无BOM UTF8Encoding参数而没有它。但以下对我有用:

$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])

我必须让文件路径绝对让它工作。否则它将文件写入我的桌面。此外,我想这只有在您知道BOM为3个字节时才有效。我不知道基于编码期望给定的BOM格式/长度是多么可靠。

另外,正如所写的那样,这可能仅适用于您的文件适合PowerShell数组的情况,该数组似乎在我的计算机上的长度限制值低于[int32]::MaxValue

答案 10 :(得分:0)

    [System.IO.FileInfo] $file = Get-Item -Path $FilePath 
    $sequenceBOM = New-Object System.Byte[] 3 
    $reader = $file.OpenRead() 
    $bytesRead = $reader.Read($sequenceBOM, 0, 3) 
    $reader.Dispose() 
    #A UTF-8+BOM string will start with the three following bytes. Hex: 0xEF0xBB0xBF, Decimal: 239 187 191 
    if ($bytesRead -eq 3 -and $sequenceBOM[0] -eq 239 -and $sequenceBOM[1] -eq 187 -and $sequenceBOM[2] -eq 191) 
    { 
        $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False) 
        [System.IO.File]::WriteAllLines($FilePath, (Get-Content $FilePath), $utf8NoBomEncoding) 
        Write-Host "Remove UTF-8 BOM successfully" 
    } 
    Else 
    { 
        Write-Warning "Not UTF-8 BOM file" 
    }  

来源How to remove UTF8 Byte Order Mark (BOM) from a file using PowerShell

答案 11 :(得分:0)

如果您想使用[System.IO.File]::WriteAllLines(),则应将第二个参数转换为String[](如果$MyFile的类型为Object[]),并指定$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)的绝对路径{1}},如:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Set-Variable MyFile
[System.IO.File]::WriteAllLines($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), [String[]]$MyFile, $Utf8NoBomEncoding)

如果您想使用[System.IO.File]::WriteAllText(),有时您应该将第二个参数传递到| Out-String |,以便明确地将CRLF添加到每一行的末尾(特别是当您将ConvertTo-Csv用于它们时) :

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Out-String | Set-Variable tmp
[System.IO.File]::WriteAllText("/absolute/path/to/foobar.csv", $tmp, $Utf8NoBomEncoding)

或者您可以[Text.Encoding]::UTF8.GetBytes()使用Set-Content -Encoding Byte

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Out-String | % { [Text.Encoding]::UTF8.GetBytes($_) } | Set-Content -Encoding Byte -Path "/absolute/path/to/foobar.csv"

请参阅:How to write result of ConvertTo-Csv to a file in UTF-8 without BOM

答案 12 :(得分:0)

通过扩展名将多个文件更改为UTF-8而不使用BOM:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in ls -recurse -filter "*.java") {
    $MyFile = Get-Content $i.fullname 
    [System.IO.File]::WriteAllLines($i.fullname, $MyFile, $Utf8NoBomEncoding)
}

答案 13 :(得分:0)

我使用的一种技术是使用 Out-File cmdlet将输出重定向到ASCII文件。

例如,我经常运行SQL脚本,创建另一个在Oracle中执行的SQL脚本。使用简单的重定向(“&gt;”),输出将为UTF-16,SQLPlus无法识别。要解决这个问题:

sqlplus -s / as sysdba "@create_sql_script.sql" |
Out-File -FilePath new_script.sql -Encoding ASCII -Force

然后可以通过另一个SQLPlus会话执行生成的脚本,而不用担心任何Unicode:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log

答案 14 :(得分:0)

对于PowerShell 5.1,启用此设置:

控制面板,区域,管理,更改系统区域设置,使用Unicode UTF-8 获得全球语言支持

然后将其输入PowerShell:

$PSDefaultParameterValues['*:Encoding'] = 'Default'

或者,您可以升级到PowerShell 6或更高版本。

https://github.com/PowerShell/PowerShell

答案 15 :(得分:-1)

有同样的问题。那帮了我大忙:

$MyFile | Out-File -Encoding Oem $MyPath

使用Visual Studio Code或Notepad ++打开文件时,其显示为UTF-8

答案 16 :(得分:-3)

可以使用以下内容获取没有BOM的UTF8

private void AutoComplete_TextChanged(object sender, RoutedEventArgs e)
{
     AutoCompleteBox box = sender as AutoCompleteBox;
     if (box == null) return;
     box.Text = box.Text.ToUpper();
}

答案 17 :(得分:-3)

这个适用于我(使用&#34;默认&#34;而不是&#34; UTF8&#34;):

$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath

结果是没有BOM的ASCII。