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

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


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

评论

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

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

从Powershell 6.0版开始,默认编码为UTF8NoBOM。docs.microsoft.com/en-us/powershell/module/ ...

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

#1 楼

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

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


评论


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

–斯科特·穆克(Scott Muc)
2011年5月24日下午6:16

一行[System.IO.File] :: WriteAllLines($ MyPath,$ MyFile)就足够了。此WriteAllLines重载可精确写入UTF8,而无需BOM。

–罗马库兹明
2011-11-8 19:42



在此处创建了MSDN功能请求:connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…

–Groostav
15年2月18日在20:08

请注意,WriteAllLines似乎要求$ MyPath是绝对的。

–sschuberth
17年1月4日在15:38

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

– Shayan Toqraee
17年9月30日19:00

#2 楼

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

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


(我也通过删除不必要的System命名空间说明将其缩短了一点-默认情况下它将自动替换。)

评论


这(出于任何原因)并没有为我删除BOM,因为接受的答案确实如此

–利亚姆
16年6月17日在10:31

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

– ForneVeR
16年6月17日在14:58

我相信.NET WriteAllLines函数的较旧版本默认情况下确实写入了BOM。因此,这可能是版本问题。

–本德最大
17年1月23日在16:38

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

–chazbot7
17-10-30在22:31

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

–Johny Skovdal
18年1月12日在7:05

#3 楼

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

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


对我来说,这导致utf-8没有bom文件,无论源格式如何。

评论


这对我有用,除了我为需要使用-encoding utf8。

–只是鲁迪
17年1月12日14:53



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

–user1529294
17年4月7日在5:50

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

–mklement0
17年4月7日在13:51

#4 楼

注意:此答案适用于Windows PowerShell;请参阅“ Windows PowerShell”。相比之下,在跨平台的PowerShell Core版本(v6 +)中,在所有cmdlet中,不带BOM的UTF-8是默认编码。


换句话说:如果您使用的是在PowerShell [Core]版本6或更高版本中,默认情况下会获得无BOM的UTF-8文件(也可以使用-Encoding utf8 / -Encoding utf8NoBOM显式请求,而使用-utf8BOM的with-BOM编码)。


如果您正在运行Windows 10,并且愿意在全系统范围内切换到无BOM的UTF-8编码-可能会有副作用-甚至Windows PowerShell也可以使用无BOM的UTF-始终为8-请参见此答案。



为补充M. Dudley自己简单而务实的答案(以及ForNeVeR更简洁的表述):
为方便起见,以下为高级函数Out-FileUtf8NoBom,它是模拟基于Out-File的基于管道的替代方法,这意味着:

您可以像在管道中使用Out-File一样使用它。
不是字符串的输入对象将按照其格式进行格式化就像将它们发送到控制台一样,就像Out-File一样。
附加的-UseLF开关允许您将Windows风格的CRLF换行符转换为Unix风格的仅LF换行符。

示例:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath # Add -UseLF for Unix newlines

请注意(Get-Content $MyPath)是如何封装在(...)中的,这可确保在通过管道发送结果之前,打开,完整读取和关闭整个文件。为了能够写回到同一文件(就地更新),这是必要的。
通常,由于以下两个原因,建议不要使用此技术:(a)整个文件必须适合内存并且(b)如果该命令被中断,则数据将丢失。
有关内存使用的说明:


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


函数的源代码Out-FileUtf8NoBom
注意:该功能也可以作为MIT许可的Gist使用,并且只有将来才能维护。
您可以使用以下命令直接安装(我可以向您保证这样做是安全的,您应该始终在以这种方式直接执行之前检查脚本的内容):
# Download and define the function.
irm https://gist.github.com/mklement0/8689b9b5123a9ba11df7214f82a673be/raw/Out-FileUtf8NoBom.ps1 | iex

