双击批处理文件时暂停,但从控制台窗口运行时不暂停?

29
有没有办法让批处理文件(在这种情况下,在Windows XP上运行)确定它是从命令行启动的(即在控制台窗口内)还是通过shell启动的(例如双击)?
我有一个脚本,我希望在通过shell运行时在某些点上暂停,但在命令行运行时不暂停。我在SO上看到了一个类似的问题,但由于两个原因无法使用相同的解决方案:首先,是否暂停需要依赖于多个因素,其中只有一个是是否双击。其次,我将向团队中的其他人分发此脚本,而我无法现实地要求他们进行会影响所有脚本的注册表更改。
这可能吗?

另一个答案没有建议更改注册表。只是更改在快捷方式上执行的命令行。当然,如果您双击实际的批处理文件,这对您帮助不大。 - Joey
@Joey,你说得对;我把答案和第一个评论混淆了。;-) - Ben Blank
相关:https://dev59.com/IG025IYBdhLWcg3wr4F6 - riQQ
4个回答

38

找到了 :-) - 在拼命思考当交互运行时cmd可能会做什么,但是直接启动批处理文件时不会做什么...最终我终于找到了。

伪变量%cmdcmdline%包含用于启动cmd的命令行。如果cmd正常启动,则其中包含类似以下内容的内容:

"C:\Windows\System32\cmd.exe"

但是,当启动批处理文件时,它看起来像这样:

cmd /c ""C:\Users\Me\test.cmd" "

小型演示:

@echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
if defined DOUBLECLICKED pause

这种检查方式可能并不是最健壮的,但是如果批处理文件是直接启动的,则/c参数应该只出现在参数中。

在我的机器上可以工作

在Windows 7 x64上进行了测试。它可能会起作用,也可能无法正常工作、出现奇怪的问题、吃掉孩子(这可能是件好事)或咬你的鼻子。


2
@Ben:我也考虑过%0的事情,但很遗憾它会出错。当我通过for运行%cmdcmdline%时,由于我的主目录包含空格并且命令行有双引号,因此批处理文件的路径和名称被分成两个标记,这使得for无法正常工作。另一个选择可能是使用echo.%cmdcmdline%|findstr /i /c:"%~0" >nul 2>&1 && set DOUBLECLICKED=1(或者也许是||,我总是在findstr上犯错误),这实际上会搜索%0而不是依赖于标记化。但如果批处理文件是由另一个批处理文件调用的话,它仍然会失败。 - Joey
4
在我的 Windows 7 拷贝版本中,以管理员身份运行批处理文件会将大写的 /C 作为参数传递给资源管理器。这将执行一个不区分大小写的比较:for %%x in (%CMDCMDLINE%) do if /I [%%~x] equ [/c] pause - indiv
1
不错,但在我看来还不够,因为即使从PowerShell调用,也会使用 cmd /c - Ruben Bartelink
@Ruben,从任何不是cmd的其他程序调用将使用cmd /c(无论如何都必须这样)。这是不可避免的,因为毕竟cmd可能是唯一一个可以解释批处理文件而不委派给另一个程序的程序。 - Joey
@Joey,是的,我知道;但问题的标题仍然是“来自控制台窗口”,而不是“来自CMD命令提示符”。因此对于我的用例(PS是我的主要shell),该解决方案无法实现这一目标。我+1以表明承认这是一个解决方案,但如果我知道“控制台窗口”上有,那么我个人不会费心思去尝试使用它,当然,显然不包括PowerShell的窗口。 - Ruben Bartelink
显示剩余4条评论

10
从这个页面(以及其他类似问题的一些堆栈溢出页面)得出的综合答案。 这个答案不依赖于检测 /c,而是实际上检查命令行中脚本的名称。 因此,如果您双击了另一个批处理文件然后调用了该文件,此解决方案将不会暂停; 您必须双击此特定的批处理文件。
:pauseIfDoubleClicked
setlocal enabledelayedexpansion
set testl=%cmdcmdline:"=%
set testr=!testl:%~nx0=!
if not "%testl%" == "%testr%" pause
  1. 变量“testl”获取完整的cmd处理器调用行,去除所有烦人的双引号。
  2. 变量“testr”接收“testl”并进一步去除当前批处理文件名(如果批处理文件是通过双击调用的,则会出现该情况)。
  3. if语句检查“testl”和“testr”是否不同。 如果是,表示批处理文件是通过双击打开的,因此暂停; 如果不是,则批处理文件是在命令行中键入的或者从另一个批处理文件中调用的,继续执行。

