使用PowerShell在没有BOM的情况下以UTF-8格式写入文件


244

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

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

如何使用PowerShell在没有BOM的情况下以UTF-8格式写入文件?


23
BOM =字节顺序标记。在文件(0xEF,0xBB,0xBF)开头放置了三个字符,看起来像“”
Signal15

39
这真令人沮丧。甚至第三方模块也受到污染,例如试图通过SSH上传文件?BOM!“是的,让我们破坏每个文件;这听起来像是个好主意。” -微软。
MichaelGG 2015年

3
默认编码UTF8NoBOM开始使用PowerShell 6.0版docs.microsoft.com/en-us/powershell/module/...
保罗Shiryaev

谈论打破向后兼容性...
Dragas

Answers:


220

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

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

42
gh,我希望这不是唯一的方法。
Scott Muc

114
一行[System.IO.File]::WriteAllLines($MyPath, $MyFile)就足够了。此WriteAllLines重载精确地写入了没有BOM的UTF8。
Roman Kuzmin

6
这里创建了一个MSDN的功能要求:connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/...
Groostav

3
注意,这WriteAllLines似乎$MyPath是绝对的。
sschuberth

9
@xdhmoore从中WriteAllLines获取当前目录[System.Environment]::CurrentDirectory。如果打开PowerShell,然后更改当前目录(使用cdSet-Location),则[System.Environment]::CurrentDirectory不会更改,并且文件最终将位于错误的目录中。您可以通过解决此问题[System.Environment]::CurrentDirectory = (Get-Location).Path
Shayan Toqraee

79

到目前为止,正确的方法是使用@Roman Kuzmin @M 注释中推荐的解决方案。达德利回答

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

(我还通过删除了不必要的System名称空间说明来缩短了它的位置-默认情况下会自动替换它。)


2
这(无论出于何种原因)并没有为我删除BOM,正如公认的答案一样
Liam

@Liam,可能是某些旧版本的PowerShell或.NET?
ForneVeR

1
我相信.NET WriteAllLines函数的较旧版本默认情况下确实写入了BOM。因此,这可能是版本问题。
本德尔(Bender)最伟大的

2
在Powershell 3中使用BOM进行写入确认,而在Powershell 4中没有BOM进行写入。我不得不使用M. Dudley的原始答案。
chazbot17年

2
因此它可以在默认情况下安装的Windows 10上运行。:)另外,建议改进之处:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal

50

我认为这不是UTF,但我只是找到了一个似乎很有效的简单解决方案...

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

对我而言,这将导致没有bom文件的utf-8(无论源格式如何)。


8
这对我有用,除了我用来-encoding utf8满足我的要求。
Chim Chimz

1
非常感谢你。我正在使用工具的转储日志-里面有选项卡。UTF-8无法正常工作。ASCII解决了这个问题。谢谢。
user1529294 '17

44
是的,-Encoding ASCII可以避免BOM表问题,但是您显然只能获得7位ASCII字符。鉴于ASCII是UTF-8的子集,从技术上讲,生成的文件也是有效的UTF-8文件,但是输入中的所有非ASCII字符都将转换为原义?字符
mklement0

4
@ChimChimz我不小心投票赞成您的评论,但-encoding utf8仍输出带有BOM的UTF-8。:(
TheDudeAbides

33

注意:此答案适用于Windows PowerShell;相比之下,在跨平台的PowerShell Core版本(v6 +)中,所有cmdlet 的默认编码不带BOM的 UTF-8 。 换句话说:如果您使用的是PowerShell [Core]版本6或更高版本
,则默认情况下会获得无BOM的UTF-8文件(也可以使用-Encoding utf8/ 显式请求-Encoding utf8NoBOM,而使用 -BOM编码获得-utf8BOM)。


为了补充达德利(M. Dudley)自己简单而务实的答案(以及ForNeVeR更为简洁的表述)):

为了方便起见,这里的先进功能Out-FileUtf8NoBom基于管道的替代,模仿Out-File,这意味着:

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

例:

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

请注意如何使用(Get-Content $MyPath)括起来(...),以确保在通过管道发送结果之前,打开,完整读取和关闭整个文件。为了能够写回相同的内容,这是必需的文件(在适当位置更新它),。
但是,通常不建议使用此技术,原因有两个:(a)整个文件必须适合内存;(b)如果命令中断,数据将丢失。

关于的注释 内存使用的说明

  • M. Dudley自己的答案要求首先在内存中建立整个文件的内容,这对于大文件可能是有问题的。
  • 下面的功能仅对此稍有改进:仍然首先缓冲所有输入对象,但随后生成它们的字符串表示形式并将其逐个写入输出文件。

的源代码Out-FileUtf8NoBom(也可作为MIT许可的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.ProviderPath) # Caveat: Older .NET Core versions don'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()
  }

}

16

从开始第6版 PowerShell支持UTF8NoBOM的编码都设置内容出文件,甚至以此为默认的编码。

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

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

@RaúlSalinas-Monteagudo您使用的是什么版本?
John Bentley

真好 仅供参考,以$PSVersionTable.PSVersion
KCD,

14

使用Set-Content代替时Out-File,您可以指定encoding 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

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

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

