GOTO :EOF 返回到哪里?

64

我试图理解代码中GOTO :EOF会返回到哪里?

以下是代码:

SET count=1 
FOR /f "tokens=*" %%G IN (somefile.txt) DO (call :subroutine "%%G") 
GOTO :EOF

:subroutine  
echo %count%:%1  
set /a count+=1  
GOTO :EOF
4个回答

103
:EOF 是一个预定义标签,如Microsoft在GOTO命令的文档中所解释的。在命令提示符窗口中运行goto /?的帮助输出也解释了这个特殊的标签用于文件结束。但是默认情况下,只有启用命令扩展时才支持此预定义标签。
在命令提示符窗口中运行call /?的帮助输出以及CALL命令的文档都解释了goto :EOF应该用于退出由call :Label调用的子例程。
子例程实际上就是嵌入在当前批处理文件中用call命令调用的另一个批处理文件。如果子例程位于批处理文件的末尾,则真正的文件结束标记标志着子例程的结束。
但是一个批处理文件中可以有多个子例程。
需要在命令处理中到达特定行时退出子例程并返回调用命令行,因此需要一个命令来退出命令解释器。无论是goto :EOF还是exit /B都可以在任何地方使用,以退出子例程或退出当前批处理文件处理。
在问题的批处理代码中,第一个goto :EOF用于在完成循环后,退出批处理文件处理而不会意外地落入子例程代码中。
问题批处理代码中的第二个goto :EOF用于退出子例程并继续处理第二行中的FOR循环。它不会退出批处理文件的处理,只会退出子例程的处理。

注意1:goto EOF没有冒号的要求是批处理文件中确实存在以:EOF开头的行,即标签EOF必须存在于文件中。即使批处理文件中有一个以:EOF开头的行,goto :EOF始终会启用命令扩展来退出子例程/批处理处理。

注意2:不带参数/B的命令EXIT始终会导致整个命令过程退出,与调用层次结构和Windows命令处理器的启动方式无关-使用参数/K保持cmd.exe运行时打开命令提示窗口或使用/C在命令处理完成后关闭时使用。因此,在批处理文件中应明智地使用不带/Bexit(最好永远不要使用)。

注意 3:exit /B 带或不带退出代码都能正常工作,但是在禁用命令扩展的情况下会输出错误消息,如以下代码所示:

@echo off
setlocal DisableExtensions
echo Use command exit /B with command extensions disabled.
exit /B 5
echo This line is not processed anymore.

在命令提示符窗口中执行此批处理文件会导致错误消息的输出:

The system cannot find the batch label specified - EOF

尽管如此,批处理文件的处理仍然以退出代码值5退出,可以在同一命令提示符窗口中运行下一个命令echo ERRORLEVEL is: %ERRORLEVEL%查看该值,它输出:ERRORLEVEL is: 5。似乎首先将指定的退出代码值5分配给动态变量ERRORLEVEL,然后由于选项/B执行goto :EOF。由于禁用了命令扩展,因此失败并导致出现错误消息,并在批处理文件处理结束时退出,因为在批处理文件中要转到的标签不存在。
换句话说,exit /B 始终有效,无论是否附加了退出代码,但是在禁用命令扩展名时应添加 2>nul 以抑制错误消息,即使用 exit /B 2>nul(不带退出代码)或 exit /B 5 2>nul(带退出代码)。

注意 4: ERRORLEVEL 不受 goto :EOF 的影响,但是 Microsoft GOTO 文档对此不作说明。 exit /B #ERRORLEVEL 设置为由 Microsoft 文档 记载的 #。可以使用 exit /B # 代替 goto :EOF 以使用在调用子程序时从命令行计算的特定退出代码退出子程序,就像使用运算符 &&|| 或在调用带有 if errorlevel X 命令行的下一个命令时一样。然而,通常不需要显式地使用特定的退出代码退出批处理文件或子程序,因为既不 goto :EOF 也不 exit /B 修改当前的 ERRORLEVEL 值。

注意事项 5:在批处理文件中,不要在GOTOCALL(参数0)命令与标签(参数1)之间没有空格的情况下使用goto:EOFcall:Label。应该始终使用带有空格的goto :EOFcall :Label作为命令和标签之间的参数字符串分隔符。原因是goto:EOF会导致尝试在当前目录中首先查找名为goto:的文件,然后查找名为goto:EOF的文件。而不正确的call:Label命令会导致搜索名为call:的文件,然后再搜索名为call:Label的文件。文件系统对于这两个语法错误的命令都会返回两次“无效名称”的错误信息给cmd.exe。然后cmd.exe检测到冒号是无效名称的原因,并将命令分割成命令和标签参数,最终成功运行命令。使用goto :EOFcall :Label不会引起任何错误的文件系统访问,因为cmd.exe立即将字符串gotocall识别为内部命令。

有关ERRORLEVEL行为的详细信息,请参见:


8

GOTO :EOF 是与 exit /B 功能等效的,但这两种形式仅在启用扩展时才有效。测试这一点非常简单:

setlocal DisableExtensions
goto :EOF

与先前的代码相比,这段代码有所改进:

setlocal DisableExtensions
exit /B

这意味着 GOTO :EOF 返回到与 exit /B 返回相同的位置。

1
默认情况下,扩展是启用的,还是必须显式地启用它们? - Max Cascone
@MaxCascone:cmd /? 帮助屏幕指示了类似“命令扩展默认启用”的内容... - Aacini
goto :eof 返回 %ERRORLEVEL% == 1,exit /B 也是如此。 - Sandburg
@Sandburg:不管是goto :eof还是exit /B,都不会改变之前的%ERRORLEVEL%值。请参考这个答案了解更多细节... - Aacini

2

:eof 的意思是“文件结束”。它用于使脚本在不执行下面任何命令的情况下完成。


谢谢!但是循环是如何发生的呢?第二个GOTO也说::eof,难道不应该是例如GOTO :DO吗? - s6398197atuser
7
call 命令会新建一个子线程,并且暂停当前线程的执行,直到 call 命令执行完成。call :subroutine 命令会一直执行直到遇到自己的 exit /b 或者程序结束符(EOF),然后再返回到调用者的程序继续执行。打开回显功能可以观察命令的执行顺序,有助于更好地理解。更多关于 call 命令的信息可以参考这个页面,有关批处理函数的教程可以参考这个页面 - rojo
答案不太清楚,因为它会从子程序返回到类似于“返回”命令在C#中返回到调用方法的 CALL 命令。因此,如果这些命令随后作为单独的子程序调用,那么“下面的命令”是可以执行的。 - E. A. Bagby

1
作为寻找标签的功能,GOTOCALL 具有相同的功能,因此您可以使用 CALL 访问 :EOF
CALL ::EOF

GOTO相同(当使用附加分号的CALL时),实际文件结尾将成为脚本流程的首选点。如果您使用EOF定义了自己的标签/函数,则可以使用单个分号访问它。
虽然调用:EOF没有太多用处 - 您不能在文件末尾放置代码,因此此行实际上什么也没做(尽管这会影响性能,因为文件末尾被解析)。并且像GOTOEXIT /B一样,这不会在未启用扩展名的情况下工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接