function Out-FileUtf8NoBom {
<#
.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-File parameters:
    * Only a literal output path is supported, and only as a parameter.
    * -Force is not supported.
    * Conversely, an extra -UseLF switch is supported for using LF-only newlines.
  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 Windows PowerShell
  lacks the ability to write UTF-8 files without a BOM: using -Encoding UTF8 
  invariably prepends a BOM.
  Copyright (c) 2017, 2020 Michael Klement <mklement0@gmail.com> (http://same2u.net), 
  released under the [MIT license](https://spdx.org/licenses/MIT#licenseText).
#>

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

  #requires -version 3

  # Convert the input path to a full one, since .NET's working dir. usually
  # differs from PowerShell's.
  $dir = Split-Path -LiteralPath $LiteralPath
  if ($dir) { $dir = Convert-Path -ErrorAction Stop -LiteralPath $dir } else { $dir = $pwd.ProviderPath}
  $LiteralPath = [IO.Path]::Combine($dir, [IO.Path]::GetFileName($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 System.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 | % { 
      if ($UseLf) {
        $sw.Write($_ + "`n") 
      }
      else {
        $sw.WriteLine($_) 
      }
    }
  } finally {
    $sw.Dispose()
  }

}


#5 楼

从版本6开始,powershell支持UTF8NoBOM的set-content和out-file编码,甚至将其用作默认编码。

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

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath


评论


真好仅供参考,使用$ PSVersionTable.PSVersion检查版本

– KCD
19-10-29在2:48

值得注意的是,在PowerShell [Core] v6 +中,永远不需要编码UTF8NoBOM,因为它是默认编码。

–mklement0
10月25日20:58

#6 楼

使用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()或类似产品的区别在于,它可以与任何类型的项目和路径,而不仅仅是实际的文件路径。

评论


尼斯-适用于字符串(可能只需要这些字符串,当然可以满足问题的要求)。万一您需要利用Out-File(与Set-Content不同)提供的格式,请先将管道传递给Out-String;例如,$ MyFile = Get-ChildItem |外弦

–mklement0
10月25日21:06



#7 楼

此脚本会将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年9月8日13:34

WriteAllLines解决方案非常适合小文件。但是,我需要更大文件的解决方案。每次我尝试将其用于更大的文件时,都会收到OutOfMemory错误。

–百慕大羊肉
15年3月25日在15:44

#8 楼

    [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)

#9 楼

如果要使用[System.IO.File]::WriteAllLines(),则应将第二个参数强制转换为String[](如果$MyFile的类型是Object[]),并使用$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"


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

评论


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

–mklement0
18年2月19日在16:05

#10 楼

通过扩展名将多个文件更改为不带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)
}


#11 楼

对于PowerShell 5.1,启用以下设置:
控制面板,区域,管理,更改系统区域设置,使用Unicode UTF-8
获得全球语言支持
,然后将其输入PowerShell:
$PSDefaultParameterValues['*:Encoding'] = 'Default'

或者,您可以升级到PowerShell 6或更高版本。
https://github.com/PowerShell/PowerShell

评论


明确说明:这是一个系统范围的设置,可使Windows PowerShell在所有cmdlet上默认都设置为不使用BOM的UTF-8,这可能是或不希望的,尤其是因为该功能仍处于测试阶段(截至撰写本文时) )并可能破坏旧版控制台应用程序-请参阅此答案以获取背景信息。

–mklement0
12月8日17:23



#12 楼

我使用的一种技术是使用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


评论


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

–mklement0
18-2-19在17:03

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

–阿米特·奈杜(Amit Naidu)
18年8月8日在0:06

#13 楼

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

$MyFile | Out-File -Encoding ASCII


评论


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

– ForneVeR
2015年10月5日15:05

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

–格雷格
2015年12月10日在22:34

@ForNeVeR:您正确地认为,ASCII编码不是UTF-8,但不是当前的ANSI代码页-您正在考虑使用Default; ASCII确实是7位ASCII编码,将> = 128的代码点转换为文字?实例。

–mklement0
16年1月21日在6:01

@ForNeVeR:您可能正在考虑“ ANSI”或“扩展的ASCII”。尝试执行以下操作以验证-Encoding ASCII确实仅是7位ASCII:'äb'|外文件($ f = [IO.Path] :: GetTempFilename())-编码ASCII; '?b'-eq $(获取内容$ f;删除项$ f)-ä已音译为?。相比之下,-Encoding Default(“ ANSI”)将正确保留它。

–mklement0
16年1月21日在15:07

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

– TNT
16年8月25日在19:25

#14 楼

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

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


结果是不带BOM的ASCII。

评论


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

–杜德利先生
2015年5月6日13:21



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

–eythort
16年8月5日在11:00

如果许多编辑器无法检测到编码,则它们以UTF-8格式打开文件。

–emptyother
17年7月22日在9:40