这一项失败,没有任何警告。我应该使用哪个版本的Powershell来运行它?
darksoulsong 2013年

3
WriteAllLines解决方案非常适合小文件。但是,我需要更大文件的解决方案。每当我尝试将其与较大的文件一起使用时,都会收到OutOfMemory错误。
百慕大羔羊

2
    [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" 
    }  

来源如何使用PowerShell从文件中删除UTF8字节顺序标记(BOM)


2

如果要使用[System.IO.File]::WriteAllLines(),则应将第二个参数强制转换为String[](如果类型$MyFileObject[]),并使用来指定绝对路径$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath),例如:

$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"

请参阅:如何在没有BOM的情况下将ConvertTo-Csv的结果写入UTF-8中的文件


好的指针;建议/:更简单的替代方法$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)Convert-Path $MyPath; 如果您想确保尾随CRLF,[System.IO.File]::WriteAllLines()甚至只需使用单个输入字符串即可(无需使用Out-String)。
mklement0 '18

0

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

例如,我经常运行创建另一个SQL脚本以在Oracle中执行的SQL脚本。使用简单重定向(“>”),输出将采用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

4
是的,-Encoding ASCII可以避免BOM表问题,但是显然您仅支持7位ASCII字符。鉴于ASCII是UTF-8的子集,从技术上讲,生成的文件也是有效的UTF-8文件,但是输入中的所有非ASCII字符都将转换为原义?字符
mklement0

该答案需要更多票。sqlplus与BOM的不兼容性是造成许多麻烦的原因。
阿米特·奈杜

0

通过扩展名将多个文件更改为不带BOM的UTF-8:

$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)
}

0

无论出于何种原因,WriteAllLines调用仍然为我生成BOM,带有BOMless UTF8Encoding参数,并且没有它。但是以下对我有用:

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

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

同样,按照书面规定,这可能仅在文件适合Powershell数组的情况下才有效,而该数组的长度限制似乎比[int32]::MaxValue我的计算机低。


1
WriteAllLines没有编码参数的话,BOM 本身不会写,但是可以想到的是,您的字符串恰巧以BOM 字符U+FEFF)开头,在编写时有效地创建了UTF-8 BOM;例如:($s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)忽略则[char] 0xfeff + 看不到任何 BOM)。
mklement0 '18

1
至于意外地写入其他位置:问题是.NET框架通常具有与PowerShell不同的当前目录;您可以先将它们与同步[Environment]::CurrentDirectory = $PWD.ProviderPath,或者作为一种更通用的替代"$(pwd)\..."方法(更好:"$pwd\...",甚至更好:"$($pwd.ProviderPath)\..."(Join-Path $pwd.ProviderPath ...)),使用(Convert-Path BOMthetorpedoes.txt)
mklement0

谢谢,我没有意识到像这样将单个BOM表字符转换为UTF-8 BOM表。
xdhmoore

1
实际上,所有 BOM 字节序列(Unicode签名)都是抽象的单个Unicode字符U+FEFF的相应编码的字节表示形式。
mklement0 '18

喔好吧。这似乎确实使事情变得简单。
xdhmoore

-2

可以在下面使用以获得没有BOM的UTF8

$MyFile | Out-File -Encoding ASCII

4
不会,它将输出转换为当前的ANSI代码页(例如cp1251或cp1252)。根本不是UTF-8!
预告2015年

1
谢谢罗宾。这可能无法在没有BOM的情况下写入UTF-8文件,但是-Encoding ASCII选项删除了BOM。这样,我可以为gvim生成一个bat文件。.bat文件在BOM表上跳闸了。
格雷格

3
@ForNeVeR:您是正确的,编码ASCII不是UTF-8,但不是当前的ANSI代码页-您正在考虑DefaultASCII真正的是7位ASCII编码,其代码点> = 128转换为文字?实例。
mklement0

1
@ForNeVeR:您可能正在考虑“ ANSI”或“ 扩展 ASCII”。尝试此操作以验证-Encoding ASCII确实仅是7位ASCII:'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- ä已被音译为?。相比之下,-Encoding Default(“ ANSI”)将正确保留它。
mklement0

3
@rob对于每个不需要utf-8或其他与ASCII不同并且对理解编码和unicode的用途不感兴趣的人来说,这都是一个完美的答案。您可以其用作utf-8,因为等效于所有ASCII字符的utf-8字符是相同的(意味着将ASCII文件转换为utf-8文件的结果是相同的文件(如果没有BOM))。对于所有在文本中使用非ASCII字符的人来说,这个答案都是错误的和误导性的。
TNT

-3

这对我有用(使用“默认”而不是“ UTF8”):

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

结果是不带BOM的ASCII。


1
根据Out-File文档,指定Default编码将使用系统当前的ANSI代码页,而不是我要求的UTF-8。
M. Dudley

这似乎确实对我有用,至少对于Export-CSV。如果在适当的编辑器中打开结果文件,则文件编码为UTF-8(不带BOM),而不是西方拉丁ISO 9(如我对ASCII所期望的那样)
eythort

如果许多编辑器无法检测到编码,则它们以UTF-8格式打开文件。
emptyother
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.