Вывод команды печатается вместо записи в Powershell - PullRequest
0 голосов
/ 30 августа 2018

В общем, я знаю, как записать вывод команды в переменную в 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 Ответ

0 голосов
/ 30 августа 2018

Это вызвано 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 в одну.

    • Если вы используете эти экземпляры только в контексте string - например, "Version: $output", вы можете не заметить или не позаботиться об этом.

  • .ToString() (для одной выходной линии) и .ForEach('ToString') / (...) | % ToString (для потенциально нескольких выходных линий) отменяют оба побочных эффекта:

    • Они преобразуют [System.Management.Automation.ErrorRecord] экземпляры обратно в строки .

    • Они сбрасывают $? на $True, потому что .ToString() / .ForEach() вызовы / использование (...) вокруг команды являются выражениями , которые сами по себе считаются успешными , даже если они содержат команды , которые сами устанавливают $? в $False.

Примечание:

  • Если не удается найти исполняемый файл python, PowerShell сам выдаст ошибку завершения оператора, что означает, что присвоение $output будет пропущено (его значение, если оно существует) , не изменится), и по умолчанию вы получите сообщение об ошибке с шумом, и $? будет установлен в $False.

  • Если python может быть вызван и it сообщает об ошибке (что маловероятно при --version), как указано с помощью ненулевого кода выхода , этот код выхода можно проверить с помощью автоматической переменной $LASTEXITCODE (значение которой не изменится до следующего вызова внешней программы ).

  • То, что простое использование (...) вокруг команды заставляет $? неизменно возвращать $True (кроме случаев, когда возникает ошибка завершения оператора или скрипта), используется в вышеупомянутых подходах, но обычно неясное поведение, которое можно считать проблематичным - см. это обсуждение на GitHub .

...