Как я могу перенаправить ввод в PowerShell без спецификации? - PullRequest
4 голосов
/ 08 февраля 2020

Я пытаюсь перенаправить ввод в PowerShell следующим образом:

Get-Content input.txt | my-program args

Проблема в том, что переданному по трубопроводу тексту UTF-8 предшествует спецификация (0xEFBBBF), и моя программа не может обработать это правильно.

Минимальный рабочий пример:

// File: Hex.java
import java.io.IOException;

public class Hex {
    public static void main(String[] dummy) {
        int ch;
        try {
            while ((ch = System.in.read()) != -1) {
                System.out.print(String.format("%02X ", ch));
            }
        } catch (IOException e) {
        }
    }
}

Затем в PowerShell:

javac Hex.java
Set-Content textfile "ABC" -Encoding Ascii
# Now the content of textfile is 0x41 42 43 0D 0A
Get-Content textfile | java Hex

Или просто

javac Hex.java
Write-Output "ABC" | java Hex

В любом случае вывод будет EF BB BF 41 42 43 0D 0A.

Как передать текст в программу без 0xEFBBBF?

Ответы [ 2 ]

3 голосов
/ 10 февраля 2020

Примечание. Нижеследующее содержит общую информацию, которая в нормально функционирующей среде PowerShell могла бы объяснить симптом OP. То, что решение не работает в случае OP, связано с машинно-определенными c причинами, которые неизвестны на данный момент.

Чтобы гарантировать, что ваша Java программа получает свой ввод UTF- 8-кодированный без спецификации , необходимо установить $OutputEncoding на System.Text.UTF8Encoding экземпляр, который не излучает спецификацию :

# Assigns UTF-8 encoding *without a BOM*.
# PowerShell uses this encoding to encode data piped to external programs.
# $OutputEncoding defaults to ASCII(!) in Windows PowerShell, and more sensibly
# to BOM-*less* UTF-8 in PowerShell [Core] v6+
$OutputEncoding = [Text.UTF8Encoding]::new($false)

Предостережение : НЕ используйте, по-видимому, эквивалентный New-Object Text.Utf8Encoding $false, поскольку из-за ошибки, описанной в , эта проблема GitHub, он не будет работать, если вы назначите $OutpuEncoding в неглобальной области, например, в script .

Если, напротив, вы используете [Text.Encoding]::Utf8 (System.Text.Encoding.UTF8), вы получите спецификацию, что, как я подозреваю, произошло в вашем случае.


Обратите внимание, что эта проблема не связана с источником кодирование любого файла, считываемого Get-Content, потому что , что отправляется через конвейер PowerShell, никогда не является потоком необработанных байтов , но . NET объектов , что в дело о f Get-Content означает, что. NET строки отправляются (System.String, внутренняя последовательность кодовых единиц UTF-16).

Поскольку вы отправляете по трубопроводу на внешнюю программу (в вашем случае - Java), PowerShell символьно кодирует отправляемые ему объекты (stringified-on-demand) на основе переменная предпочтения $OutputEncoding, и полученная внешняя кодировка - это то, что получает внешняя программа.

Возможно, что удивительно, хотя спецификации обычно используются в файлах , PowerShell учитывает настройку спецификации кодировки, назначенную $OutputEncoding, а также в конвейере , добавляя ее к первой отправленной строке (только).

См. Нижний раздел из этого ответа для получения дополнительной информации о том, как PowerShell обрабатывает конвейерный ввод и вывод из внешних программ, включая то, как это [Console]::OutputEncoding, что имеет значение, когда PowerShell интерпретирует данные, полученные из external программы .


Чтобы проиллюстрировать разницу, используя пример программы (обратите внимание, что достаточно использовать строковый литерал PowerShell в качестве входных данных; нет необходимости читать из файла):

# Note the EF BB BF sequence representing the UTF-8 BOM.
# Enclosure in & { ... } ensures that a local, temporary copy of $OutputEncoding
# is used.
PS> & { $OutputEncoding = [Text.Encoding]::Utf8; 'hö' | java Hex }
EF BB BF 68 C3 B6 0D 0A

# Note the absence of EF BB BF, due to using a BOM-less
# UTF-8 encoding.
PS> & { $OutputEncoding = [Text.Utf8Encoding]::new($false); 'hö' | java Hex }
68 C3 B6 0D 0A

В Windows PowerShell , где $OutputEncoding по умолчанию ASCII (!), вы увидите следующее со значением по умолчанию на месте:

# The default of ASCII(!) results in *lossy* encoding in Windows PowerShell.
PS> 'hö' | java Hex 
68 3F 0D 0A

Обратите внимание, что 3F представляет буквальный символ ?, который также транслитерируется не-ASCII ö, учитывая, что он не представлен в ASCII; другими словами: информация была потеряна .

PowerShell [Core] v6 + теперь разумно по умолчанию использует UTF-8 без спецификации, поэтому поведение по умолчанию там, как и ожидалось .
Хотя UTF-8 без спецификации является PowerShell [Core] по умолчанию , также для командлетов, которые читают из файлов и записывают в них, в Windows [Console]::OutputEncoding по-прежнему отражает активную кодовую страницу OEM по умолчанию начиная с v7.0, поэтому для корректного захвата вывода от внешних программ, генерирующих UTF-8, он также должен быть установлен на [Text.UTF8Encoding]::new($false) - см. эту проблему GitHub .

1 голос
/ 08 февраля 2020

Вы можете попробовать установить OutputEncoding в UTF-8 без спецификации:

# Keep the current output encoding in a variable
$oldEncoding = [console]::OutputEncoding

# Set the output encoding to use UTF8 without BOM
[console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false

Get-Content input.txt | my-program args

# Reset the output encoding to the previous
[console]::OutputEncoding = $oldEncoding

Если вышеприведенное не имеет никакого эффекта, и ваша программа понимает UTF-8, но ожидает, что она будет без 3-байтовой спецификации, тогда вы можете попробовать удалить спецификацию из содержимого и передать результат вашей программе

(Get-Content 'input.txt' -Raw -Encoding UTF8) -replace '^\xef\xbb\xbf' |  my-program args

Если вы когда-нибудь «взломали» кодовую страницу с помощью chcp 65001, я рекомендую вернуться к chcp 5129 для Engli sh - Новая Зеландия. Смотри здесь .

...