Out-File
кажется, заставляет спецификацию при использовании UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Как я могу написать файл в UTF-8 без спецификации, используя PowerShell?
Out-File
кажется, заставляет спецификацию при использовании UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Как я могу написать файл в UTF-8 без спецификации, используя PowerShell?
Ответы:
Использование UTF8Encoding
класса .NET и передача $False
в конструктор, кажется, работает:
$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
[System.IO.File]::WriteAllLines($MyPath, $MyFile)
достаточно. Эта WriteAllLines
перегрузка пишет именно UTF8 без спецификации.
WriteAllLines
кажется, требуется $MyPath
быть абсолютным.
WriteAllLines
получает текущий каталог от [System.Environment]::CurrentDirectory
. Если вы откроете PowerShell, а затем измените свой текущий каталог (используя cd
или Set-Location
), [System.Environment]::CurrentDirectory
это не изменится, и файл окажется в неправильном каталоге. Вы можете обойти это путем [System.Environment]::CurrentDirectory = (Get-Location).Path
.
На данный момент правильным способом является использование решения, рекомендованного @Roman Kuzmin в комментариях к @M. Дадли ответ :
[IO.File]::WriteAllLines($filename, $content)
(Я также немного сократил это, удалив ненужное System
уточнение пространства имен - оно будет заменено автоматически по умолчанию.)
[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Я подумал, что это не будет UTF, но я нашел довольно простое решение, которое, кажется, работает ...
Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext
Для меня это приводит к UTF-8 без файла BOM независимо от исходного формата.
-encoding utf8
для моего требования.
-Encoding ASCII
избегает проблемы спецификации, но вы, очевидно, получаете только 7-битные символы ASCII . Учитывая, что ASCII является подмножеством UTF-8, результирующий файл технически также является допустимым файлом UTF-8, но все не входящие в ASCII символы в вашем вводе будут преобразованы в литеральные ?
символы .
-encoding utf8
все равно выдает UTF-8 с спецификацией. :(
Примечание. Этот ответ относится к Windows PowerShell ; напротив, в кроссплатформенном выпуске PowerShell Core (v6 +) UTF-8 без спецификации является кодировкой по умолчанию для всех командлетов.
Другими словами: если вы используете PowerShell [Core] версии 6 или выше , по умолчанию вы получаете файлы без BOM UTF-8 (которые вы также можете явно запрашивать с помощью -Encoding utf8
/ -Encoding utf8NoBOM
, тогда как вы получаете с -BOM с кодировкой -utf8BOM
).
В дополнение к простому и прагматичному ответу М. Дадли (и более краткой переформулировке ForNeVeR ):
Для удобства, вот расширенная функция Out-FileUtf8NoBom
, альтернатива на основе конвейера, которая имитируетOut-File
, что означает:
Out-File
в конвейере.Out-File
.Пример:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath
Обратите внимание на то, как (Get-Content $MyPath)
это включено (...)
, что гарантирует, что весь файл будет открыт, прочитан полностью и закрыт перед отправкой результата по конвейеру. Это необходимо для возможности обратной записи в тот же файл (обновить его на месте ).
Однако, как правило, этот метод не рекомендуется по двум причинам: (а) весь файл должен уместиться в памяти и (б) если команда прервана, данные будут потеряны.
Примечание об использовании памяти :
Исходный кодOut-FileUtf8NoBom
(также доступный как Mist-лицензированный 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()
}
}
Начиная с версии 6 powershell поддерживает UTF8NoBOM
кодировку как для set-content, так и out-file, и даже использует ее в качестве кодировки по умолчанию.
Так что в приведенном выше примере это должно быть просто так:
$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
$PSVersionTable.PSVersion
При использовании 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()
или аналогичного заключается в том, что он должен хорошо работать с любым типом элемента и пути, а не только с реальными путями к файлам.
Этот скрипт преобразует в UTF-8 без спецификации все TXT-файлы в DIRECTORY1 и выводит их в DIRECTORY2.
foreach ($i in ls -name DIRECTORY1\*.txt)
{
$file_content = Get-Content "DIRECTORY1\$i";
[System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
[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 Byte Order Mark (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 |
передать второй параметр, чтобы явно добавить 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 в файл в UTF-8 без спецификации
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
является Convert-Path $MyPath
; если вы хотите обеспечить конечный CRLF, просто используйте [System.IO.File]::WriteAllLines()
даже с одной входной строкой (без необходимости Out-String
).
Я использую одну технику, чтобы перенаправить вывод в файл 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 без каких-либо проблем с Юникодом:
sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
-Encoding ASCII
позволяет избежать проблемы спецификации, но вы, очевидно, получаете поддержку только 7-битных символов ASCII . Учитывая, что ASCII является подмножеством UTF-8, результирующий файл технически также является допустимым файлом UTF-8, но все не входящие в ASCII символы в вашем вводе будут преобразованы в литеральные ?
символы .
Измените несколько файлов по расширению на 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 для меня, с UTF8Encoding
аргументом BOMless и без него. Но у меня сработало следующее:
$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])
Я должен был сделать путь к файлу абсолютным, чтобы он работал. В противном случае он записал файл на мой рабочий стол. Кроме того, я полагаю, это работает, только если вы знаете, что ваша спецификация составляет 3 байта. Я понятия не имею, насколько надежно ожидать заданный формат / длину спецификации на основе кодирования.
Кроме того, как написано, это, вероятно, работает, только если ваш файл помещается в массив powershell, который, кажется, имеет ограничение длины на некоторое значение ниже, чем [int32]::MaxValue
на моем компьютере.
WriteAllLines
без аргумента кодирования никогда не записывается сама спецификация , но вполне возможно, что ваша строка начиналась с символа BOM ( U+FEFF
), который при записи эффективно создавал спецификацию UTF-8; например: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)
(опускаем , [char] 0xfeff +
чтобы увидеть , что нет BOM не написано).
[Environment]::CurrentDirectory = $PWD.ProviderPath
, либо, в качестве более общей альтернативы вашему "$(pwd)\..."
подходу (лучше:, "$pwd\..."
даже лучше: "$($pwd.ProviderPath)\..."
или (Join-Path $pwd.ProviderPath ...)
), использовать(Convert-Path BOMthetorpedoes.txt)
U+FEFF
.
Можно использовать ниже, чтобы получить UTF8 без спецификации
$MyFile | Out-File -Encoding ASCII
ASCII
не UTF-8, но это не текущая кодовая страница ANSI - вы думаете Default
; ASCII
действительно является 7-битной кодировкой ASCII, с кодовыми точками> = 128, преобразованными в литеральные ?
экземпляры.
-Encoding ASCII
это действительно только 7-битный ASCII: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)
- ä
был транслитерирован в ?
. Напротив, -Encoding Default
(«ANSI») правильно его сохранит.
Это работает для меня (используйте «По умолчанию» вместо «UTF8»):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
Результат ASCII без спецификации.
Default
кодировку, будет использоваться текущая кодовая страница ANSI системы, которая не является UTF-8, как мне требовалось.