有没有可能检测批处理文件是通过CALL命令启动还是直接启动?

11
问题在于CALL命令会重复使用^字符并将两个百分号合并成一个。在混合文件中,这可能是一个大问题。或者如果一个批处理文件没有从另一个批处理文件中使用CALL执行,并且您想要警告用户。
我认为%cmdcmdline%(尽管如果直接从提示符中执行文件,则可以使用此选项)和(goto)>nul技术在这里都不起作用。这个可以被用来解决此问题。

我仍在考虑这个问题,但如果有人提出优雅的解决方案将会很棒。

编辑。一个可能的方法是通过可靠的方式检测批处理递归级别,但需要一种可靠的方法来检查此配置项(我不知道如何计算它)。


2
  1. (goto)>nul 技巧对于 CALL 和直接启动都会产生相同的结果。
  2. cmdcmdline 也会产生相同的结果。
  3. 使用批处理递归级别可以确定它,但我无法看到任何程序不被检测到就停止的方法。
- jeb
我必须纠正自己,(goto) 只在从命令行本身使用 call 时显示相同的结果,但不是在从另一个批处理文件中使用时。因此,MC ND 的解决方案似乎几乎完美。链接 - jeb
1
冬季派对:戴上帽子,不要穿围嘴 ;) - Io-oI
5个回答

9
根据我的理解,您想要一种方法,使得一个名为file.bat的文件能够确定它是否是通过其他批处理文件中的call命令调用的,并且需要在批处理文件中插入额外的检查代码来实现这一点,对吗?
好的,一个非常简单的方法就是在调用者代码中在调用命令之前插入一行额外的代码,以定义一个“调用标志”变量。这样,当在file.bat中未定义此类变量时,它就不是通过call进行调用的:
在调用者代码中:
set "callFlag=1"
call file.bat

在file.bat文件中:

if not defined callFlag echo Warning, I was not executed via CALL command!

我不知道谁以及如何使用我的 .bat 文件。这在某些情况下可能很有用,但是如果没有先决条件,它无法检测到调用。 - npocmaka

5

这可以通过一些“恶劣”的技巧来完成,因此这并不是一个完美的解决方案。
我计算出需要多少次调用才能得到一个stackoverflow,然后将该值与错误输出的值进行比较。

但是需要启动第二个cmd.exe进程,因为stackoverflow会杀死第一个批处理实例。

@echo off
setlocal EnableDelayedExpansion
set "_fileCheck=%~0"
if "!_fileCheck:~-4!"==".bAt" goto :child
del stackoverflow.log
start "" /b "%~dpn0.bAt" AllParameters

set count=0
(call :recusion & echo NEVER) 1> stackoverflow.log  2>&1
echo #* NEVER
exit /b

:recusion
set /a count+=1
echo +%count%
call :recusion


:child
ping -n 2 localhost 2>&1 > nul

:loop
(
    < stackoverflow.log  (
      rem  dummy
    ) || goto :loop
) 2>nul

:X
for /F "tokens=1,2 delims=,=" %%L in ('type stackoverflow.log') do (
    set "line=%%L"
    set "firstChar=!line:~0,1!"
    if "!firstChar!"=="+" (
        set /a maxCount=!line!        
    ) ELSE if "!firstChar!" NEQ "*" (
        set /a overflow=%%M
    )
)
if !maxCount! EQU !overflow! (
    echo Direct started
) ELSE (
    echo CALL started
)
exit 

