Как указывает Ансгар Вихерс в комментарии, проблема заключается в том, что Windows PowerShell при отсутствии спецификации по умолчанию интерпретирует файлы как "ANSI" -кодируется , т. е. кодировка, подразумеваемая языковым стандартом устаревшей системы (кодовая страница ANSI), как отражено .NET Framework (но не .NET Core ) в [System.Text.Encoding]::Default
.
Учитывая, что, исходя из ваших последующих комментариев, файлы без спецификации среди ваших входных файлов представляют собой смесь в кодировке Windows-1251 и UTF-8файлы , вы должны проверить их содержимое , чтобы определить их конкретную кодировку:
Считайте каждый файл с помощью -Encoding Utf8
и проверьте,результирующая строка содержит Unicode REPLACEMENT CHARACTER (U+FFFD
) .Если это так, это означает, что файл не UTF-8, потому что этот специальный символ используется, чтобы сигнализировать, что были обнаружены последовательности байтов, которые недопустимы в UTF-8.
Если файл не является допустимым UTF-8, просто прочитайте файл еще раз без , указав -Encoding
, что заставляет Windows PowerShell интерпретировать файл как кодированный Windows-1251,учитывая, что это кодировка (кодовая страница), подразумеваемая языковым стандартом вашей системы.
$MyPath = "D:\my projects\etc"
Get-ChildItem $MyPath\* -Include *.h, *.cpp, *.c | Foreach-Object {
# Note:
# * the use of -Encoding Utf8 to first try to read the file as UTF-8.
# * the use of -Raw to read the entire file as a *single string*.
$content = Get-Content -Raw -Encoding Utf8 $_.FullName
# If the replacement char. is found in the content, the implication
# is that the file is NOT UTF-8, so read it again *without -Encoding*,
# which interprets the files as "ANSI" encoded (Windows-1251, in your case).
if ($content.Contains([char] 0xfffd)) {
$content = Get-Content -Raw $_.FullName
}
# Note the use of WriteAllText() in lieu of WriteAllLines()
# and that no explicit encoding object is passed, given that
# .NET *defaults* to BOM-less UTF-8.
# CAVEAT: There's a slight risk of data loss if writing back to the input
# file is interrupted.
[System.IO.File]::WriteAllText($_.FullName, $content)
}
A Более быстрая альтернатива заключается в использовании [IO.File]::ReadAllText()
с UTF-8 объект кодирования, который генерирует исключение при обнаружении недопустимых байтов as-UTF-8 (синтаксис PSv5 +):
$utf8EncodingThatThrows = [Text.UTF8Encoding]::new($false, $true)
# ...
try {
$content = [IO.File]::ReadAllText($_.FullName, $utf8EncodingThatThrows)
} catch [Text.DecoderFallbackException] {
$content = [IO.File]::ReadAllText($_.FullName, [Text.Encoding]::Default)
}
# ...
Адаптация вышеуказанных решений к PowerShell Core / .NET Core:
PowerShell Ядро по умолчанию (без спецификации) UTF-8, поэтому просто пропустите -Encoding
не работает для чтения файлов в кодировке ANSI.
Аналогично, [System.Text.Encoding]::Default
неизменно reпорты UTF-8 в .NET Core.
Поэтому необходимо вручную определить кодовую страницу ANSI активной языковой системы и получить соответствующий объект кодирования :
$ansiEncoding = [Text.Encoding]::GetEncoding(
[int] (Get-ItemPropertyValue HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage ACP)
)
Затем необходимо явно передать эту кодировку в Get-Content -Encoding
(Get-Content -Raw -Encoding $ansiEncoding $_.FullName
) или в методы .NET ([IO.File]::ReadAllText($_.FullName, $ansiEncoding)
).
Оригинальная форма ответа:для входных файлов, уже все в кодировке UTF-8:
Следовательно, , если некоторые из ваших файлов в кодировке UTF-8 (уже) без спецификации, вы должны явно поручить Get-Content
обращаться с ними как с UTF-8, используя -Encoding Utf8
- в противном случае они будут неверно истолкованы, если они содержат символы вне 7-битного диапазона ASCII:
$MyPath = "D:\my projects\etc"
Get-ChildItem $MyPath\* -Include *.h, *.cpp, *.c | Foreach-Object {
# Note:
# * the use of -Encoding Utf8 to ensure the correct interpretation of the input file
# * the use of -Raw to read the entire file as a *single string*.
$content = Get-Content -Raw -Encoding Utf8 $_.FullName
# Note the use of WriteAllText() in lieu of WriteAllLines()
# and that no explicit encoding object is passed, given that
# .NET *defaults* to BOM-less UTF-8.
# CAVEAT: There's a slight risk of data loss if writing back to the input
# file is interrupted.
[System.IO.File]::WriteAllText($_.FullName, $content)
}
Примечание. Файлы UTF-8 без спецификации не требуют перезаписи в вашем сценарии, но это не мешает работе и упрощает код; альтернативой будет проверка , если первые 3 байта каждого файла являются спецификацией UTF-8 и пропустить такой файл:
$hasUtf8Bom = "$(Get-Content -Encoding Byte -First 3 $_.FullName)" -eq '239 187 191'
(Windows PowerShell) или
$hasUtf8Bom = "$(Get-Content -AsByteStream -First 3 $_.FullName)" -eq '239 187 191'
(PowerShell Core).
В качестве отступления: если есть входные файлы с кодировкой не-UTF8 (например, UTF-16), решение все еще работает , пока этифайлы имеют спецификацию , поскольку PowerShell (тихо) отдает предпочтение спецификации над кодировкой, указанной в -Encoding
.
Обратите внимание, что использование -Raw
/ WriteAllText()
длячтение / запись файлов в целом (одна строка) не только немного ускоряет обработку, но и обеспечивает сохранение следующих характеристик каждого входного файла :
- определенный стиль новой строки (CRLF (Windows) и LF-only (Unix))
- независимо от того, имеет ли последняя строка завершающий символ новой строки.
В отличие от этого, нетиспользование -Raw
(построчное чтение) и использование .WriteAllLines()
делает не сохранение этих характеристик: выnvariably получить соответствующие новой платформе строки (в Windows PowerShell, всегда CRLF), и вы всегда получаете в конце новой строки.
Обратите внимание, что мультиплатформенный Powershell Core edition заметно по умолчанию использует UTF-8 при чтении файла без спецификации и также по умолчанию создает файлы UTF-8 без спецификации - создание файла UTF-8 с Спецификация требует явного согласия с -Encoding utf8BOM
.
Следовательно, решение PowerShell Core намного проще :
# PowerShell Core only.
$MyPath = "D:\my projects\etc"
Get-ChildItem $MyPath\* -Include *.h, *.cpp, *.c | Foreach-Object {
# * Read the file at hand (UTF8 files both with and without BOM are
# read correctly).
# * Simply rewrite it with the *default* encoding, which in
# PowerShell Core is BOM-less UTF-8.
# Note the (...) around the Get-Content call, which is necessary in order
# to write back to the *same* file in the same pipeline.
# CAVEAT: There's a slight risk of data loss if writing back to the input
# file is interrupted.
(Get-Content -Raw $_.FullName) | Set-Content -NoNewline $_.FullName
}
Более быстрое решение на основе .NET
Вышеуказанные решения работают, но Get-Content
и Set-Content
относительно медленны , поэтому использование типов .NET для чтения и перезаписи файлов будет работать лучше.
Как и выше, никакое кодирование не должно быть явно указано в следующем решении (даже в Windows PowerShell ), потому что .NET с самого начала рекомендовал использовать UTF-8 без спецификации для UTF-8 (все еще распознавая спецификацию UTF-8 , если присутствует):
$MyPath = "D:\my projects\etc"
Get-ChildItem $MyPath\* -Include *.h, *.cpp, *.c | Foreach-Object {
# CAVEAT: There's a slight risk of data loss if writing back to the input
# file is interrupted.
[System.IO.File]::WriteAllText(
$_.FullName,
[System.IO.File]::ReadAllText($_.FullName)
)
}