Вывод команды печатается вместо записи в Powershell
В общем, я знаю, как записать вывод команды в переменную в Powershell:
> $output = python3 --version
> Write-Host $output
Python 3.7.0
Python 3 печатает свою версию, но записывается в переменную $output
и ничего не отображается на консоли. Как и ожидалось:
> $output = python3 --version
> Write-Host $output
Python 3.7.0
Но в случае с Python 2 это не работает. Python 2 печатает свою версию, но она отображается на консоли вместо захваченной и переменной $output
пустой.
> $output = python2 --version
Python 2.7.15
> Write-Host $output
>
Я пробовал несколько альтернативных синтаксисов, но пока ничего не помогло:
> $output = (python2 --version) # use a subcommand
Python 2.7.15
> $output = "$(python2 --version)" # enclose the subcommand into a string
Python 2.7.15
Если я использую это в Write-Command
результат выглядит следующим образом:
> Write-Host "Python version: $(python2 --version)"
Python 2.7.15
Python version:
>
Как Python 2 печатает свою версию, что она не записана в переменную, и возможно ли ее захватить в любом случае?
1 ответ
Это вызвано Python 2, который печатает версию для stderr
вместо stdout
, Назначение выходных захватов вызова программы stdout
только пустое в этом случае. На другой стороне, stderr
по умолчанию выводится на консоль, поэтому $output
переменная пуста, а версия выводится на консоль. Смотрите ниже для деталей.
ТЛ; др:
# Redirect stderr to the success output stream and convert to a string.
$output = (python --version 2>&1).ToString()
# $? is now $True if python could be invoked, and $False otherwise
Для команд, которые могут возвращать несколько строк stderr, используйте:
PSv4 +, используя
.ForEach()
метод:$output = (python --version 2>&1).ForEach('ToString')
PSv3-, используя
ForEach-Object
командлет (чей встроенный псевдоним%
):# Note: The (...) around the python call are required for # $? to have the expected value. $output = (python --version 2>&1) | % { $_.ToString() } # Shorter PSv3+ equivalent, using an operation statement $output = (python --version 2>&1) | % ToString
Как отмечает Mathias R. Jessen в комментарии к вопросу, и вы сами уточнили с точки зрения версий,
python
2.x - что удивительно - выводит информацию о своей версии в stderr, а не в stdout, в отличие от 3.x.Причина, по которой вы не смогли захватить результат с
$output = ...
является то, что назначение вывода внешнего вызова программы для переменной по умолчанию захватывает только вывод stdout.- С помощью встроенной команды PowerShell он захватывает поток вывода успешной команды - см. About_redirection.
Основное исправление заключается в использовании перенаправления
2>&1
объединить поток ошибок PowerShell (аналог PowerShell для stderr, которому перенаправляется вывод stderr, если он перенаправлен) в поток вывода успешных результатов PowerShell (аналог stdout), который затем записывается в переменную.
Это имеет два побочных эффекта, однако:Поскольку поток ошибок PowerShell теперь задействован из-за
2>&1
перенаправление (по умолчанию вывод stderr передается на консоль),$?
неизменно установлен в$False
потому что запись чего-либо в поток ошибок делает это (даже если поток ошибок в конечном итоге отправляется в другое место, как в этом случае).Обратите внимание, что
$?
не установлен в$False
без перенаправления (за исключением ISE, что является прискорбным расхождением), потому что вывод stderr тогда никогда не "касается" потока вывода ошибок PowerShell.
В консоли PowerShell при вызове внешней программы без2>
перенаправление,$?
установлен в$False
только если его код выхода не равен нулю ($LASTEXITCODE -ne 0
).Поэтому использование
2>&1
решает одну проблему (вывод невозможности захвата) при представлении другой ($?
неправильно установлен на$False
) - см. ниже лекарство.
Это не строки, которые записываются в поток вывода успеха, но
[System.Management.Automation.ErrorRecord]
экземпляры, потому что PowerShell упаковывает каждую строку вывода stderr в одну.Если вы используете эти экземпляры только в строковом контексте - например,
"Version: $output"
, вы можете не заметить или позаботиться, однако.
.ToString()
(для одной строки вывода) и.ForEach('ToString')
/(...) | % ToString
(для потенциально нескольких выходных линий) отменить оба побочных эффекта:Они преобразовывают
[System.Management.Automation.ErrorRecord]
экземпляры обратно в строки.Они сбрасывают
$?
в$True
, поскольку.ToString()
/.ForEach()
звонки / использование(...)
вокруг команды находятся выражения, которые сами по себе считаются успешными, даже если они содержат команды, которые сами устанавливают$?
в$False
,
Замечания:
Если исполняемый
python
не может быть найден, сама PowerShell выдаст ошибку завершения оператора, что означает, что присвоение$output
будет пропущен (его значение, если оно существует, не изменится), и по умолчанию вы получите шумный вывод ошибок и$?
будет установлен в$False
,Если
python
может быть вызван, и он сообщает об ошибке (что вряд ли--version
), как указано через ненулевой код выхода, вы можете проверить этот код выхода с помощью автоматического$LASTEXITCODE
переменная (значение которой не изменится до следующего вызова внешней программы).Это так просто, как положить
(...)
вокруг команды делает$?
неизменно возвращаются$True
(за исключением случаев, когда возникает ошибка завершения оператора или сценария) в вышеупомянутых подходах, но обычно это неясное поведение, которое может считаться проблематичным - см. это обсуждение на GitHub.