1

Usage

I have a large batch files with many useful functions.

I would like to put some of those functions in separate files while at other times I want them in a single containing file so the whole script is a single file.

I want to know, since the CALL command checks the disk for something even in call to labels, does there exist a file which could override an internal label.

Here is how to test this.

in a new folder, first test script labelorfile.bat

@echo off

echo starting test
echo we call the label :mytestfunction
call :mytestfunction
echo this is after "call :mytestfunction" in file labelorfile.bat
echo we call the file mytestfunction.bat
call mytestfunction.bat
echo this is after "call mytestfunction.bat" in file labelorfile.bat
call mytestfunction
echo this is after "call mytestfunction" in file labelorfile.bat
goto :eof

:mytestfunction
echo this is the label :mytestfunction in the labelorfile.bat file
goto :eof

Next file, single line batch file, mytestfunction.bat

echo this is the file mytestfunction.bat

Next file, single line batch file with no extension, mytestfunction

echo this is the file mytestfunction with no extension

I run labelorfile.bat, this is the output

labelorfile.bat
starting test
we call the label :mytestfunction
this is the label :mytestfunction in the labelorfile.bat file
this is after "call :mytestfunction" in file labelorfile.bat
we call the file mytestfunction.bat
this is the file mytestfunction.bat
this is after "call mytestfunction.bat" in file labelorfile.bat
this is the file mytestfunction.bat
this is after "call mytestfunction" in file labelorfile.bat

Conclusion, a file with the name of a label, cannot override a label inside a batch file

Is this correct ?

Shodan
  • 1,065
  • 2
  • 13
  • 35
  • 1
    `Call :label` will always execute the routine `:label` contained within the batch file. If `label` is missing, you get an error. `call label` will always access an external executable. Your results prove that `call label` executes the external routine if `:label` is present in the file. What do you mean by `override`? – Magoo Aug 06 '23 at 22:47
  • My problem is, I have written a large batch framework and I wish to break it down into individual files per function. I want to maintain the ability to create single file scripts but I would also like to call same name internal functions – Shodan Aug 07 '23 at 05:43
  • So the question becomes, when I call a script, how do I always call a function in such a way that, if the label name exists as a file, then the file is run, but if not, the label runs. Since I don't know want to create two versions of every function, one that calls external files and another that calls internal labels. Keeping in mind that function calls sometimes are run inside of loops with thousands of elements – Shodan Aug 07 '23 at 05:45
  • One possibility call :FunctionNameOrLabel "my test argument" 123 || call FunctionNameOrFile.bat "my test argument" 123 – Shodan Aug 07 '23 at 06:07
  • That was with an inline test, now here is another possibility, setting macros for each function, depending on that test – Shodan Aug 07 '23 at 06:08
  • :SetCallMacro set "IsNumeric=Call :IsNumeric" %IsNumeric% 1 >nul 2>&1 if errorlevel 1 set "IsNumeric=Call IsNumeric.bat" GoTo :EOF – Shodan Aug 07 '23 at 06:08
  • I was really hoping someone would know, what does call check the disk for when doing a call :label and if that means it was possible to name a file the right way that it would run the file if it exists ! – Shodan Aug 07 '23 at 06:09
  • When batch executes a logical line, it retains the file location (#bytes) for the next command. At completion of that process, it re-loads the batch file and commences at the location retained, hence there will be a file access operation. Whether that access is physical or not depends on whether the OS considers it necessary or can deliver cached data. You can check this by editing the file while the batch is running (eg at a `pause`). You will find that the modified file contents will be executed, not the original. – Magoo Aug 07 '23 at 12:03
  • Thanks ! I was under the incorrect impression that call was looking if a file with the label's name existed ! Do you have more information about how the cmd interpreter works internally ? I imagine some people have decompiled it and figured out how it works inside, but I am unable to find this, if it exists. – Shodan Aug 07 '23 at 22:52
  • oh, that's a deep [rabbit hole](https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts). Be sure to have enough coffee available before climbing down... – Stephan Aug 08 '23 at 14:10

2 Answers2

1

As your tests proves, call :label always call a local label, whereas call file always call an external file.bat; there is no way to "override" this beahavior...

However, I think that the solution to your problem could be to check if the call to a local label caused an error, and in such a case, call the external file:

@echo off
setlocal

echo Calling 1:
call :localLabel1
echo Calling 2:
call :localLabel2 2> NUL
if errorlevel 1 (
   echo :localLabel2 don't exist. Calling the external file:
   call localLabel2
)
goto :EOF

:localLabel1
echo I am local subroutine 1
exit /B

This is localLabel2.bat file:

echo I am the separate file localLabel2.bat

This is the output:

Calling 1:
I am local subroutine 1
Calling 2:
:localLabel2 don't exist. Calling the external file:
I am the separate file localLabel2.bat
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Thank you, I am curious if that could be made more compact. For instance if the call is within a loop and runs thousands of times, running these expensive test calls would be quite an extra burden – Shodan Aug 07 '23 at 05:47
  • I presume the script preamble could run these test for each existing call function once and then the solution would be run for the rest of the scripts execution. Only way I can think of doing this is macros maybe ? – Shodan Aug 07 '23 at 05:48
  • He who shall not be named, has just suggested to me this syntax, however it is not great for arguments call :FunctionNameOrLabel || call FunctionNameOrFile.bat – Shodan Aug 07 '23 at 05:52
  • For instance call :FunctionNameOrLabel "my test argument" 123 mytestargument || call FunctionNameOrFile.bat "my test argument" 123 mytestargument – Shodan Aug 07 '23 at 05:53
  • This is also an issue with my function Call :IsNumeric "123A" && echo it is numeric || echo it is not numeric – Shodan Aug 07 '23 at 05:55
  • Perhaps something like :SetCallMacro set "IsNumeric=Call :IsNumeric" %IsNumeric% 1 >nul 2>&1 if errorlevel 1 set "IsNumeric=Call IsNumeric.bat" GoTo :EOF – Shodan Aug 07 '23 at 06:05
  • And do that for every function call in the current file. That does mean keeping track of which function is being used in the current file. Or maybe having a generic SetCallMacro that has every possible function call I've ever seen. And I think that means SetCallMacro can't itself be a .bat file – Shodan Aug 07 '23 at 06:06
1
@echo off

call :calling sub
echo done
goto :eof

:sub
echo hello original
goto :eof

:calling
if exist "%1.bat" ( 
  call "%~1.bat" 2>nul
) else (
  call :%~1 2>nul || ( 
    echo such no file or label: '%1'
    exit /b 1
)

run with and without sub.bat (with content @echo hello called).
With the file existing, :calling will call it and return.
With the file not existing, it will try to call the label and return.
If neither the file nor the label exists, it will output a message and return with an errorlevel of 1.

This is "prefer a file over a label" (as I interpret your question).

Doing the logic in a subroutine itself keeps the main code clean (as you seem to use it several times in a script) with call :calling sub

Stephan
  • 53,940
  • 10
  • 58
  • 91
  • Thanks this solved the issue of using return values to determine if a label exists in the current file. However, would this technique work for .bat files that are in the path but not in the current folder ? I believe that if exists only checks for the current folder, I will give it a try – Shodan Aug 07 '23 at 22:58
  • no time to test right now, but replacing `if exist` with `where` should work (`where /q %1`). Where looks if a matching file is located in current working folder or `%PATH%` – Stephan Aug 08 '23 at 06:21
  • I have been studying batch file language and window console for 9 months and this is the first time I have ever heard of where ! Thanks !! – Shodan Aug 08 '23 at 21:18