Пакетный переход теряет уровень ошибки
Рассмотрим следующую bat, test.bat (PC01 выключен):
mkdir \\PC01\\c$\Test || goto :eof
Если я запускаю эту летучую мышь из командной оболочки:
> test.bat || echo 99
> if ERRORLEVEL 1 echo 55
Выход только 55. Нет 99. Есть уровень ошибки, но ||
Оператор этого не видел.
Если я запускаю эту летучую мышь, используя cmd /c -
> cmd /c test.bat || echo 99
> if ERRORLEVEL 1 echo 55
Выход пуст. Уровень ошибки равен 0.
Если я удалю || goto :eof
все работает так, как можно было бы предсказать - т.е.
99 55
Кто-нибудь знает, почему происходит это полусухое полусуществующее поведение ERRORLEVEL?
3 ответа
В большинстве случаев ||
это самый надежный способ обнаружения ошибки. Но вы наткнулись на один из редких случаев, когда ERRORLEVEL работает, но ||
не.
Проблема связана с тем, что ваша ошибка возникает в пакетном скрипте, и ||
отвечает на код возврата последней выполненной команды. Вы думаете о test.bat как о единой "команде", но на самом деле это последовательность команд. Последняя команда, выполненная в скрипте GOTO :EOF
и это успешно выполнено. Так что ваши test.bat||echo 99
отвечает на успех GOTO :EOF
,
Когда вы удаляете ||GOTO :EOF
из сценария, то ваш test.bat||echo99
видит результат неудачного mkdir
, Но если бы вы должны были добавить REM
команда до конца test.bat, затем test.bat||echo 99
будет отвечать на успех REM
и ошибка будет замаскирована снова.
ERRORLEVEL по-прежнему ненулевой после test.bat||echo 99
потому что команды как GOTO
а также REM
не удаляйте любые предыдущие ненулевые ОШИБКИ при успехе. Это одно из многих доказательств того, что ERRORLEVEL и код возврата не совсем одно и то же. Это определенно сбивает с толку.
Вы можете рассматривать test.bat как команду модуля и получить желаемое поведение с помощью CALL.
C:\test>call test.bat && echo OK || echo FAIL
FAIL
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
Это работает, потому что CALL
Команда временно передает управление вызываемому скрипту. Когда сценарий завершается, управление возвращается CALL
команда, и он возвращает текущий ERRORLEVEL. Так ||echo 99
отвечает на ошибку, возвращаемую самой командой CALL, а не последней командой в скрипте.
Теперь для CMD /C
вопрос.
Код возврата, возвращаемый CMD /C
код возврата последней выполненной команды.
Это работает:
C:\test>cmd /c call test.bat && echo OK || echo FAIL
FAIL
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
так как CMD /C
возвращает ОШИБКУ, возвращенную CALL
заявление
Но это не удается полностью:
C:\test>cmd /c test.bat && echo OK || echo FAIL
OK
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
OK2
Без CALL
, CMD /C
возвращает код возврата последней выполненной команды, которая является GOTO :EOF
, CMD /C
также устанавливает ERRORLEVEL в тот же код возврата, так что теперь нет никаких свидетельств того, что когда-либо была ошибка в скрипте.
И по кроличьей норе мы идем
RLH в своем ответе и в своих комментариях к моему ответу обеспокоен тем, что ||
иногда очищает ОШИБКУ. Он предоставляет доказательства, которые, кажется, подтверждают его заключение. Но ситуация не так проста, и оказывается, что ||
это самый надежный (но все еще не идеальный) способ обнаружения ошибок.
Как я уже говорил ранее, код возврата, который все внешние команды возвращают при выходе, - это не то же самое, что cmd.exe ERRORLEVEL.
ERRORLEVEL - это состояние, поддерживаемое в самом сеансе cmd.exe, полностью отличное от кодов возврата.
Это даже задокументировано в определении exitCode в справке EXIT.
(help exit
или же exit /?
)
EXIT [/B] [exitCode]
/B specifies to exit the current batch script instead of
CMD.EXE. If executed from outside a batch script, it
will quit CMD.EXE
exitCode specifies a numeric number. if /B is specified, sets
ERRORLEVEL that number. If quitting CMD.EXE, sets the process
exit code with that number.
Когда CMD.EXE выполняет внешнюю команду, она обнаруживает код возврата исполняемого файла и устанавливает соответствие ERRORLEVEL. Обратите внимание, что это всего лишь соглашение, что 0 означает успех, а ненулевое значение означает ошибку. Некоторые внешние команды могут не следовать этому соглашению. Например, команда HELP (help.exe) не соответствует соглашению - она возвращает 0, если указана неверная команда, как в help bogus
, но возвращает 1, если вы запрашиваете помощь в действительной команде, как в help rem
,
||
оператор никогда не очищает ERRORLEVEL при выполнении внешней команды. Код завершения процесса обнаружен и срабатывает ||
если он не равен нулю, и ERRORLEVEL все равно будет соответствовать коду выхода. При этом команды, которые появляются после &&
и / или ||
может изменить ERRORLEVEL, поэтому нужно быть осторожным.
Но есть много других ситуаций, кроме внешних команд, где мы, разработчики, заботимся об успехах / неудачах и кодах возврата / ОШИБКАХ.
- выполнение внутренних команд
- операторы перенаправления
<
,>
, а также>>
- выполнение пакетных скриптов
- не удалось выполнить недопустимые команды
К сожалению, CMD.EXE совсем не соответствует тому, как он обрабатывает ошибки в этих ситуациях. CMD.EXE имеет несколько внутренних точек, где он должен обнаруживать ошибки, предположительно с помощью некоторой формы внутреннего кода возврата, который не обязательно является ERRORLEVEL, и в каждой из этих точек CMD.EXE может устанавливать ERRORLEVEL в зависимости от того, что он находит,
Для моих тестовых случаев ниже, обратите внимание, что (call )
, с пробелом, является тайным синтаксисом, который очищает ERRORLEVEL до 0 перед каждым тестом. Позже я также буду использовать (call)
без пробела, чтобы установить ERRORLEVEL в 1
Также обратите внимание, что отложенное расширение было включено в моей командной сессии с помощью
cmd /v: on
до запуска моих тестов
Подавляющее большинство внутренних команд устанавливает ERRORLEVEL в ненулевое значение при сбое, и условие ошибки также срабатывает ||
, ||
никогда не очищает и не изменяет ОШИБКУ в этих случаях. Вот пара примеров:
C:\test>(call ) & set /a 1/0
Divide by zero error.
C:\test>echo !errorlevel!
1073750993
C:\test>(call ) & type notExists
The system cannot find the file specified.
C:\test>echo !errorlevel!
1
C:\test>(call ) & set /a 1/0 && echo OK || echo ERROR !errorlevel!
Divide by zero error.
ERROR 1073750993
C:\test>(call ) & type notExists.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 1
Тогда есть по крайней мере одна команда, RD, (возможно, больше), а также операторы перенаправления, которые запускают ||
при ошибке, но не устанавливайте ERRORLEVEL, если только ||
используется.
C:\test>(call ) & rd notExists
The system cannot find the file specified.
C:\test>echo !errorlevel!
0
C:\test>(call ) & echo x >\badPath\out.txt
The system cannot find the path specified.
C:\test>echo !errorlevel!
0
C:\test>(call ) & rd notExists && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 2
C:\test>(call ) & echo x >\badPath\out.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the path specified.
ERROR 1
См. "Rd" для выходов с уровнем ошибки, установленным на 0, при ошибке при удалении, и т. Д., И Перенаправление файлов в Windows и% errorlevel% для получения дополнительной информации.
Мне известна одна внутренняя команда (может быть и другая) плюс базовые неудачные операции ввода-вывода, которые могут выдавать сообщения об ошибках в stderr, но они не запускаются ||
и при этом они не устанавливают ненулевой ОШИБКА.
Команда DEL может вывести ошибку, если файл доступен только для чтения или не существует, но не запускается ||
или установите ERRORLEVEL в ненулевое значение
C:\test>(call ) & del readOnlyFile
C:\test\readOnlyFile
Access is denied.
C:\test>echo !errorlevel!
0
C:\test>(call ) & del readOnlyFile & echo OK || echo ERROR !errorlevel!
C:\test\readOnlyFile
Access is denied.
OK
См. /questions/40487918/ne-udalos-udalit-otchet-v-fajl-v-paketnyih-fajlah-del/40487930#40487930 для получения дополнительной информации, связанной с ошибками DEL.
Во многом таким же образом, когда стандартный вывод был успешно перенаправлен в файл на устройстве USB, но затем устройство удаляется до того, как команда, такая как ECHO, пытается выполнить запись на устройство, тогда ECHO завершится ошибкой с сообщением об ошибке stderr., еще ||
не срабатывает, и ERRORLEVEL не установлен в ненулевое значение. См. http://www.dostips.com/forum/viewtopic.php?f=3&t=6881 для получения дополнительной информации.
Тогда у нас есть случай, когда выполняется пакетный скрипт - фактическая тема вопроса ОП. Без CALL
, ||
Оператор отвечает на последнюю команду, выполненную в скрипте. С CALL
, ||
Оператор отвечает на значение, возвращаемое CALL
команда, которая является окончательной ОШИБКОЙ, существующей после завершения пакета.
Наконец, у нас есть случай, когда RLH сообщает, что о неверной команде обычно сообщают как ERRORLEVEL 9009, но как ERRORLEVEL 1, если ||
используется.
C:\test>(call ) & InvalidCommand
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:\test>echo !errorlevel!
9009
C:\test>(call ) & InvalidCommand && echo OK || echo ERROR !errorlevel!
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
ERROR 1
Я не могу доказать это, но я подозреваю, что обнаружение сбоя команды и установка ERRORLEVEL в 9009 происходит очень поздно в процессе выполнения команды. Я предполагаю, что ||
перехватывает обнаружение ошибок до того, как будет установлен 9009, и в этот момент вместо него устанавливается 1. Так что я не думаю ||
очищает ошибку 9009, а скорее это альтернативный путь, по которому ошибка обрабатывается и устанавливается.
Альтернативный механизм для этого поведения состоит в том, что недопустимая команда всегда может установить для ERRORLEVEL значение 9009, но иметь другой код возврата, равный 1. ||
мог впоследствии обнаружить код возврата 1 и установить соответствие ERRORLEVEL, перезаписав таким образом 9009.
Несмотря на это, я не знаю ни о какой другой ситуации, когда ненулевой результат ERRORLEVEL отличается в зависимости от того, ||
был использован или нет.
Так что это заботится о том, что происходит, когда команда не выполняется. Но что делать, когда внутренняя команда преуспевает? К сожалению, CMD.EXE еще менее согласован, чем с ошибками. Он зависит от команды и может также зависеть от того, выполняется ли он из командной строки, из пакетного сценария с .bat
расширение или из пакетного сценария с .cmd
расширение.
Я основываю все обсуждение ниже на поведении Windows 10. Я сомневаюсь, что есть различия с более ранними версиями Windows, которые используют cmd.exe, но это возможно.
Следующие команды всегда сбрасывают ERRORLEVEL в 0 в случае успеха, независимо от контекста:
- CALL: Сбрасывает ERRORLEVEL, если команда CALLed не устанавливает его иначе.
Пример:call echo OK
- CD
- CHDIR
- ЦВЕТ
- COPY
- ДАТА
- DEL: всегда очищает ERRORLEVEL, даже если DEL не удается
- DIR
- ERASE: всегда очищает ERRORLEVEL, даже если ERASE завершается неудачей
- Мэриленд
- MKDIR
- MKLINK
- ПЕРЕЕХАТЬ
- PUSHD
- REN
- ПЕРЕИМЕНОВАТЬ
- SETLOCAL
- ВРЕМЯ
- ТИП
- VER
- ПРОВЕРКИ
- VOL
Следующий набор команд никогда не сбрасывает ERRORLEVEL в 0 при успешном завершении, независимо от контекста, но вместо этого сохраняет любое существующее ненулевое значение ERRORLEVEL:
- ПЕРЕРЫВ
- ЦБС
- ECHO
- ENDLOCAL
- ВЫХОД: Очевидно
EXIT /B 0
очищает ОШИБКУ, ноEXIT /B
без значения сохраняет прежнюю ОШИБКУ. - ЗА
- ИДТИ К
- ЕСЛИ
- КЛЮЧИ
- ПАУЗА
- POPD
- RD
- REM
- RMDIR
- СДВИГ
- НАЧНИТЕ
- ЗАГЛАВИЕ
И затем есть эти команды, которые не очищают ERRORLEVEL при успешном завершении, если они выполняются из командной строки или внутри сценария с .bat
расширение, но очистите ERRORLEVEL до 0, если он выпущен из сценария с .cmd
расширение. См. /questions/37874146/paketnyie-fajlyi-windows-bat-protivcmd/37874154#37874154 и https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J для получения дополнительной информации.
- ASSOC
- DPATH
- FTYPE
- ДОРОЖКА
- НЕЗАМЕДЛИТЕЛЬНЫЙ
- ЗАДАВАТЬ
Независимо от значения ERRORLEVEL, &&
Оператор определяет, была ли предыдущая команда успешной, и выполняет только последующие команды, если она была. &&
Оператор игнорирует значение ERRORLEVEL и никогда не изменяет его.
Вот два примера, которые показывают, что &&
всегда срабатывает, если предыдущая команда была успешной, даже если значение ERRORLEVEL не равно нулю. Команда CD является примером, где команда очищает любой предыдущий ERRORLEVEL, а команда ECHO является примером, где команда не очищает предыдущий ERRORLEVEL. Обратите внимание, я использую (call)
принудительно установить ERRORLEVEL в 1, прежде чем выполнить команду, которая будет выполнена успешно.
C:\TEST>(call)
C:\TEST>echo !errorlevel!
1
C:\test>(call) & cd \test
C:\test>echo !errorlevel!
0
C:\test>(call) & cd \test && echo OK !errorlevel! || echo ERROR !errorlevel!
OK 0
C:\test>(call) & echo Successful command
Successful command
C:\test>echo !errorlevel!
1
C:\test>(call) & echo Successful command && echo OK !errorlevel! || echo ERROR !errorlevel!
Successful command
OK 1
Во всех моих примерах кода для обнаружения ошибок я полагался на тот факт, что ECHO никогда не очищает ранее существующий ненулевой ERRORLEVEL. Но приведенный ниже скрипт является примером того, что может произойти, когда другие команды используются после &&
или же ||
,
@echo off
setlocal enableDelayedExpansion
(call)
echo ERRORLEVEL = !errorlevel!
(call) && echo OK !errorlevel! || echo ERROR !errorlevel!
(call) && (echo OK !errorlevel! & set "err=0") || (echo ERROR !errorlevel! & set "err=1" & echo ERROR !errorlevel!)
echo ERRORLEVEL = !errorlevel!
echo ERR = !ERR!
Вот вывод, когда скрипт имеет .bat
расширение:
C:\test>test.bat
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 1
ERRORLEVEL = 1
ERR = 1
И вот вывод, когда скрипт имеет .cmd
расширение:
C:\test>test.cmd
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 0
ERRORLEVEL = 0
ERR = 1
Помните, что каждая выполненная команда может изменить ERRORLEVEL. Так что даже если &&
а также ||
являются наиболее надежными способами обнаружения успеха или неудачи команд, нужно быть осторожным с тем, какие команды используются после этих операторов, если вам важно значение ERRORLEVEL.
И теперь пришло время вылезти из этой вонючей кроличьей норы и подышать свежим воздухом!
Итак, что мы узнали?
Не существует единого идеального способа определить, была ли какая-либо произвольная команда успешной или неудачной. Тем не мение, &&
а также ||
являются наиболее надежными методами выявления успеха и неудачи.
В общем, ни &&
ни ||
изменить ERRORLEVEL напрямую. Но есть несколько редких исключений.
||
правильно устанавливает ERRORLEVEL, который в противном случае был бы пропущен при сбое RD или перенаправления||
устанавливает другой ERRORLEVEL при неудачном выполнении недопустимой команды, тогда произойдет, если||
не использовались (1 против 9009).
В заключение, ||
не обнаруживает ненулевой ERRORLEVEL, возвращенный пакетным сценарием, как ошибку, если не была использована команда CALL.
Если вы полагаетесь строго на if errorlevel 1 ...
или же if %errorlevel% neq 0 ...
чтобы обнаружить ошибки, вы рискуете пропустить ошибки, которые могут вызвать RD и перенаправление (и другие?), и вы также рискуете ошибочно думать, что некоторые внутренние команды потерпели неудачу, хотя в действительности это может быть удержание от предыдущей команды, которая не удалось.
Построить решение для пакетных файлов, чтобы установить код возврата при использовании goto :eof
Вы можете немного изменить свой сценарий.
mkdir \\\failure || goto :EXIT
echo Only on Success
exit /b
:exit
(call)
Теперь вы можете использовать
test.bat || echo Failed
Единственным недостатком здесь является потеря уровня ошибки.
В этом случае код возврата устанавливается на false
но уровень ошибки всегда установлен на 1
, недействительным (call)
В настоящее время я не могу найти какой-либо возможный способ установить оба, уровень ошибки и код возврата для пользовательских значений
Истинный уровень ошибок не выдерживает оператор двойной трубы (при ошибке).
Вместо этого используйте логику. Например, ниже приведен один из способов сделать это:
mkdir \\PC01\c$\Test
if "%errorlevel%" == "0" (
echo success
) else (
echo failure
)
редактировать:
Если команда, которая предшествует этому, не является единственной командой, то для отслеживания уровня ошибки необходимо сделать больше. Например, если вы вызываете скрипт, то в этом скрипте вы должны управлять передачей уровня ошибки обратно в этот код.
редактировать 2:
Вот тестовые сценарии, где ERRORLEVEL должен быть '9009' и их вывод.
1) Нет неисправных трубопроводов, используется логика if.
setlocal enableDelayedExpansion
mybad.exe
if "!errorlevel!" == "0" (
echo success !errorlevel!
) else (
echo Errorlevel is now !errorlevel!
echo Errorlevel is now !errorlevel!
)
"mybad.exe" не распознается как внутренняя или внешняя команда, работающая программа или командный файл.
Уровень ошибок теперь 9009
Уровень ошибок теперь 9009
2) Отказ трубопровода, эхо
setlocal enableDelayedExpansion
mybad.exe || echo Errorlevel is now !errorlevel!
echo Errorlevel is now !errorlevel!
"mybad.exe" не распознается как внутренняя или внешняя команда, работающая программа или командный файл.
Errorlevel теперь 1
Errorlevel теперь 1
3) Отказ трубопроводов, переход
setlocal enableDelayedExpansion
mybad.exe || goto :fail
exit
:fail
echo Errorlevel is now !errorlevel!
"mybad.exe" не распознается как внутренняя или внешняя команда, работающая программа или командный файл.
Errorlevel теперь 1
Так что, если все, что вас волнует, это "Что-то" не удалось, тогда ОК. Код 1 так же хорош, как и все остальное. Но если вам нужно знать "что" не удалось, то вы не можете просто отказать трубе и позволить ей уничтожить фактический результат.