Выход из пакетного файла из подпрограммы


20

Как я могу выйти из командного файла из подпрограммы?

Если я использую команду EXIT, я просто возвращаюсь к строке, где я вызвал подпрограмму, и выполнение продолжается.

Вот пример:

@echo off
ECHO Quitting...
CALL :QUIT
ECHO Still here!
GOTO END

:QUIT
EXIT /B 1

:END
EXIT /B 0

Выход:

Quitting...
Still here!

Обновить:

Это неправильный ответ, но в итоге я сделал что-то вроде:

@echo off
CALL :SUBROUTINE_WITH_ERROR || GOTO HANDLE_FAIL
ECHO You shouldn't see this!
GOTO END

:SUBROUTINE_WITH_ERROR
ECHO Simulating failure...
EXIT /B 1

:HANDLE_FAIL
ECHO FAILURE!
EXIT /B 1

:END
ECHO NORMAL EXIT!
EXIT /B 0

Двухтрубный оператор:

CALL :SUBROUTINE_WITH_ERROR || GOTO HANDLE_FAIL

является сокращением для:

CALL :SUBROUTINE_WITH_ERROR 
IF ERRORLEVEL 1 GOTO HANDLE_FAIL    

Я все еще хотел бы знать, есть ли способ выйти непосредственно из подпрограммы, а не заставлять CALLER справиться с ситуацией, но это, по крайней мере, делает работу.


Обновление № 2: При вызове подпрограммы из другой подпрограммы, вызываемой вышеописанным способом, я вызываю подпрограмму таким образом:

CALL :SUBROUTINE_WITH_ERROR || EXIT /B 1

Таким образом, ошибка распространяется обратно до «основного», так сказать. Затем основная часть пакета может обработать ошибку с помощью обработчика ошибок GOTO: FAILURE

Ответы:


21

Добавьте это в начало вашего командного файла:

@ECHO OFF
SETLOCAL

IF "%selfWrapped%"=="" (
  REM this is necessary so that we can use "exit" to terminate the batch file,
  REM and all subroutines, but not the original cmd.exe
  SET selfWrapped=true
  %ComSpec% /s /c ""%~0" %*"
  GOTO :EOF
)

Тогда вы можете просто позвонить:

  • EXIT [errorLevel] если вы хотите выйти из всего файла
  • EXIT /B [errorLevel] выйти из текущей подпрограммы
  • GOTO :EOF выйти из текущей подпрограммы

+1 за фактическое упоминаниеGOTO :EOF
afrazier

1
Очень хорошо. Я сделал небольшую модификацию, присвоить %~0переменной вместо true: if not "%selfwrapped%"=="%~0" ( set selfwrapped=%~0 .... ). Таким образом, вы можете использовать один и тот же прием в нескольких пакетных сценариях, которые вызывают друг друга.
GolezTrol

Это отличное решение. Как вы думаете, стоит ли редактировать, чтобы объяснить, как это работает? Мне потребовалась минута, чтобы распаковать все это и понять, что на самом деле это просто вызов командного файла ( %~0) со всеми аргументами ( %*) из вложенного cmd.exe, и /sон используется для управления способом, которым %ComSpec%аргумент обрабатывает двойные кавычки вокруг вызов.
Шон

@Sean Я считаю, что краткость более полезна для большинства людей. За 7 лет, с тех пор как я его написал, больше документов не требовалось, поэтому, похоже, он не пользуется большим спросом. Я также думаю, что есть некоторая ценность для людей, которые ищут вещи сами, а не копируют / фрагментируют документы. Но, может быть, если еще несколько человек спросят, я мог бы что-то добавить. Это также CW, так что вы также можете предложить редактирование
Мерлин Морган-Грэм,

3

Как насчет этой незначительной корректировки?

@echo off
ECHO Quitting...
CALL :QUIT
:: The QUIT subroutine might have set the error code so let's take a look.
IF ERRORLEVEL 1 GOTO :EOF
ECHO Still here!
GOTO END

:QUIT
EXIT /B 1

:END
EXIT /B 0

Выход:

Quitting...

Технически это не выходит из подпрограммы. Скорее, он просто проверяет результат подпрограммы и предпринимает действия оттуда.


2
Спасибо, это, безусловно, сделало бы работу, и если я не смогу найти лучший ответ, это то, что я должен сделать. Однако я бы не стал вставлять эту строку после каждого CALL в моем длинном и сложном пакетном файле.
Браун

1

Если вы не хотите возвращаться из процедуры, не используйте call: вместо этого используйте goto.

@echo off
ECHO Quitting...
GOTO :QUIT
ECHO Will never be there!
GOTO END

:QUIT
EXIT /B 1

:END
EXIT /B 0

Суть вопроса в том, как сделать это из подпрограмм (то есть, используя call), чтобы на это не было ответа.
Стив Крейн

1

Я поместил обработку ошибок в мои командные файлы. Вы можете вызвать обработчики ошибок следующим образом:

CALL :WARNING "This is" "an important" "warning."

И вот конец командного файла:

::-------------------------------------------------------------------
::  Decisions
::-------------------------------------------------------------------
:INFO
IF "_DEBUG"=="true" (
  ECHO INFO: %~1
  IF NOT "%~2"=="" ECHO          %~2
  IF NOT "%~3"=="" ECHO          %~3
)
EXIT /B 0
:WARNING
ECHO WARNING: %~1
IF NOT "%~2"=="" ECHO          %~2
IF NOT "%~3"=="" ECHO          %~3
EXIT /B 0
:FAILURE
ECHO FAILURE: %~1
IF NOT "%~2"=="" ECHO          %~2
IF NOT "%~3"=="" ECHO          %~3
pause>nul
:END
ECHO Closing Server.bat script
FOR /l %%a in (5,-1,1) do (TITLE %TITLETEXT% -- closing in %%as&PING.exe -n 2 -w 1 127.0.0.1>nul)

1

Это выйдет из текущего контекста и родительского контекста (т.е. при выполнении внутри одного callскрипта глубокой подпрограммы выйдет):

(goto) 2>nul || exit /b

Или, если вам нужен уровень ошибки 0:

(goto) 2>nul || (
    type nul>nul
    exit /b
)

По сути, (goto) 2>nulустанавливает уровень ошибки равным 1 (без вывода ошибки), возвращает выполнение в родительский контекст и код после выполнения двойной трубы в родительском контексте. type nul>nulустанавливает уровень ошибки равным 0.

UPD:

Чтобы возвратить выполнение более двух раз подряд, соедините несколько цепочек (goto) 2>nul ||следующим образом:

(goto) 2>nul || (goto) 2>nul || (goto) 2>nul || (
    type nul>nul
    exit /b
)

Вот рекурсивная подпрограмма, которая возвращает контекст переменное число раз:

:Kill
(goto) 2>nul || (
    set /a depth=%1-1
    if %1 GEQ 1 (
        call:Kill !depth!
    )
    (goto) 2>nul || (type nul>nul)
)

Когда вызывается из рекурсивной функции:

@echo off
setlocal EnableDelayedExpansion
call:Recurs 5
echo This won't be printed
exit /b

:Recurs
set /a ri+=1
echo %ri%
if %ri% LSS %1 (
    call:Recurs %1
)
echo This will be printed only once
call:Kill %1
exit /b

вывод будет:

1
2
3
4
5
This will be printed only once
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.