Почему ErrorLevel устанавливается только после || оператора при неудачном перенаправлении?
После неудачного перенаправления (из-за несуществующего файла или недостаточного доступа к файлу) значение ErrorLevel
, похоже, не установлено (в следующих примерах файл test.tmp
защищен от записи и файл test.nil
не существует):
>>> (call ) & rem // (reset `ErrorLevel`)
>>> > "test.tmp" echo Text
Access is denied.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
>>> (call ) & rem // (reset `ErrorLevel`)
>>> < "test.nil" set /P DUMMY=""
The system cannot find the file specified.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
Однако, как только неудачное перенаправление сопровождается оператором условной конкатенации ||
, который запрашивает код выхода, ErrorLevel
устанавливается на 1
, неожиданно:
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) || echo Fail
Access is denied.
Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") || echo Fail
The system cannot find the file specified.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
Интересно, что ErrorLevel
остается 0
, когда используется оператор &&
:
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) && echo Pass
Access is denied.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") && echo Pass
The system cannot find the file specified.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
ErrorLevel
остается также 0
с помощью оператора &
:
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) & echo Pass or Fail
Access is denied.
Pass or Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") & echo Pass or Fail
The system cannot find the file specified.
Pass or Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
В случае появления операторов условной конкатенации &&
и ||
, ErrorLevel
также устанавливается на 1
(если ||
встречается до &&
, обе ветки выполняются как в последнем примере, но Я думаю, это просто потому, что &&
оценивает код выхода предыдущей команды echo
):
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) && echo Pass || echo Fail
Access is denied.
Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") || echo Fail && echo Pass
The system cannot find the file specified.
Fail
Pass
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
Итак, какова связь между значением ErrorLevel
и оператором ||
, почему ErrorLevel
влияет на ||
? Является ли ||
копирование кода выхода на ErrorLevel
? Возможно ли это только при перенаправлении (неудачных), поскольку они обрабатываются до того, как будут выполнены какие-либо команды?
Еще более странно, я не мог наблюдать обратное поведение - ErrorLevel
0
&&
- при правильном возврате тестовой настройки (то есть замене (call )
на (call)
(сначала установить ErrorLevel
в 1
), очищая атрибут только для чтения файла test.tmp
, создавая файл test.nil
(первая строка не пуста, чтобы избежать set /P
, чтобы установить ErrorLevel
в 1
) и используя расширение файла .bat
, а не .cmd
для тестирования (чтобы избежать set /P
до reset ErrorLevel
до 0
)).
Я наблюдал описанное поведение в Windows 7 и Windows 10.
Ответы
Ответ 1
Я впервые обнаружил это нелогичное поведение почти 5 лет назад в Перенаправление файлов в Windows и% errorlevel%. Через два месяца я обнаружил ту же проблему с командой RD (RMDIR) в batch: Exit code for "rd" равно 0 при ошибке. Заголовок этого последнего вопроса фактически вводит в заблуждение, потому что код возврата неудавшегося RD не равен нулю, но ERRORLEVEL не изменяется от любого значения, существовавшего до выполнения команды. Если код возврата был действительно 0, тогда оператор ||
не срабатывал.
Все это возможно только при перенаправлении (неудачных), поскольку такие обрабатывается до выполнения каких-либо команд?
Вы правы, что перенаправление не выполняется до выполнения команды. И ||
отвечает на ненулевой код возврата операции перенаправления. Команда (ECHO в вашем случае) никогда не выполняется, если перенаправление не работает.
Итак, какова связь между значением ErrorLevel и || оператор, почему ErrorLevel влияет на ||? Является ли || копирование выхода код в ErrorLevel?
Есть два разных значения, связанные с ошибкой, которые необходимо отслеживать - 1) любой код команды (или операции) возврата (код выхода) и 2) ERRORLEVEL. Коды возврата являются переходными - их необходимо проверять после каждой операции. ERRORLEVEL - это способ cmd.exe для сохранения "важных" состояний ошибки с течением времени. Цель состоит в том, чтобы все ошибки были обнаружены, и ERRORLEVEL должен быть установлен соответствующим образом. Но ERRORLEVEL был бы бесполезным для пакетных разработчиков, если бы он всегда очищался до 0 после каждой успешной операции. Поэтому разработчики cmd.exe пытались сделать логический выбор, когда успешная команда очищает ERRORLEVEL и сохраняет прежнее значение. Я не уверен, насколько мудрым они были в их выборе, но я попытался документировать правила в Какие внутренние команды cmd.exe очищают ERRORLEVEL до 0 при успешном завершении?.
В остальной части этого раздела есть обоснованная гипотеза. Я не думаю, что окончательный ответ возможен без общения с оригинальными разработчиками cmd.exe. Но это то, что дает мне ментальные рамки для успешного преодоления болота поведения ошибки cmd.exe.
Я считаю, что везде, где может произойти ошибка в cmd.exe, разработчики должны были обнаружить код возврата, установите ERRORLEVEL в ненулевое значение при ошибке, а затем запустите любой код ||
, если он находится в игре. Но в некоторых случаях разработчик ввел ошибку, не играя по правилам. После неудачного перенаправления или неудачного RD разработчик успешно вызвал код ||
, но не смог правильно установить ERRORLEVEL.
Я также считаю, что разработчик (s) ||
сделал некоторое защитное программирование. ERRORLEVEL уже должен быть установлен на ненулевое значение до того, как будет выполнен код ||
. Но я думаю, что разработчик ||
мудро не доверял своим сверстникам и решил установить ERRORLEVEL в обработчике ||
.
Что касается того, что используется ненулевое значение, представляется логичным, что ||
пересылает исходное значение кода возврата в ERRORLEVEL. Это означало бы, что исходный код возврата должен храниться в некоторой временной области хранения, отличной от ERRORLEVEL. У меня есть два доказательства, подтверждающих эту теорию:
1) Оператор ||
устанавливает не менее 4 различных значений ERRORLEVEL при сбое RD в зависимости от типа ошибки.
2) ERRORLEVEL, установленный ||
, является тем же значением, что и CMD/C, и CMD/C просто пересылает код возврата последней команды/операции.
C:\test>(call )&rd .
The process cannot access the file because it is being used by another process.
C:\test>echo %errorlevel%
0
C:\test>(call )&rd . || rem
The process cannot access the file because it is being used by another process.
C:\test>echo %errorlevel%
32
C:\test>(call )&cmd /c rd .
The process cannot access the file because it is being used by another process.
C:\test>echo %errorlevel%
32
Однако есть одна особенность, которая угрожает аннулировать эту теорию. Если вы попытаетесь запустить несуществующую команду, вы получите ошибку 9009:
C:\test>invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:\test>echo %errorlevel%
9009
Но если вы используете оператор ||
или CMD/C, то ERRORLEVEL равен 1: -/
C:\test>invalidCommand || rem
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:\test>echo %errorlevel%
1
C:\test>(call )
C:\test>cmd /c invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:\test>echo %errorlevel%
1
Я разрешаю эту аномалию в своем уме, предполагая, что код, ответственный за установку 9009 ERRORLEVEL в отсутствии ||
, должен выполнять некоторый тип чувствительного к контексту перевода для генерации 9009. Но обработчик ||
не знает перевода, поэтому он просто перенаправляет код исходного кода в ERRORLEVEL, перезаписывая значение 9009, которое уже существует.
Мне неизвестны какие-либо другие команды, которые дают разные ненулевые значения ERRORLEVEL в зависимости от того, использовался ли ||
или нет.
Еще более странно, я не мог наблюдать противоположное поведение - ErrorLevel reset to 0 by && & - при правильном возврате (т.е. замена (вызов) на (вызов) (для установки параметра ErrorLevel на 1 изначально), очищая атрибут только для чтения файла test.tmp, создание файла test.nil(первая строка не пуста, чтобы избежать установки /P для установки ErrorLevel до 1) и использование расширения файла .bat, а не .cmd для тестирование (чтобы избежать установки /P до reset ErrorLevel до 0)).
Как только вы признаете, что не все команды очищают ERRORLEVEL после успеха, это поведение имеет смысл. Было бы неплохо сохранить предыдущие ошибки, если &&
должен был стереть сохраненную ошибку.
Ответ 2
note. Все содержание в этом ответе - это просто личная интерпретация символов кода ассемблера/отладки cmd.exe
, вывод исходного кода, который генерирует выход ассемблера. Весь код в этом ответе - это всего лишь псевдокод, который примерно отражает то, что происходит внутри cmd.exe
, показывая только те части, которые относятся к вопросу.
Первое, что нам нужно знать, это то, что значение errorlevel
извлекается из внутренней переменной _LastRetCode
(по крайней мере, это имя в информационных символах отладки) из функции GetEnvVar
.
Второе, что нужно знать, состоит в том, что большинство команд cmd
связаны с набором функций. Эти функции изменяют (или не) значение в _LastRetCode
, но также возвращают код sucess/failure (a 0/1
), который используется внутри, чтобы определить, есть ли ошибка.
В случае команды echo
функция eEcho
обрабатывает выходные функции, закодированные как-то вроде
eEcho( x ){
....
// Code to echo the required value
....
return 0
}
То есть команда echo
имеет только одну точку выхода и не устанавливает/не очищает переменную _LastRetCode
(она не изменит значение errorlevel
) и всегда будет возвращать код sucess. echo
не сбой (с точки зрения партии, он может выйти из строя и записать на stderr
, но он всегда будет возвращать 0
и никогда не изменится _LastRetCode
).
Но как называется эта функция?, как создается перенаправление?
Существует функция Dispatch
, которая определяет команду/функцию для вызова и которая ранее вызывает функцию SetDir
(если необходимо) для создания необходимых перенаправлений. Как только перенаправление установлено, функция GetFuncPtr
получает адрес исполняемой функции (функция, связанная с командой cmd
) вызывает ее и возвращает ее результат вызывающему.
Dispatch( x, x ){
....
if (redirectionNeeded){
ret = SetRedir(...)
if (ret != 0) return 1
}
....
func = GetFuncPtr(...)
ret = func(...)
return ret
}
В то время как возвращаемые значения этих функций отражают наличие ошибки (они возвращают 1
при сбое, 0
при успешном завершении), а Dispatch
а не SetDir
изменяют переменную _LastRetCode
(while не показаны здесь, ни одна из них не ссылается на переменную), поэтому при переходе переназначения нет errorlevel
.
Что меняется при использовании оператора ||
?
Оператор ||
обрабатывается внутри функции eOr
, которая закодирована, да, снова более или менее
eOr( x ){
ret = Dispatch( leftCommand )
if (ret == 0) return 0
_LastRetCode = ret
ret = Dispatch( rightCommand )
return ret
}
Сначала выполняется команда слева. Если он не возвращает ошибку, ничего не остается делать и выходит из значения sucess. Но если левая команда не работает, она сохраняет возвращаемое значение внутри переменной _LastRetCode
перед вызовом команды с правой стороны. В случае в вопросе
(> "test.tmp" echo Text) || echo Fail
Выполнение
-
Dispatch
(который вызывается из функций, обрабатывающих пакетное выполнение и принимающих в качестве параметра выполнение) вызывает eOr
-
eOr
вызывает Dispatch
для выполнения eEcho
(левая часть оператора)
-
Dispatch
вызывает SetDir
, чтобы создать перенаправление
-
SetDir
сбой и возвращает 1
(никаких изменений в _LastRetCode
)
-
Dispatch
возвращает 1
, поскольку SetDir
потерпел неудачу
-
eOr
проверяет возвращаемое значение и, поскольку оно не 0
, возвращаемое значение сохраняется в _LastRetCode
. Теперь _LastRetCode
есть 1
-
eOr
вызывает Dispatch
для выполнения eEcho
(справа от оператора)
-
Dispatch
вызывает GetFuncPtr
для получения адреса функции eEcho
.
-
Dispatch
вызывает возвращаемый указатель функции и возвращает возвращаемое значение (eEcho
всегда возвращает 0
)
-
eOr
возвращает возвращаемое значение из Dispatch
(0
)
Таким образом, возвращаемое значение ошибки (return 1
) при неудачной операции перенаправления сохраняется в _LastRetCode
при использовании оператора ||
.
Что происходит с функцией eAnd
, то есть с оператором &&
?
eAnd( x ){
ret = Dispatch( leftCommand )
if (ret != 0) return ret
ret = Dispatch( rightCommand )
return ret
}
Никаких изменений в _LastRetCode
нет, поэтому, если вызываемые команды не меняют эту переменную, никаких изменений в errorlevel
нет.
Пожалуйста, помните, что это просто интерпретация того, что я вижу, реальный код будет вести себя примерно одинаково, но он почти наверняка будет другим.