$LastExitCode=0, а $?= False в PowerShell. Перенаправление stderr в stdout дает NativeCommandError
почему Powershell показывает удивительное поведение во втором примере ниже?
во-первых, пример разумного поведения:
PS C:> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True
никаких сюрпризов. Я печатаю сообщение до стандартной ошибки (используя cmd
' s echo
). Я проверяю переменные $?
и $LastExitCode
. Как и ожидалось, они равны True и 0 соответственно.
однако, если я попрошу PowerShell перенаправить стандартную ошибку на стандартный вывод по первой команде, я получу NativeCommandError:
PS C:> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<< /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
+ CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
$LastExitCode=0 and $?=False
мой первый вопрос, почему NativeCommandError?
во-вторых, почему $?
False, когда cmd
успешно и $LastExitCode
равен 0? Документации в PowerShell об автоматических переменных явно не определяет $?
. Я всегда предполагал, что это правда, если и только если $LastExitCode
0, но мой пример противоречит этому.
вот как я столкнулся с этим поведением в реальном мире (упрощенном). Это действительно ФУБАР. Я вызывал один сценарий PowerShell из другого. Внутренний скрипт:
cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
echo "Job failed. Sending email.."
exit 1
}
# Do something else
работает это просто как .job.ps1
, он работает нормально, и нет электронной почты. Тем не менее, я вызывал его из другого сценария PowerShell, войдя в файл .job.ps1 2>&1 > log.txt
. В этом случае отправляется электронное письмо! То, что вы делаете вне скрипта с потоком ошибок, влияет на внутреннее поведение скрипта. наблюдение за явлением меняет результат. это похоже на quantum физика, а не сценарии!
[интересно: .job.ps1 2>&1
может или не взорвать в зависимости от того, где вы запустите его]
4 ответов
(я использую PowerShell v2.)
в '$?
' переменная задокументирована в about_Automatic_Variables
:
$? Contains the execution status of the last operation
это относится к самой последней операции PowerShell, в отличие от последней внешней команды, которую вы получаете в $LastExitCode
.
в вашем примере $LastExitCode
равен 0, потому что последняя внешняя команда cmd
, который был успешных повторяем текст. Но ... --13--> причины сообщения stderr
чтобы быть преобразованы к записям ошибок в выходном потоке, который сообщает PowerShell, что во время последнего операция, причинив $?
на False
.
чтобы проиллюстрировать это немного больше, рассмотрим это:
> java -jar foo; $?; $LastExitCode Unable to access jarfile foo False 1
$LastExitCode
равно 1, потому что это был код выхода java.исполняемый. $?
является ложным, потому что последнее, что оболочка не удалось.
но если все, что я сделать, это включить их:
> java -jar foo; $LastExitCode; $? Unable to access jarfile foo 1 True
... тогда $?
is Верно, потому что последнее, что сделала оболочка, это напечатала $LastExitCode
хосту, который был успешным.
и наконец:
> &{ java -jar foo }; $?; $LastExitCode Unable to access jarfile foo True 1
...что кажется немного противоречивым, но $?
и правда теперь, потому что выполнение блока сценария было успешных, даже если команда запуска внутри него не была.
возвращение в 2>&1
редирект.... это приводит к записи об ошибке в выходной поток, который что дает неторопливый клякса о NativeCommandError
. Раковина сброса все ошибки.
это может быть особенно раздражает, когда все вы хотите сделать, это труба stderr
и stdout
вместе, чтобы их можно было объединить в файл журнала или что-то еще. Кто хочет, чтобы PowerShell вмешивался в их файл журнала??? Если я это сделаю ant build 2>&1 >build.log
, затем любые ошибки, которые идут в stderr
есть PowerShell любопытный $0.02, вместо того, чтобы получать чистые сообщения об ошибках в моем журнал.
но выходной поток не является текст поток! Перенаправления просто другой синтаксис объект. Записи об ошибках являются объектами, поэтому все, что вам нужно сделать, это преобразовать объекты в этом потоке в строки до переадресации:
от:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 cmd.exe : Hello from standard error At line:1 char:4 + cmd &2" 2>&1 + CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError
в:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } Hello from standard error
...и с перенаправлением на файл:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt Hello from standard error
...или просто:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
эта ошибка является непредвиденным следствием предписывающего дизайна PowerShell для обработки ошибок, поэтому, скорее всего, она никогда не будет исправлена. Если ваш сценарий воспроизводится только с другими сценариями PowerShell, вы в безопасности. Однако, если ваш скрипт взаимодействует с приложениями из большого мира, эта ошибка может укусить.
PS> nslookup microsoft.com 2>&1 ; echo $?
False
попался! Тем не менее, после некоторых болезненных царапин, вы никогда не забудете урок.
использовать ($LastExitCode -eq 0)
вместо $?
(Примечание: это в основном предположения; я редко использую много собственных команд в PowerShell, и другие, вероятно, знают больше о внутренних устройствах PowerShell, чем я)
Я думаю, вы нашли несоответствие в Хосте консоли PowerShell.
- если PowerShell подбирает материал в стандартном потоке ошибок, если допустит ошибку и бросит
NativeCommandError
. - PowerShell может только забрать это, если это мониторы Стандартная ошибка поток.
- PowerShell ISE до контролируйте его, потому что это не консольное приложение, и, следовательно, собственное консольное приложение не имеет консоли для записи. Вот почему в PowerShell ISE это не удается независимо от
2>&1
оператор перенаправления. - хост консоли будет монитор стандартный поток ошибок, если вы используете
2>&1
оператор перенаправления, потому что вывод в стандартном потоке ошибок должен быть перенаправлен и таким образом читать.
Я предполагаю, что узел консоли PowerShell ленив и просто передает собственные консольные команды консоли, если ему не нужно выполнять какую-либо обработку на их выходе.
Я действительно считаю, что это ошибка, потому что PowerShell ведет себя по-разному в зависимости от хост-приложения.
для меня это была проблема для. При запуске из ISE я установил $ErrorActionPreference = "Stop" в первых строках, и это перехватывало все событие с *>&1, добавленным в качестве параметров к вызову.
Итак, сначала у меня была эта строка:
& $exe $parameters *>&1
который, как я уже сказал, не работал, потому что у меня был $ErrorActionPreference = "Stop" ранее в файле (или его можно установить глобально в профиле для запуска скрипта пользователем).
поэтому я попытался оберните его в Invoke-Expression, чтобы заставить ErrorAction:
Invoke-Expression -Command "& `"$exe`" $parameters *>&1" -ErrorAction Continue
и это тоже не работает.
поэтому мне пришлось отступить, чтобы взломать временный переопределяющий ErrorActionPreference:
$old_error_action_preference = $ErrorActionPreference
try
{
$ErrorActionPreference = "Continue"
& $exe $parameters *>&1
}
finally
{
$ErrorActionPreference = $old_error_action_preference
}
, который работает для меня.
и я обернул это в функцию:
<#
.SYNOPSIS
Executes native executable in specified directory (if specified)
and optionally overriding global $ErrorActionPreference.
#>
function Start-NativeExecutable
{
[CmdletBinding(SupportsShouldProcess = $true)]
Param
(
[Parameter (Mandatory = $true, Position = 0, ValueFromPipelinebyPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string] $Path,
[Parameter (Mandatory = $false, Position = 1, ValueFromPipelinebyPropertyName=$True)]
[string] $Parameters,
[Parameter (Mandatory = $false, Position = 2, ValueFromPipelinebyPropertyName=$True)]
[string] $WorkingDirectory,
[Parameter (Mandatory = $false, Position = 3, ValueFromPipelinebyPropertyName=$True)]
[string] $GlobalErrorActionPreference,
[Parameter (Mandatory = $false, Position = 4, ValueFromPipelinebyPropertyName=$True)]
[switch] $RedirectAllOutput
)
if ($WorkingDirectory)
{
$old_work_dir = Resolve-Path .
cd $WorkingDirectory
}
if ($GlobalErrorActionPreference)
{
$old_error_action_preference = $ErrorActionPreference
$ErrorActionPreference = $GlobalErrorActionPreference
}
try
{
Write-Verbose "& $Path $Parameters"
if ($RedirectAllOutput)
{ & $Path $Parameters *>&1 }
else
{ & $Path $Parameters }
}
finally
{
if ($WorkingDirectory)
{ cd $old_work_dir }
if ($GlobalErrorActionPreference)
{ $ErrorActionPreference = $old_error_action_preference }
}
}