Sair do arquivo em lote da sub-rotina

20

Como posso sair de um arquivo em lotes de dentro de uma sub-rotina?

Se eu usar o comando EXIT, simplesmente retornarei à linha em que chamei a sub-rotina e a execução continuará.

Aqui está um exemplo:

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

:QUIT
EXIT /B 1

:END
EXIT /B 0

Resultado:

Quitting...
Still here!

Atualizar:

Esta não é uma resposta adequada, mas acabei fazendo algo como:

@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

A declaração de canal duplo de:

CALL :SUBROUTINE_WITH_ERROR || GOTO HANDLE_FAIL

é uma abreviação de:

CALL :SUBROUTINE_WITH_ERROR 
IF ERRORLEVEL 1 GOTO HANDLE_FAIL    

Eu ainda adoraria saber se há uma maneira de sair diretamente de uma sub-rotina, em vez de ter que fazer o LIGADOR lidar com a situação, mas isso pelo menos faz o trabalho.


Atualização # 2: Ao chamar uma sub-rotina de dentro de outra sub-rotina, chamada da maneira acima, chamo de dentro das sub-rotinas da seguinte maneira:

CALL :SUBROUTINE_WITH_ERROR || EXIT /B 1

Dessa forma, o erro se propaga de volta para o "principal", por assim dizer. A parte principal do lote pode lidar com o erro com o manipulador de erros GOTO: FAILURE

Brown
fonte

Respostas:

21

Adicione isso ao topo do seu arquivo em lotes:

@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
)

Então você pode simplesmente ligar para:

  • EXIT [errorLevel] se você quiser sair do arquivo inteiro
  • EXIT /B [errorLevel] para sair da sub-rotina atual
  • GOTO :EOF para sair da sub-rotina atual
Merlyn Morgan-Graham
fonte
+1 para realmente mencionarGOTO :EOF
afrazier
11
Muito agradável. Fiz uma pequena modificação, atribuir %~0à variável em vez de true: if not "%selfwrapped%"=="%~0" ( set selfwrapped=%~0 .... ). Dessa forma, você pode usar o mesmo truque em vários scripts em lote que se chamam.
GolezTrol
Esta é uma otima soluçao. Você acha que vale a pena editar para explicar como funciona? Levei um minuto para descompactar tudo isso e perceber que, na verdade, está apenas chamando o arquivo em lotes ( %~0) com todos os argumentos ( %*) de um cmd.exe aninhado, e /sé usado para controlar a maneira como o %ComSpec%argumento lida com aspas duplas ao redor do arquivo ligar.
Sean
@ Sean Acho que a brevidade é mais útil para a maioria das pessoas. Mais documentos não foram solicitados nos sete anos desde que eu o escrevi, então isso não parece estar em alta demanda. Eu também acho que há algum valor para as pessoas que pesquisam as coisas por si mesmas e não para enganar / fragmentar documentos. Mas talvez se mais algumas pessoas perguntarem, eu poderia adicionar algo. É também um CW para que você poderia propor uma edição muito
Merlyn Morgan-Graham
3

Que tal esse pequeno ajuste?

@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

Resultado:

Quitting...

Tecnicamente, isso não sai da sub-rotina. Em vez disso, simplesmente verifica o resultado da sub-rotina e executa ações a partir daí.

JMD
fonte
2
Obrigado, isso certamente faria o trabalho, e se eu não encontrar uma resposta melhor, é isso que terei que fazer. No entanto, prefiro não ter que colar essa linha após cada CHAMADA no meu longo e complexo arquivo em lotes.
Brown
1

Se você não quiser voltar do procedimento, não use call: use goto.

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

:QUIT
EXIT /B 1

:END
EXIT /B 0
dolmen
fonte
O objetivo da pergunta é como fazê-lo a partir de sub-rotinas (ou seja, usando chamada) para que isso não atenda.
Steve Guindaste
1

Coloquei o tratamento de erros nos meus arquivos em lotes. Você pode chamar manipuladores de erro como este:

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

E aqui está o final do arquivo em lote:

::-------------------------------------------------------------------
::  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)
djangofan
fonte
1

Isso sairá do contexto atual e um contexto pai (ou seja, quando executado dentro de um callscript de sub-rotina profunda, será encerrado):

(goto) 2>nul || exit /b

Ou, se você precisar do nível de erro 0:

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

Basicamente, (goto) 2>nuldefine o nível de erro como 1 (sem gerar um erro), retorna a execução ao contexto pai e o código após a execução de pipe duplo no contexto pai. type nul>nuldefine o nível de erro como 0.

UPD:

Para retornar a execução mais de duas vezes seguidas, encadeie vários (goto) 2>nul ||como este:

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

Aqui está uma sub-rotina recursiva para retornar o contexto várias vezes:

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

Quando chamado de uma função recursiva:

@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

a saída será:

1
2
3
4
5
This will be printed only once
SER HÍBRIDO
fonte