编辑:可以使用一行代码完成相同的操作:

echo %cmdcmdline% | findstr /i /c:"%~nx0" && set standalone=1

通俗易懂地说,这段代码会将%cmdcmdline%的值传递给findstr,然后在其中搜索当前脚本名称。如果shift之前没有被调用,%0将包含当前脚本名称。%~nx0%0中提取文件名和扩展名。通过将输出重定向到NUL,>NUL 2>&1可以使findstr变为静音状态。如果findstr无法找到所需的子字符串,则设置非零错误级别。&&只有在前一个命令返回而没有出错时才执行。因此,如果从命令行启动脚本,则standalone不会被定义。

稍后在脚本中我们可以这样做:

if defined standalone pause

2
不错,我喜欢这个一行代码!它可以在Windows 7上运行。如果你不使用“shift”,你甚至可以直接在脚本末尾调用 ECHO %cmdcmdline% | findstr /i /c:"%~nx0" >NUL 2>&1 && PAUSE - 不需要变量。 - EM0

2
一种方法是在 C 盘根目录下创建一个名为 autoexec.nt 的文件,其内容大致如下:
@set nested=%nested%Z

在您的批处理文件中,检查%nested%是否为“Z”-如果是“Z”,则表示您已经双击了,因此请暂停。如果不是“Z”-那么它将是“ZZ”或“ZZZ”等,因为CMD继承父进程的环境块。 -Oisin

有趣的想法,但似乎不起作用。 AUTOEXEC.NT 完全被忽略,AUTOEXEC.BAT 只在系统启动时执行一次,并且 AutoRun 注册表键每个 CMD.EXE 会话只执行一次,无论它是如何启动的。 - Ben Blank
1
批处理文件不会在新的 cmd 进程中启动。因此,如果我只是启动一个 cmd 实例并从那里启动批处理文件,则 nested 将为 Z。否则,这是检测嵌套 shell 的好方法,但不适用于批处理文件。 - Joey
1
另外需要注意的是:autoexec.nt 从未存储在驱动器的根目录中,而是位于 %systemroot%\system32 目录下。据我所知,它只会影响 command.com,而不会影响 cmd - Joey
1
有时候,发表自己的想法是值得的,希望能激发其他人的灵感。 - x0n

0

更多信息...

我从一个包含以下内容的批处理文件(test.cmd)开始:

@echo %cmdcmdline%

如果我在Windows资源管理器中双击“test.cmd”批处理文件,那么echo %cmdcmdline%的显示结果是:
cmd /c ""D:\Path\test.cmd" "

在命令提示符窗口中执行“test.cmd”批处理文件时,echo %cmdcmdline% 的显示取决于命令窗口的启动方式...

如果我通过单击“开始”按钮和“命令提示符”来启动“cmd.exe”,或者从搜索/运行框中执行“cmd.exe”,然后执行“test.cmd”批处理文件,则 echo %cmdcmdline% 的显示为:

"C:\Windows\system32\cmd.exe"

此外,对于我来说,如果我从桌面快捷方式点击“命令提示符”,然后执行“test.cmd”批处理文件,那么echo %cmdcmdline%的显示也是:
"C:\Windows\system32\cmd.exe"

但是,如果我在Windows资源管理器窗口内“右键单击”,然后选择“在此处打开命令提示符”,然后执行“test.cmd”批处理文件,那么echo %cmdcmdline%的显示结果为:

"C:\Windows\System32\cmd.exe" /k ver

所以,如果你从一个包含“/c”字符的快捷方式中启动“cmd.exe”,那么前面示例中的测试将无法正确测试此情况。因此,请小心处理。


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