Вывод команды печатается вместо записи в 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.

Другие вопросы по тегам