Out-File
Здається, що змушує BOM використовувати UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Як я можу записати файл в UTF-8 без BOM за допомогою PowerShell?
Out-File
Здається, що змушує BOM використовувати UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Як я можу записати файл в UTF-8 без BOM за допомогою 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 без BOM.
WriteAllLines
здається, потрібно $MyPath
абсолютне значення.
WriteAllLines
отримує поточний каталог від [System.Environment]::CurrentDirectory
. Якщо ви відкриєте PowerShell, а потім зміните поточний каталог (використовуючи cd
або Set-Location
), він [System.Environment]::CurrentDirectory
не буде змінено, і файл в кінцевому підсумку опиниться в неправильному каталозі. Ви можете обійти це за допомогою [System.Environment]::CurrentDirectory = (Get-Location).Path
.
Правильний шлях як зараз полягає в використанні рішення, @Roman Кузьмін рекомендований в коментарі до @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
уникнути проблеми BOM, але ви, очевидно, отримуєте лише 7-бітові символи ASCII . Зважаючи на те, що ASCII є підмножиною UTF-8, отриманий файл технічно також є дійсним файлом UTF-8, але всі символи, що не є ASCII у вашому введенні, будуть перетворені в буквальні ?
символи .
-encoding utf8
все одно виводить UTF-8 з BOM. :(
Примітка. Ця відповідь стосується Windows PowerShell ; на відміну від цього, у міжплатформенній версії PowerShell Core (v6 +) UTF-8 без BOM є кодуванням за замовчуванням для всіх командлетів.
Іншими словами: Якщо ви використовуєте 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)
додається (...)
, що забезпечує відкриття всього файлу, читання повністю та закриття перед надсиланням результату по конвеєру. Це необхідно для того, щоб можна було записати назад у той самий файл (оновити його на місці ).
Однак, як правило, ця методика недоцільна з 2 причин: (а) весь файл повинен вміститися в пам'яті та (б) якщо команда буде перервана, дані втрачаються.
Примітка про використання пам'яті :
Вихідний кодOut-FileUtf8NoBom
(також доступний як ліцензований 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()
}
}
Починаючи з версії 6 повноваження підтримує UTF8NoBOM
кодування як для встановленого контенту, так і для файлу, і навіть використовує це як кодування за замовчуванням.
Тож у наведеному вище прикладі це має бути просто так:
$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
$PSVersionTable.PSVersion
Використовуючи Set-Content
замість Out-File
, ви можете вказати кодування Byte
, яке можна використовувати для запису байтового масиву у файл. Це в поєднанні зі спеціальним кодуванням UTF8, яке не випромінює BOM, дає бажаний результат:
# 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 без 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);
}
[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
є 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 без BOM
$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 без будь-яких турбот Unicode:
sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
-Encoding ASCII
уникає проблеми BOM, але ти, очевидно, отримуєш підтримку лише для 7-бітових символів ASCII . Зважаючи на те, що ASCII є підмножиною UTF-8, отриманий файл технічно також є дійсним файлом UTF-8, але всі символи, що не є ASCII у вашому введенні, будуть перетворені в буквальні ?
символи .
Змініть декілька файлів шляхом розширення на UTF-8 без 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)
}
З будь-якої причини, WriteAllLines
дзвінки все ще виробляли BOM для мене, з UTF8Encoding
аргументом BOMless і без цього. Але мені працювало наступне:
$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])
Я повинен був зробити шлях до файлу абсолютним, щоб він працював. Інакше він записав файл на мій робочий стіл. Крім того, я вважаю, що це працює лише в тому випадку, якщо ви знаєте, що ваш BOM має 3 байти. Я поняття не маю, наскільки надійно очікувати заданого формату / довжини BOM на основі кодування.
Крім того, як написано, це, ймовірно, працює лише в тому випадку, якщо ваш файл вписується в масив повноцінної оболонки, який, здається, має обмеження довжини на якесь значення нижче, ніж [int32]::MaxValue
на моїй машині.
WriteAllLines
без кодування аргумент ніколи не пише BOM сам , але це можливо , що ваша рядок трапилася почати з BOM символом ( U+FEFF
), який з написання ефективно створив BOM 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 без BOM
$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)
- ä
транслітеровано в a ?
. Навпаки, -Encoding Default
("ANSI") правильно збереже його.
Цей для мене працює (використовуйте "За замовчуванням" замість "UTF8"):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
Результат - ASCII без BOM.
Default
кодування буде використано поточну кодову сторінку ANSI системи, яка не є UTF-8, як мені потрібно.