Usando o PowerShell para gravar um arquivo em UTF-8 sem a BOM

246

Out-File parece forçar a lista técnica ao usar UTF-8:

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

Como gravar um arquivo em UTF-8 sem BOM usando o PowerShell?

M. Dudley
fonte
23
BOM = marca de ordem de bytes. Três caracteres colocados no início de um arquivo (0xEF, 0xBB, 0xBF) que se parecem com "ï» ¿"
Signal15
40
Isso é incrivelmente frustrante. Até módulos de terceiros ficam poluídos, como tentar fazer upload de um arquivo por SSH? BOM! "Sim, vamos corromper todos os arquivos; isso parece uma boa idéia." -Microsoft.
MichaelGG
3
A codificação padrão é UTF8NoBOM a partir da versão PowerShell 6.0 docs.microsoft.com/en-us/powershell/module/...
Paul Shiryaev
Fale sobre quebrar a compatibilidade com versões anteriores ...
Dragas 13/01

Respostas:

220

Usar a UTF8Encodingclasse do .NET e passar $Falsepara o construtor parece funcionar:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
M. Dudley
fonte
42
Espero que esse não seja o único caminho.
Scott Muc
114
Uma linha [System.IO.File]::WriteAllLines($MyPath, $MyFile)é suficiente. Essa WriteAllLinessobrecarga grava exatamente UTF8 sem BOM.
Roman Kuzmin
6
Criou uma solicitação de recurso do MSDN aqui: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav 18/02/15
3
Observe que WriteAllLinesparece exigir $MyPathser absoluto.
precisa saber é o seguinte
9
@xdhmoore WriteAllLinesobtém o diretório atual de [System.Environment]::CurrentDirectory. Se você abrir o PowerShell e alterar o diretório atual (usando cdou Set-Location), [System.Environment]::CurrentDirectorynão será alterado e o arquivo acabará no diretório errado. Você pode contornar isso por [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee
79

A maneira correta a partir de agora é usar uma solução recomendada por @Roman Kuzmin nos comentários para @M. Dudley responde :

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

(Também reduzi um pouco removendo os Systemesclarecimentos desnecessários do espaço para nome - ele será substituído automaticamente por padrão.)

Para nunca
fonte
2
Esta (por qualquer motivo) não removeu o BOM para mim, onde, como a resposta aceita fez
Liam
@ Liam, provavelmente alguma versão antiga do PowerShell ou .NET?
ForNeVeR
1
Acredito que versões mais antigas da função .NET WriteAllLines gravaram a lista técnica por padrão. Portanto, pode ser um problema de versão.
Bender the Great
2
Confirmado com gravações com uma lista técnica no Powershell 3, mas sem uma lista técnica no Powershell 4. Eu tive que usar a resposta original de M. Dudley.
chazbot7
2
Por isso, funciona no Windows 10, onde é instalado por padrão. :) Além disso, sugeriu melhorias:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal
50

Achei que isso não seria UTF, mas acabei de encontrar uma solução bastante simples que parece funcionar ...

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

Para mim, isso resulta em um utf-8 sem arquivo bom, independentemente do formato de origem.

Lenny
fonte
8
Isso funcionou para mim, exceto que eu usei -encoding utf8para minha exigência.
Chim Chimz
1
Muito obrigado. Estou trabalhando com logs de despejo de uma ferramenta - que tinha guias dentro dela. UTF-8 não estava funcionando. ASCII resolveu o problema. Obrigado.
user1529294
44
Sim, -Encoding ASCIIevita o problema da lista técnica, mas obviamente você recebe apenas caracteres ASCII de 7 bits . Como o ASCII é um subconjunto do UTF-8, o arquivo resultante também é tecnicamente um arquivo UTF-8 válido, mas todos os caracteres não ASCII da sua entrada serão convertidos em ?caracteres literais .
precisa saber é o seguinte
4
@ChimChimz Eu votei acidentalmente seu comentário, mas -encoding utf8ainda gera UTF-8 com uma BOM. :(
TheDudeAbides
33

Nota: Esta resposta se aplica ao Windows PowerShell ; por outro lado, na edição do PowerShell Core para várias plataformas (v6 +), UTF-8 sem BOM é a codificação padrão em todos os cmdlets.
Em outras palavras: se você estiver usando o PowerShell [Core] versão 6 ou superior , por padrão , você obtém arquivos UTF-8 sem BOM (que você também pode solicitar explicitamente com -Encoding utf8/ -Encoding utf8NoBOM, enquanto que com a codificação -BOM -utf8BOM).


Para complementar a resposta simples e pragmática de M. Dudley (e a reformulação mais concisa do ForNeVeR ):

Por conveniência, aqui está a função avançada Out-FileUtf8NoBom, uma alternativa baseada em pipeline que imitaOut-File , o que significa:

  • você pode usá-lo como Out-Fileem um pipeline.
  • os objetos de entrada que não são cadeias de caracteres são formatados como seriam se você os enviasse para o console, assim como com Out-File.

Exemplo:

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

Observe como (Get-Content $MyPath)está incluído (...), o que garante que o arquivo inteiro seja aberto, lido na íntegra e fechado antes de enviar o resultado pelo pipeline. Isso é necessário para poder gravar novamente no mesmo arquivo (atualize-o no local ).
Geralmente, porém, essa técnica não é aconselhável por 2 motivos: (a) o arquivo inteiro deve caber na memória e (b) se o comando for interrompido, os dados serão perdidos.

Uma observação sobre o uso da memória :

  • A resposta de M. Dudley exige que todo o conteúdo do arquivo seja construído na memória primeiro, o que pode ser problemático com arquivos grandes.
  • A função abaixo aprimora isso apenas um pouco: todos os objetos de entrada ainda são armazenados em buffer primeiro, mas suas representações de sequência são geradas e gravadas no arquivo de saída, uma por uma.

Código fonte deOut-FileUtf8NoBom (também disponível como um Gist licenciado pelo MIT ):

<#
.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()
  }

}
mklement0
fonte
16

