Использование PowerShell для записи файла в UTF-8 без спецификации

Out-File Кажется, что заставляет BOM при использовании UTF-8:

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

Как я могу написать файл в UTF-8 без спецификации с помощью PowerShell?

14 ответов


использование .NET UTF8Encoding класса и передает $False конструктору кажется, что работает:

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

на правильный способ на данный момент-использовать решение, рекомендованное @Roman Kuzmin в комментариях к @M. Dudley ответ:

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

(Я также немного сократил его, зачистив ненужное System уточнение пространства имен - по умолчанию оно будет заменено автоматически.)


Я подумал, что это не будет UTF, но я просто нашел довольно простое решение, которое, кажется, работает...

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

для меня это приводит к utf-8 Без файла спецификации независимо от исходного формата.


дополнить собственный простой и прагматичный ответ М. ДадлиForNeVeR более краткой формулировке):

для удобства, вот расширенная функция Out-FileUtf8NoBom, альтернатива на основе конвейера, которая имитирует Out-File, что означает:

  • вы можете использовать его просто как Out-File в трубопроводе.
  • входные объекты, которые не являются строками, форматируются так, как если бы вы отправили их на консоль, так же, как с Out-File.

пример:

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

обратите внимание, как (Get-Content $MyPath), заключенный в (...), что гарантирует, что весь файл будет открыт, прочитан полностью и закрыт перед отправкой результата по конвейеру. Это необходимо для того, чтобы иметь возможность писать обратно к то же самое (обновление это на месте).
Как правило, этот метод не рекомендуется по 2 причинам: (a) весь файл должен соответствовать память и (b) если команда прервана, данные будут потеряны.

Примечание использование памяти:

  • собственный ответ М. Дадли требует, чтобы все содержимое файла в памяти, что может быть проблематичным с большими файлами.
  • функция ниже улучшает это только немного: все входные объекты сначала буферизуются, но их строковые представления затем генерируются и записываются в выходной файл один за другим.

код 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) # 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()
  }

}

этот скрипт преобразует, в UTF-8 без BOM, все .txt файлы в DIRECTORY1 и выводить их в DIRECTORY2

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

при использовании Set-Content вместо Out-File, вы можете указать кодировку Byte, который может использоваться для записи массива байтов в файл. Это в сочетании с пользовательской кодировкой 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() или аналогично, что он должен работать нормально с любым типом элемента и пути, а не только фактические пути к файлам.


была та же проблема. Это сделало трюк для меня:

$MyFile | Out-File -Encoding Oem $MyPath

при открытии файла с кодом Visual Studio или Notepad++ он отображается как UTF-8


один из методов, который я использую, - перенаправить вывод в файл ASCII с помощью Out-File.

например, я часто запускаю сценарии SQL, которые создают другой сценарий SQL для выполнения в Oracle. При простом перенаправлении ( " > " ) вывод будет в 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

измените несколько файлов по расширению на 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)
}

по какой причине WriteAllLines звонки все еще производили BOM для меня, с BOMless

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

мне пришлось сделать путь к файлу абсолютным, чтобы он работал. В противном случае он записал файл на мой рабочий стол. Кроме того, я полагаю, что это работает, только если вы знаете, что ваша спецификация составляет 3 байта. Я понятия не имею, насколько надежно ожидать заданный формат/длину спецификации на основе кодирования.

кроме того, как написано, это, вероятно работает, только если ваш файл помещается в массив powershell, который, похоже, имеет ограничение длины некоторого значения ниже [int32]::MaxValue на моей машине.


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

источник Как удалить метку порядка байтов UTF8 (BOM) из файла с помощью PowerShell


если вы хотите использовать [System.IO.File]::WriteAllLines(), вы должны привести второй параметр к String[] (если типа $MyFile is 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 | чтобы добавить CRLFs в конец каждой строки явно (особенно когда вы используете их с 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 в файл в UTF-8 без BOM


можно использовать НИЖЕ, чтобы получить UTF8 без BOM

$MyFile | Out-File -Encoding ASCII

это работает для меня (используйте "Default" вместо "UTF8"):

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

результат ASCII без BOM.