10

The problems is that CALL command doubles the caret sign ^ and make double percentage a single one. And in hybrid files this can be a big problem.Or if a bat is not executed with CALL from another bat and you want to warn the user.

The %cmdcmdline%(despite this can be used if the file is executed directly from the prompt) and (goto)>nul (eventually this can be used with this) techniques I think are not useful here.

I'm still thinking over this , but if anybody comes with elegant solution will be great.

EDIT. one possible approach is to detect the batch recursion level though a reliable way to check this configuration entry is needed (I have no idea how it is calculated).

Community
  • 1
  • 1
npocmaka
  • 55,367
  • 18
  • 148
  • 187
  • 2
    1) The `(goto)>nul` technic shows the same result for `CALL` and direct start. 2) `cmdcmdline` produces also the same results. 3) With the batch recursion level you can determine it, but I can't see a way that your program doesn't stop by the detection – jeb Nov 26 '15 at 15:55
  • I have to correct myself, `(goto)` shows only the same results when the `call` is used from the command line itself, but not when it's used from another batch file. Therefore the [solution of MC ND](http://stackoverflow.com/a/43184105/463115) this seems to be nearly perfect – jeb Apr 04 '17 at 08:09
  • 1
    Winter bash: wear a hat, not a bib ;) – Io-oI Dec 20 '20 at 15:07

5 Answers5

8

As I understand your question, you want a method that allows a file.bat file to determine if it was called via a call command executed in other Batch file or not, and the way to do this is inserting additional checking code in the Batch file, right?

Well, a very simple method to do this is just inserting an additional line before the call command in the caller code in order to define a "call flag" variable. This way, when such variable is not defined in the file.bat, then it was not called via call:

In the caller code:

set "callFlag=1"
call file.bat

In the file.bat:

if not defined callFlag echo Warning, I was not executed via CALL command!
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • the idea is that I don't know who and how uses my .bat . This could be useful in some situations but is can't detect the call without prerequisites. – npocmaka Nov 27 '15 at 09:08
5

It can be done with some nasty tricks, so this isn't a perfect solution.
I count how many calls are necessary to get a stackoverflow and then I compare the value with the value from the error output.

But it's necessary to start a second cmd.exe process, as the stackoverflow kills the first batch instance

@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 
jeb
  • 78,592
  • 17
  • 171
  • 225
  • Great! though I think this line `for /F "tokens=1,2 delims=,= "` should be `for /F "tokens=1,3 delims=,= "` . Also I've put two additional `exit /b` lines at the end and increased the ping to `3`. – npocmaka Nov 27 '15 at 10:16
  • The `for /F ...` fits for my german error output `Rekursionszähler=593, Stapelauslastung=90 Prozent`. I don't know if other languages needs different rules – jeb Nov 27 '15 at 10:18
  • The ping is only a weak synchronization and waiting that the file exists. This could be done with a `if exist .. goto :loop` or possible one of dbenhams technics – jeb Nov 27 '15 at 10:20
  • OK, I removed the space from the delims list, that works for german+US/EN – jeb Nov 27 '15 at 10:21
  • Will be `eol=*` be faster than `if "!firstChar!" NEQ "*"` (though here a good performance cannot be achieved)? – npocmaka Nov 27 '15 at 10:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/96339/discussion-between-jeb-and-npocmaka). – jeb Nov 27 '15 at 10:24
  • 2
    Maybe I'm missing something and I'm wrong but as I see it, this method only detect `call` usage somewhere in the chain to reach the current batch file, not the `call` usage in the call to the current batch file. If `a.bat` calls (that is, uses `call` command) `b.bat` and `b.bat` directly invoke (does not use `call`) your batch file, this method will return a false positive `call` command usage. – MC ND Nov 30 '15 at 10:05
  • @MCND You are right, it seems not possible to detect the difference between this cases – jeb Nov 30 '15 at 11:23
5

If I can understand your question: you want a method that allows a batch script to determine if it was called via a call command from another batch script. Next 33939966test.bat script shows possible approach: force usage with mandatory parameter "^%%%%" as follows:

@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

Output from command line:

==> 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

Output from a caller:

==> 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]

where the 33939966myCaller.bat reads as follows (copy&paste part included):

@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

Note that above approach is not bullet-proof as we could imagine false positive from command line:

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

Perhaps combining with (goto) technique could help?

JosefZ
  • 28,460
  • 5
  • 44
  • 83
  • Good idea to use mandatory parameter! This works (at least what I've tested), and is faster than than jeb's solution.For now I'll leave the jeb's one as accepted answer because it have no/less prerequisites for the caller. – npocmaka Nov 27 '15 at 10:48
3

As with the Jeb's answer, this can not differentiate if the current batch file has been called or it has been directly invoked from a called batch file. This code just detects if when finished the execution context will be a batch file (there was a call somewhere to reach this point) or the command line (there was not any call or the initial context was the command line). Not the same, but maybe someone could derive a better way from it.

edited after Jeb's comment. I though the echo could be more problematic but it wasn't, so the (goto) approach is clearly a better option.

@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

And yes, of course, not bullet proof. This code has the habitual batch argument handling problems around %*.

Community
  • 1
  • 1
MC ND
  • 69,615
  • 8
  • 84
  • 126
  • 1
    This seems to be the best approach. But you don't need the `.test.` invocation, you could replace `"%_f0%" .test.` with `(goto) 2> nul` – jeb Apr 04 '17 at 07:56
  • @jeb, I included the `.test.` because (at least in my environment) it is cleaner (yes, also slower). The `(goto)` leaves active the `echo off` and it is necessary to later reenable it (at least if working in command line), something that does not happen with the direct batch invocation. – MC ND Apr 04 '17 at 08:16
  • @jeb, I though I would have to deal with the `echo` in more places, but it wasn't needed. `(goto)` wins, answer updated. Thank you. – MC ND Apr 04 '17 at 11:08
3

Low-tech solution, just set and check an environment variable. like so:

caller.cmd:

SET calling=1
CALL called.cmd
SET calling=

called.cmd

IF [%calling%]==[1] SET PATH=%PATH%;.
James John McGuire 'Jahmic'
  • 11,728
  • 11
  • 67
  • 78