A partir da versão 6, o powershell suporta a UTF8NoBOMcodificação para conteúdo definido e arquivo externo e até a usa como codificação padrão.

Portanto, no exemplo acima, deve ser simplesmente assim:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
sc911
fonte
@ RaúlSalinas-Monteagudo em que versão você está?
John Bentley
Agradável. FYI verificar versão com$PSVersionTable.PSVersion
KCD
14

Ao usar em Set-Contentvez de Out-File, você pode especificar a codificação Byte, que pode ser usada para gravar uma matriz de bytes em um arquivo. Isso em combinação com uma codificação UTF8 personalizada que não emite a lista técnica fornece o resultado desejado:

# 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

A diferença de usar [IO.File]::WriteAllLines()ou semelhante é que ele deve funcionar bem com qualquer tipo de item e caminho, não apenas os caminhos reais do arquivo.

Lucero
fonte
5

Esse script converterá, para UTF-8 sem BOM, todos os arquivos .txt no DIRECTORY1 e os produzirá em DIRECTORY2

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
jamhan
fonte
Este falha sem nenhum aviso. Qual versão do PowerShell devo usar para executá-lo?
darksoulsong
3
A solução WriteAllLines funciona muito bem para arquivos pequenos. No entanto, preciso de uma solução para arquivos maiores. Toda vez que tento usar isso com um arquivo maior, recebo um erro OutOfMemory.
usar o seguinte
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" 
    }  

Origem Como remover UTF8 Byte Order Mark (BOM) de um arquivo usando o PowerShell

bronzeado
fonte
2

Se você deseja usar [System.IO.File]::WriteAllLines(), deve converter o segundo parâmetro para String[](se o tipo de $MyFilefor Object[]) e também especificar o caminho absoluto com $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), como:

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

Se você deseja usar [System.IO.File]::WriteAllText(), algumas vezes você deve canalizar o segundo parâmetro | Out-String |para adicionar CRLFs ao final de cada linha explicitamente (especialmente quando você os usa 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)

Ou você pode usar [Text.Encoding]::UTF8.GetBytes()com 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"

consulte: Como gravar o resultado do ConvertTo-Csv em um arquivo no UTF-8 sem BOM

SATO Yusuke
fonte
Boas dicas; sugestões /: a alternativa mais simples para $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)é Convert-Path $MyPath; se você deseja garantir um CRLF à direita, basta usar [System.IO.File]::WriteAllLines()mesmo com uma única sequência de entrada (não é necessário Out-String).
mklement0
0

Uma técnica que utilizo é redirecionar a saída para um arquivo ASCII usando o cmdlet Out-File .

Por exemplo, geralmente executo scripts SQL que criam outro script SQL para executar no Oracle. Com o redirecionamento simples (">"), a saída será em UTF-16, que não é reconhecida pelo SQLPlus. Para contornar isso:

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

O script gerado pode ser executado através de outra sessão do SQLPlus sem preocupações com o Unicode:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
Erik Anderson
fonte
4
Sim, -Encoding ASCIIevita o problema da lista técnica, mas obviamente você só obtém suporte para caracteres ASCII de 7 bits . Como o ASCII é um subconjunto do UTF-8, o arquivo resultante também é tecnicamente um arquivo UTF-8 válido, mas todos os caracteres não ASCII da sua entrada serão convertidos em ?caracteres literais .
mklement0
Esta resposta precisa de mais votos. A incompatibilidade do sqlplus com a BOM é uma causa de muitas dores de cabeça .
Amit Naidu
0

