У меня есть сценарий PowerShell (ориентированный на 5.1), который при запуске с использованием PowerShell ISE в Windows 10 работает нормально, но в Windows 11 происходит сбой. Скрипт передает строковые литералы, например, в sqlite3.exe.
".show" | & .\sqlite3.exe db
Успешный вывод в Windows 10:
echo: off eqp: off explain: auto headers: off mode: list nullvalue: "" output: stdout colseparator: "|" rowseparator: "\n" stats: off width: filename: db
Вывод в Windows 11:
sqlite3.exe: ошибка синтаксического анализа рядом со строкой 1: рядом с «.»: синтаксическая ошибка At
строка:1 символ:11
+ ".шоу" | & .\sqlite3.exe db
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo: NotSpecified: (Ошибка анализа около...": синтаксическая ошибка:String) [], RemoteException + FullyQualifiedErrorId: NativeCommandError
.show
^--- здесь ошибка
Итак, из последних двух строк видно, что ISE добавляет, как я полагаю, метку порядка байтов в начало строки, которую sqlite3.exe не может обработать.
Я понимаю, что очевидный вариант — сказать пользователям, чтобы они просто не запускали его в ISE, но мне было интересно, есть ли какой-нибудь способ настроить ISE или переписать сценарий, чтобы избежать ошибки?
@iRon, спасибо. К сожалению, ISE является приложением по умолчанию в Windows 11 для открытия файлов .ps1 (в образе, который все равно использует наш ИТ-специалист), поэтому я бы предпочел, чтобы оно «просто работало», если это возможно.
Я не знаком с внешней командой .\sqlite3.exe
, но пробовали ли вы использовать одинарные кавычки и применять ее в качестве аргумента: что-то вроде: & .\sqlite3.exe db '.show'
.
& .\sqlite3.exe db '.show'
(одинарные или двойные кавычки) работает! Не знаю, почему я вообще сделал это по-трубному. В любом случае, если вы добавите это как ответ, я отмечу это как решение.
Команда отлично работает в стандартном окне PowerShell и командном окне Visual Studio Code, поэтому я думаю, что это ISE.
(Рад видеть, что проблема была решена для вас) «Если вы добавите это как ответ, я отмечу это как решение». Я просто стрелял от бедра, основываясь на некоторых общих передовых методах работы с конвейерами/внешними командами (строками) PowerShell, но у меня нет хорошего понимания фактической причины различного поведения, которое вы определили (как Windows 10/11 и т. д.). Поэтому я оставлю кому-нибудь другому написать адекватный ответ на этот вопрос...
вр; доктор
Либо: обойти проблему, передав .show
в качестве аргумента (а не через конвейер) в sqlite3.exe
, как предлагает iRon (обратите внимание, что я опускаю &
, вызывающий оператор, поскольку здесь он не нужен):
.\sqlite3.exe db .show
Или: Исправьте проблему с кодировкой символов, в простейшем случае, следующим образом, но учтите, что это работает только для текста, состоящего только из символов ASCII:
$OutputEncoding = [Console]::OutputEncoding
'.show' | .\sqlite3.exe db
Действительно, лучше избегать Windows PowerShell ISE (чтобы добавить к полезному комментарию iRon по этому вопросу):
Плохая поддержка вызова внешних программ, таких как sqlite3.exe
, является одной из причин избегать этого, и вы столкнулись с одним аспектом:
Необъяснимо, что привилегированная переменная $OutputEncoding по умолчанию имеет значение UTF-8 в Windows 11; в Windows 10 это была, что более разумно, активная кодировка системы ANSI, синхронизированная с [Console]::OutputEncoding
.
В обычной консоли Windows PowerShell по умолчанию используется ASCII(!), что не менее загадочно. В PowerShell 7 по умолчанию используется UTF-8 без спецификации, что, хотя и лучше из-за отсутствия спецификации, но также вызывает недоумение: см. следующий пункт.
Учитывая, что окна консоли (а также вкладки Windows Terminal и встроенный терминал Visual Studio Code (я не буду упоминать их подробно ниже), но не эмуляция консоли, встроенная в ISE) по умолчанию используют активную устаревшую кодовую страницу OEM системы, кодировка которой отражено в [Console]::OutputEncoding
(который PowerShell использует для декодирования вывода внешней программы), имеет смысл по умолчанию $OutputEncoding
использовать ту же кодовую страницу (кодировку).
Также необъяснимо то, что ISE по умолчанию использует кодовую страницу ANSI, что приводит к различному поведению по умолчанию между ним и окнами консоли.
chcp
для проверки), так что любые символы, отличные от ASCII, в выходных данных внешней программы будут неправильно интерпретированы PowerShell в ISE.Что еще хуже, $OutputEncoding
в ISE - неуместно - кодировка UTF-8 со спецификацией, что и вызывает вашу проблему - см. ниже.
Поскольку $OutputEncoding
по умолчанию представляет собой кодировку UTF-8 со спецификацией в ISE, любые данные, которые вы отправляете через конвейер во внешнюю программу, будут иметь добавленную спецификацию - чего НЕ ожидают большинство внешних программ, включая sqlite3.exe
, что заставляет их интерпретировать 3 байты, составляющие спецификацию как часть текста.
Поскольку [Console]::OutputEncoding
в ISE является активной кодовой страницей ANSI, и PowerShell использует ее для декодирования вывода stdout и stderr из внешних программ, он неправильно декодирует спецификацию UTF-8, содержащуюся в выводе stderr sqlite3.exe
перед словом .show
(sqlite3.exe
предполагает фиксированная кодировка UTF-8, но не распознает спецификацию): Спецификация UTF-8 состоит из 3 байтов, 0xef
, 0xbb
, 0xbf
, которые неправильно декодируются как строка 
(т. е. 3 символа), если активен ANSI. кодовая страница: Windows-1252, например, в американско-английском языке.[1]
Варианты решения:
Прежде чем звонить sqlite3.exe
, выполните одно из следующих действий:
Совместите $OutputEncoding
с [Console]::OutputEncoding
, что отправит текст в той же кодировке ANSI, которую ожидают хорошо работающие внешние консольные программы - однако из-за несоответствия между кодировкой ANSI, которую использует ISE, и кодировкой OEM, используемой внешними программами, это решение эффективно только если входной текст, а также выходные данные внешней программы состоят только из символов диапазона ASCII или если целевая программа использует активную кодовую страницу ANSI, как, например, python
. (Это фактически восстанавливает поведение Windows 10.)
# Effective only for all-ASCII-characters input and output text.
$OutputEncoding = [Console]::OutputEncoding
Переключите сеанс на последовательное использование UTF-8 без спецификации, что обеспечивает полную поддержку Unicode как на входной, так и на выходной стороне:
chcp >$null # dummy command that forces allocation of a console
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
Обратите внимание на необходимость выполнения фиктивной команды, вызывающей консольное приложение (chcp
), чтобы гарантировать, что ISE незаметно выделил консоль; без этого присвоение [Console]::InputEncoding
или [Console]::OutputEncoding
вызовет ошибку.
(Кроме того, прямое использование chcp 65001
для изменения активной кодовой страницы на UTF-8 не сработает, поскольку .NET кэширует кодировки, хранящиеся в [Console]::InputEncoding
и [Console]::OutputEncoding
.)
См. этот ответ для получения дополнительной информации.
[1] You can verify this as follows: [System.Text.Encoding]::GetEncoding(1252).GetString( [System.Text.UTF8Encoding]::new($true).GetPreamble())
В голландском языке есть поговорка: «de klok hebben horen luiden maar niet weten waar de klepel Hangt» , которая в данном случае очень применима ко мне. @saviourofdp, я думаю, вы получили полный ответ здесь, и я рекомендую принять это.
Спасибо, @iRon. Я понял, что упустил еще один проблемный аспект ISE, из-за чего быстрое исправление $OutputEncoding = [Console]::OutputEncoding
эффективно только для символов диапазона ASCII: ISE по умолчанию использует ANSI, тогда как все внешние программы по умолчанию используют OEM. Я обновил ответ соответственно.
@mklement0 отличный ответ, спасибо
Windows 10 22H2 ISE:
$outputencoding
IsSingleByte : True
BodyName : iso-8859-1
EncodingName : Western European (Windows)
HeaderName : Windows-1252
WebName : Windows-1252
WindowsCodePage : 1252
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
EncoderFallback : System.Text.InternalEncoderBestFitFallback
DecoderFallback : System.Text.InternalDecoderBestFitFallback
IsReadOnly : True
CodePage : 1252
Windows 11 22H2 ИСЕ:
$outputencoding
BodyName : utf-8
EncodingName : Unicode (UTF-8)
HeaderName : utf-8
WebName : utf-8
WindowsCodePage : 1200
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.EncoderReplacementFallback
DecoderFallback : System.Text.DecoderReplacementFallback
IsReadOnly : True
CodePage : 65001
Один из способов установить старую кодировку:
$outputencoding = [text.encoding]::default
'.show' | .\sqlite3.exe file1.sql
echo: off
eqp: off
explain: auto
headers: off
mode: list
nullvalue: ""
output: stdout
colseparator: "|"
rowseparator: "\n"
stats: off
width:
filename: file1.sql
Кодировка utf8, похоже, не мешает передаче внешних команд, таких как findstr или find...
'.show' | findstr /l .show
.show
'.show' | find '".show"'
.show
'hi' | findstr ^...hi$ # allow for BOM chars
hi
очень полезно, спасибо
Хорошо иметь подтверждение того, что поведение изменилось между Windows 10 и 11. findstr
и find
ищите подстроки, чтобы наличие посторонней спецификации не было проблемой; обратите внимание, что он там есть, как показывает следующая команда (при запуске в ISE на W11): ` '.show' | cmd/c 'findstr/l.show >out.txt'; Формат-Hex out.txt`. Любопытно, однако, что PowerShell, похоже, удаляет его при захвате вывода внешней программы.