pingеҸӘжҳҜдёҖз§ҚејұеҗҢжӯҘе’Ңзӯүеҫ…ж–Ү件еӯҳеңЁзҡ„ж–№жі•гҖӮиҝҷеҸҜд»ҘйҖҡиҝҮдҪҝз”Ёif exist .. goto :loopжҲ–еҸҜиғҪдҪҝз”Ёdbenhamзҡ„жҠҖе·§д№ӢдёҖжқҘе®ҢжҲҗгҖӮ - jeb
好的,我已从分隔符列表中移除了空格,这对德语+美国/英语有效。 - jeb
让我们在聊天中继续这个讨论 - jeb
2
也许我错了,但在我看来,这种方法只能检测到链中某处使用了 call 命令以达到当前批处理文件,而不能检测到调用当前批处理文件的 call 命令。如果 a.bat 调用(即使用 call 命令)b.bat,而 b.bat 直接调用(不使用 call)你的批处理文件,则此方法将返回错误的正面 call 命令使用情况。 - MC ND
@MCND 你是对的,似乎无法检测出这些情况之间的差异。 - jeb
显示剩余3条评论

5

如果我理解您的问题正确:您想要一种方法,使批处理脚本能够确定它是否是通过另一个批处理脚本的call命令调用的。下面的33939966test.bat脚本展示了可能的解决方案:使用强制性参数"^%%%%"进行使用,如下:

@ECHO OFF >NUL
if "%~1" == "" goto :USAGE
if "%~1" == "^^%%"        echo call from another script %* & goto :GOOD
if "%~1" == "^%%%%"       echo      from another script %* & goto :USAGE
if "%~1" == "^^%%%%%%%%"  echo call from command line   %* & goto :USAGE
if "%~1" == "^%%%%%%%%"   echo      from command line   %* & goto :USAGE
echo(wrong 1st parameter %%*=%*
goto :USAGE

:GOOD
echo(
rem echo original %%*=%*
shift /1
rem echo shifted  %%*=%*
:: Note that `shift` does not affect %*
:::::::::::::::::::::::::::::::::::::::
:: Useful code begins here

goto :eof

:USAGE
echo USAGE: CALL %~nx0 "^%%%%%%%%" [parameters]
echo(
rem the 1st parameter must be: 
rem "CARET followed by four PERCENT SIGNS" including all " DOUBLE QUOTES
goto :eof

命令行输出:

==> rem cmd copy&paste start

==> ECHO OFF >NUL
33939966test.bat "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
wrong 1st parameter %*="C&D pay 21 % VAT" "^=caret" Windows_NT %Windows_NT%
USAGE: CALL 33939966test.bat "^%%%%" [parameters]

call 33939966test.bat "^%%%%" "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
call from command line   "^^%%%%" "C&D pay 21 % VAT" "^^=caret" Windows_NT %Windows_NT%
USAGE: CALL 33939966test.bat "^%%%%" [parameters]

33939966test.bat "^%%%%" "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
     from command line   "^%%%%" "C&D pay 21 % VAT" "^=caret" Windows_NT %Windows_NT%
USAGE: CALL 33939966test.bat "^%%%%" [parameters]

ECHO ON >NUL

==> rem cmd copy&paste end

来自调用方的输出:

==> 33939966myCaller.bat

==> call 33939966test.bat "C&D pay 21 %% VAT" "^=caret" Windows_NT %OS%
wrong 1st parameter %*="C&D pay 21 % VAT" "^^=caret" Windows_NT Windows_NT
USAGE: CALL 33939966test.bat "^%%%%" [parameters]


==> call 33939966test.bat "^%%" "C&D pay 21 %% VAT" "^=caret" Windows_NT %OS%
call from another script "^^%" "C&D pay 21 % VAT" "^^=caret" Windows_NT Windows_NT


==> 33939966test.bat "^%%" "C&D pay 21 % VAT" "^=caret" Windows_NT %OS%
     from another script "^%%" "C&D pay 21 % VAT" "^=caret" Windows_NT %OS%
USAGE: CALL 33939966test.bat "^%%%%" [parameters]

在此,33939966myCaller.bat 的读取方式如下(包括 复制&粘贴 部分):

@ECHO ON
call 33939966test.bat "C&D pay 21 %%%% VAT" "^=caret" %OS% %%OS%%
@ECHO ON
call 33939966test.bat "^%%%%" "C&D pay 21 %%%% VAT" "^=caret" %OS% %%OS%%
@ECHO ON
33939966test.bat "^%%%%" "C&D pay 21 %% VAT" "^=caret" %OS% %%OS%%
@ECHO OFF
goto :eof

rem cmd copy&paste start
ECHO OFF >NUL
33939966test.bat "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
call 33939966test.bat "^%%%%" "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
33939966test.bat "^%%%%" "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
ECHO ON >NUL
rem cmd copy&paste end

请注意,上述方法并不是百分之百可靠的,因为我们可能会从命令行得到错误的结果:
==> 33939966test.bat "^^%" "C&D pay 21 % VAT" "^=caret" %OS% %%OS%%
call from another script "^^%" "C&D pay 21 % VAT" "^=caret" Windows_NT %Windows_NT%

也许结合使用(goto)技术可以有帮助?

使用强制参数是个好主意!这个解决方案(至少在我测试过的情况下)是可行的,而且比jeb的解决方案快。目前我会将jeb的答案作为接受的答案,因为对于调用者来说它没有/较少的前提条件。 - npocmaka

3
Jeb's answer一样,此代码无法区分当前批处理文件是被调用还是直接从被调用的批处理文件中调用。该代码只能检测执行上下文是否为批处理文件(在某个位置有一个call来到达此点)或命令行(没有call或初始上下文为命令行)。虽然不同,但也许有人可以从中得出更好的方法。

Jeb的评论之后进行了编辑。我认为echo可能会更有问题,但事实并非如此,所以(goto)方法显然是更好的选择。

@echo off
rem Check if this is the initial invocation or we have already determined 
rem how the batch file was started

    rem If already tested save method, remove test variable and goto main
    if defined __callTest__ (
        set "startMethod=%__callTest__%" 
        set "__callTest__=" 
        goto :main
    )

rem Initial invocation - Test routine 

    rem Cancel current batch context and check if the new context is 
    rem   - batch (there was a call somewhere) 
    rem   - command line (no call)
    rem Once the invocation method is determined, restart the current batch

    setlocal enableextensions disabledelayedexpansion
    call :getCurrentFile _f0
    (
        rem Cancel context
        2>nul (goto)

        rem The context cancel operation interferes with echo logic. Restore
        echo on

        rem We try to read a non existing variable and see if we could. 
        rem   - In command line context, the reading of a non existing variable
        rem     returns the name of the variable, setting our test var
        rem   - In batch context, reading a non existing variable does not 
        rem     return anything, and the test variable is undefined

        call set "__callTest__=%%{[%random%""%random%]}%%"
        if defined __callTest__ (
            set "__callTest__=direct"
            "%_f0%" %*
        ) else (
            set "__callTest__=call"
            call "%_f0%" %*
        )
    )
    :getCurrentFile returnVar
        set "%~1=%~f0"
        goto :eof

rem Main batch code
:main
    setlocal enableextensions disabledelayedexpansion
    echo Method invocation is [ %startMethod% ]
    goto :eof

当然,它并不是百分之百可靠的。这段代码存在批处理参数处理问题,涉及到%*

1
这似乎是最佳方法。但你不需要.test.调用,你可以用(goto) 2> nul替换"%_f0%" .test. - jeb
@jeb,我加入了.test.是因为(至少在我的环境中)这样更干净(是的,也更慢)。 (goto) 保留了 echo off 的状态,稍后需要重新启用它(至少在命令行中工作时),而直接批处理调用不会发生这种情况。 - MC ND
@jeb,我本以为我需要在更多的地方处理echo,但实际上并不需要。(goto)胜出,答案已更新。谢谢。 - MC ND

3
低技术解决方案,只需设置和检查环境变量,像这样:
caller.cmd:
SET calling=1
CALL called.cmd
SET calling=

called.cmd

IF [%calling%]==[1] SET PATH=%PATH%;.

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