Altere vários arquivos por extensão para UTF-8 sem 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)
}
Jaume Suñer Mut
fonte
0

Por qualquer motivo, as WriteAllLineschamadas ainda estavam produzindo uma lista técnica para mim, com o UTF8Encodingargumento BOMless e sem ele. Mas o seguinte funcionou para mim:

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

Eu tive que tornar o caminho do arquivo absoluto para que ele funcionasse. Caso contrário, ele gravou o arquivo na minha área de trabalho. Além disso, suponho que isso funcione apenas se você souber que sua lista técnica é de 3 bytes. Não tenho idéia de quão confiável é esperar um determinado formato / comprimento de lista técnica com base na codificação.

Além disso, conforme escrito, isso provavelmente só funcionará se o arquivo se encaixar em uma matriz do PowerShell, que parece ter um limite de tamanho de algum valor menor do que [int32]::MaxValuena minha máquina.

xdhmoore
fonte
1
WriteAllLinessem um argumento de codificação nunca grava uma BOM propriamente dita , mas é concebível que sua string tenha começado com o caractere BOM ( U+FEFF), que ao escrever efetivamente criou uma BOM UTF-8; por exemplo: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)(omita o [char] 0xfeff + para ver que nenhuma lista técnica é gravada).
mklement0
1
Quanto à gravação inesperada em um local diferente: o problema é que a estrutura .NET geralmente possui um diretório atual diferente do PowerShell; você pode sincronizá-los primeiro com [Environment]::CurrentDirectory = $PWD.ProviderPath, ou, como uma alternativa mais genérica à sua "$(pwd)\..."abordagem (melhor: "$pwd\...", ainda melhor: "$($pwd.ProviderPath)\..."ou (Join-Path $pwd.ProviderPath ...)), o uso(Convert-Path BOMthetorpedoes.txt)
mklement0
Obrigado, eu não sabia que poderia haver um único caractere de BOM para a conversão de UTF-8 BOM dessa maneira.
Xdhmoore
1
Todas as seqüências de bytes da BOM (assinaturas Unicode) são de fato a representação de bytes da respectiva codificação do caractere Unicode únicoU+FEFF abstrato .
precisa saber é o seguinte
Ah ok. Isso parece simplificar as coisas.
xdhmoore 21/02
-2

Poderia usar abaixo para obter UTF8 sem BOM

$MyFile | Out-File -Encoding ASCII
Robin Wang
fonte
4
Não, ele converterá a saída na página de código ANSI atual (cp1251 ou cp1252, por exemplo). Não é UTF-8!
ForNeVeR
1
Obrigado Robin. Isso pode não ter funcionado para gravar um arquivo UTF-8 sem a BOM, mas a opção -Encoding ASCII removeu a BOM. Dessa forma, eu poderia gerar um arquivo bat para o gvim. O arquivo .bat estava disparando na BOM.
Greg
3
@ForNeVeR: Você está certo que a codificação ASCIInão é UTF-8, mas também não é a página de código ANSI atual - você está pensando Default; ASCIIverdadeiramente é a codificação ASCII de 7 bits, com pontos de código> = 128 sendo convertidos em ?instâncias literais .
precisa saber é o seguinte
1
@ForNeVeR: Você provavelmente está pensando em "ANSI" ou " ASCII estendido ". Tente isso para verificar -Encoding ASCIIse realmente é apenas ASCII de 7 bits: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- o äfoi transliterado para a ?. Por outro lado, -Encoding Default("ANSI") a preservaria corretamente.
precisa saber é o seguinte
3
@rob Esta é a resposta perfeita para todos que simplesmente não precisam de utf-8 ou qualquer outra coisa diferente do ASCII e não estão interessados ​​em entender codificações e a finalidade do unicode. Você pode usá- lo como utf-8 porque os caracteres utf-8 equivalentes a todos os caracteres ASCII são idênticos (significa converter um arquivo ASCII em um arquivo utf-8 resulta em um arquivo idêntico (se não houver BOM)). Para todos os que possuem caracteres não ASCII em seu texto, essa resposta é apenas falsa e enganosa.
TNT
-3

Este funciona para mim (use "Padrão" em vez de "UTF8"):

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

O resultado é ASCII sem BOM.

Krzysztof
fonte
1
De acordo com a documentação do arquivo especificado, a Defaultcodificação usará a página de código ANSI atual do sistema, que não é UTF-8, conforme necessário.
M. Dudley
Isso parece funcionar para mim, pelo menos para Export-CSV. Se você abrir o arquivo resultando em um editor adequado, a codificação do arquivo é UTF-8 sem BOM, e não ocidentais Latina ISO 9 como eu teria esperado com ASCII
eythort
Muitos editores abrem o arquivo como UTF-8 se não conseguirem detectar a codificação.
emptyother