批量限制 - 浏览菜单时的最大递归

时间:2012-08-11 19:00:48

标签: batch-processing batch-file

我在测试代码时遇到了一个大问题。这个问题很烦人...... “超出最大递归深度”。看看它是如何工作的:

@echo off

REM ---------- MAIN MENU ----------
:Main

echo 1.Supermarket
echo 2.Exit Batch

setlocal
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A"
set /p Menu=%BS% Type in your option {1,2} followed by ENTER:
if not '%Menu%'=='' set Menu=%Menu:~0,1%
if %Menu% EQU 1 ENDLOCAL & goto Super
if %Menu% EQU 2 goto EOF

REM ---------- SECONDARY MENU ----------
:Super

echo 1.Supermarket
echo   a.Apple
echo   b.Banana

setlocal
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A"
set /p Menu=%BS% Type in your option {1,a,b} followed by ENTER:
if not '%Menu%'=='' set Menu=%Menu:~0,1%
if %Menu% EQU 1 ENDLOCAL & goto Main
if %Menu% EQU a ENDLOCAL & goto App
if %Menu% EQU b ENDLOCAL & goto Ban

:App

 setlocal enabledelayedexpansion
 set /a cnt=0
 for /f "tokens=* delims= " %%a in (*.apple) do (set /a cnt+=1)
 if %cnt% EQU 0 (
 echo There are %cnt% apples & pause>nul & ENDLOCAL & goto Main
 ) else (
 echo There are %cnt% apples

 [... do many things and after 200 lines]

 echo The total number of apples was %cnt% & pause>nul & ENDLOCAL & goto Main

:Ban
echo This is Banana & pause>nul & goto Main

:EOF

假设用户连续遵循此序列:

1 - > a(第一次) - > 1 - > a(第二次) - > 1 - > a(第三次)等等 1 - > a(第八次) - > 1 - > a(ninith time),此时,您将获得“超出最大递归深度”。消息。如果你只是一直忽略这个消息,代码的内部计算将开始表现得很奇怪,甚至很难“显然”似乎已经产生了良好的结果

事实上,顺序并不重要。如果你在字母“a”和“b”之间或者“a”而不是“b”之间保持不同,则会发生同样的事情:

1 - > b(第一次) - > 1 - > a(第二次) - > 1 - > a(第三次)...... 1 - > a(第八次) - > 1 - > b(第九次)。再次“超出最大递归深度”。消息

此外,如果用户无情地输入选项1(总是只停留在MAIN MENU),则会在第12次试用后弹出错误消息。

如何保持与上面相同的代码结构,但避免此错误?我在这里进行了简化,但我正在谈论一批数百行代码和几个二级,三级,四级等子菜单。

谢谢,
马莱克

1 个答案:

答案 0 :(得分:10)

我在你发布的代码中没有看到任何递归,并且不足为奇,我无法让代码失败。

您必须在未显示导致问题的代码中进行递归。

我知道批处理文件有两种类型的致命递归错误:

1)SETLOCAL限制为最多32个级别。试图在没有发出ENDLOCAL的情况下第33次使用SETLOCAL将导致以下致命错误消息:

Maximum setlocal recursion level reached.

我怀疑这是你达到的递归限制。

更新: 实际上,每个CALL级别的限制为32 SETLOCAL。有关详细信息,请阅读http://ss64.org/viewtopic.php?id=1778

中的整个主题

2)如果在任何CALL返回之前发出太多CALL,CALL递归可能会失败。只要到达脚本结尾,EXIT / B或GOTO:EOF,批处理就从CALL返回。递归CALL的最大数量取决于机器。 (也许代码依赖?)错误消息看起来像这样:

******  B A T C H   R E C U R S I O N  exceeds STACK limits ******
Recursion Count=600, Stack Usage=90 percent
******       B A T C H   PROCESSING IS   A B O R T E D      ******

可能有一些系统配置可能会增加你可以进行的递归CALL的数量,但总会有一些上限。


如果达到递归限制,那么除了重构代码之外,你真的没有太多选择。但除非你展示你的递归代码,否则很难(就我而言几乎不可能)我们建议你解决问题的方法。


编辑以回应更新的问题和评论

以下是您需要遵循的一般规则:如果您有代码循环,则每个SETLOCAL必须与ENDLOCAL配对。循环可以由GOTO创建,也可以是FOR循环。

你正在大量使用GOTO循环。您在发出SETLOCAL之后成功识别出在执行循环返回的GOTO之前必须发出ENDLOCAL。在我看来你解决了这个问题。


最后一次编辑 - 使用CALL简化逻辑

在使用GOTO循环时,要准确跟踪必须发出ENDLOCAL的位置和次数,这很痛苦。如果你改用CALL,那么逻辑就会简单得多,而且代码也更加结构化。

在执行CALLed子例程期间发出的所有SETLOCAL语句在例程结束时隐式结束。使用CALL时无需显式使用ENDLOCAL。

下面我展示了如何重构代码以使用CALL。研究所有代码 - 我做了一些微妙的更改,我认为这些更改可以简化和/或改进逻辑的其他方面。

@echo off
setlocal
:: Define the BS variable just once at the beginning
for /f %%A in ('"prompt $H & echo on & for %%B in (1) do rem"') do set "BS=%%A"

:Main  ---------- MAIN MENU ----------
:: ECHO( echoes a blank line
echo(
echo Main Menu
echo   1.Supermarket
echo   2.Exit Batch
:: If user hits ENTER without entering anything then current value remains.
:: So clear the value to make sure we don't accidently take action based
:: on prior value.
set "Menu="
set /p Menu=%BS% Type in your option {1,2} followed by ENTER:
if defined Menu set "Menu=%Menu:~0,1%"
if "%Menu%" EQU "1" call :Super
if "%Menu%" EQU "2" exit /b
goto :Main

:Super ---------- SECONDARY MENU ----------
setlocal
echo(
echo Supermarket
echo   a.Apple
echo   b.Banana
echo   1.Retutn to Main
set "Menu="
set /p Menu=%BS% Type in your option {1,a,b} followed by ENTER:
if defined Menu set "Menu=%Menu:~0,1%"
:: Use IF /I option so case does not matter
if /i "%Menu%" EQU "1" exit /b
:: Note that :App and :Ban are still logially part of this subroutine
:: because I used GOTO instead of CALL. When either :App or :Ban ends, then
:: control will return to the Main menu
if /i "%Menu%" EQU "a" goto :App
if /i "%Menu%" EQU "b" goto :Ban
goto :Super

:App
setlocal enableDelayedExpansion
set /a cnt=0
for /f "tokens=* delims= " %%a in (*.apple) do (set /a cnt+=1)
echo There are %cnt% apples
if %cnt% GTR 0 (
 REM ... do many things and after 200 lines
 echo The total number of apples was %cnt%
)
pause>nul
exit /b
:: The EXIT /B above returns to the Main menu (ends the CALL to :Super)
:: Note that the SETLOCAL at the start of :Super and the one at the
:: start of :App are implicitly ended. No need to explictly issue
:: ENDLOCAL.

:Ban
echo This is Banana
pause>nul
exit /b
:: The EXIT /B above returns to the Main menu (ends the CALL to :Super)
:: Note that the SETLOCAL at the start of :Super is implicitly ended.
:: No need to explictly issue ENDLOCAL.