Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!
Posted: 23 Nov 2014 14:05
What this strange title means? Well, this point requires a small explanation...
In StackOverflow site there are a few posts that requests a method to "execute a Batch file with an extension different than .BAT or .CMD". After some tests, I posted an answer that uses this trick:
The NotBatch.txt file is executed in the command-line context instead of in the Batch-file context, so there are several differences when compared vs. a standard Batch file. This is a summary of the features/limitations of a text file executed this way.
Execute it this way: cmd < EXAMPLE.TXT > NUL
You may review more extensive examples at the link given above
=======================================================================
Now the interesting part. When GOTO or CALL commands are executed in a Batch file, cmd.exe change the File Pointer (FP) used to read the next line from the running Batch file. In the case of CALL, the previous FP is stored in a return stack, so the return command (EXIT /B or GOTO :EOF) just pop the last stored value and change the FP to it.
In order to "execute" (or "simulate" or whichever) these commands in our running NotBatch.txt file, that is redirected to cmd.exe, we would need a way to modify the File Pointer of the file currently redirected to Stdin. Hey, wait... We HAVE such a program! This is precisely the purpose of my FilePointer.exe auxiliary program !
Then, I got the offset of the :label with FINDSTR /O "^" TEST.TXT that was 102, set FP=102, executed the text file via "CMD < TEST.TXT > NUL" AND IT WORKED!!!
After that, I developed a series of aids that allows to execute/simulate/whichever these commands in a NotBatch.txt text file in a way as standard as possible. The offsets of all labels in the NotBatch.txt file are obtained via a findstr /O "^:.*" ^< NotBatch.txt command and stored in variabes with same names, so "GOTO/CALL" could directly use those names in a FilePointer 0 %labelName% command. At beginning, I used a label placed after the "CALL" in order to have the destination point for the corresponding "RETURN", but then I realized that I could define Batch files with names like GOTO.BAT or CALL.BAT and execute they from the NotBatch.txt file without including the Batch "call" command itself. This points not only allows me to write much clearer equivalents, but also to get the return FP in CALL.BAT, so the return labels were no needed anymore. I ended with the definition of ECHO.BAT and SET.BAT that just includes the commonly used redirections. The result is very good!
This is NotBatch.txt:
And these are its auxiliary Batch files.
CALL.BAT
GOTO.BAT
SET.BAT
ECHO.BAT
Download previous files (and FilePointer.exe one, of course) and execute they this way:
In which cases this feature may be useful? I HAVE NO IDEA! But all this stuff is EXTREMELY INTERESTING!!!
Antonio
In StackOverflow site there are a few posts that requests a method to "execute a Batch file with an extension different than .BAT or .CMD". After some tests, I posted an answer that uses this trick:
Code: Select all
cmd.exe < NotBatch.txt
- Batch-file parameters %1 %2 ... can not be used nor SHIFT command.
- @ECHO OFF command just supresses the echoing of the prompt and FOR iterations. To supress the echoing of all commands, the NotBatch.txt file must be executed this way: "cmd.exe < NotBatch.txt > NUL" and then use "echo Text > CON" in order to display text in the screen.
- SETLOCAL/ENDLOCAL commands have not effect in the NotBatch.txt file. You may use the CALL trick to expand changing variables, or enable delayed expansion via /V:ON switch of cmd.exe. Anyway, there is an implicit SETLOCAL because cmd.exe is executed as a child process.
- SET /P command does not read data from the keyboard, but from the lines placed after it in the NotBatch.txt file itself. This point allows to simulate the "here doc" Unix feature. To read data from keyboard, execute: "set /P var=Prompt: < CON > CON".
- FOR and IF commands may be combined/nested at any level and are executed correctly; just use single percent characters in FOR replaceable parameters.
- GOTO and CALL commands does not work on labels. However, an external Batch file may be invoked with or without the CALL command with the same result, even inside IF or FOR.
- EXIT or EXIT /B commands terminate the execution of the NotBatch.txt file.
Code: Select all
@echo off
rem Read the next four lines; "next" means placed after the FOR command
rem (this may be used to simulate a Unix "here doc")
for /L %i in (1,1,4) do (
set /P line[%i]=
)
Line one of immediate data
This is second line
The third one
And the fourth and last one...
(
echo Show the elements of the array read:
echo/
for /L %i in (1,1,4) do call echo Line %i- %line[%i]%
) > CON
You may review more extensive examples at the link given above
=======================================================================
Now the interesting part. When GOTO or CALL commands are executed in a Batch file, cmd.exe change the File Pointer (FP) used to read the next line from the running Batch file. In the case of CALL, the previous FP is stored in a return stack, so the return command (EXIT /B or GOTO :EOF) just pop the last stored value and change the FP to it.
In order to "execute" (or "simulate" or whichever) these commands in our running NotBatch.txt file, that is redirected to cmd.exe, we would need a way to modify the File Pointer of the file currently redirected to Stdin. Hey, wait... We HAVE such a program! This is precisely the purpose of my FilePointer.exe auxiliary program !
I started with a simple test as a proof of concept. I wrote the following TEST.TXT file:FilePointer.exe documentation wrote: Get or set the file pointer position of a redirected file handle.
Code: Select all
@echo off
echo Execute the goto > CON
FilePointer 0 %FP%
echo This should not appear! > CON
:label
echo It works! > CON
After that, I developed a series of aids that allows to execute/simulate/whichever these commands in a NotBatch.txt text file in a way as standard as possible. The offsets of all labels in the NotBatch.txt file are obtained via a findstr /O "^:.*" ^< NotBatch.txt command and stored in variabes with same names, so "GOTO/CALL" could directly use those names in a FilePointer 0 %labelName% command. At beginning, I used a label placed after the "CALL" in order to have the destination point for the corresponding "RETURN", but then I realized that I could define Batch files with names like GOTO.BAT or CALL.BAT and execute they from the NotBatch.txt file without including the Batch "call" command itself. This points not only allows me to write much clearer equivalents, but also to get the return FP in CALL.BAT, so the return labels were no needed anymore. I ended with the definition of ECHO.BAT and SET.BAT that just includes the commonly used redirections. The result is very good!
This is NotBatch.txt:
Code: Select all
@echo off
rem NotBatch.txt: Simulate GOTO/CALL commands in a text file redirected to cmd.exe
rem Antonio Perez Ayala
rem Requires FilePointer.exe auxiliary program; download it from:
rem http://www.dostips.com/forum/viewtopic.php?f=3&t=5552&p=34051#p34051
rem Get the offsets of the labels in this NotBatch.txt file
for /F "tokens=1,2 delims=:" %a in ('findstr /O "^:.*" ^< NotBatch.txt') do set "%b=%a"
echo.bat
echo.bat Calling subroutine from first point
call.bat :subroutine "First time"
echo.bat Returned from subroutine
echo.bat
set.bat /P "line=Enter a few words: "
set i=0
:nextWord
set /A i+=1
for /F "tokens=1*" %a in ("%line%") do (
echo.bat Word %i%- %a
set "line=%b"
)
if defined line goto.bat nextWord
echo/> CON
echo.bat Calling subroutine from second point
call.bat :subroutine "Second time"
echo.bat Returned from subroutine
rem Use exit /B to terminate the main program
exit /B
:subroutine
echo.bat --^^^> Greetings from the subroutine; my parameters are: %params%
echo.bat --^^^> Returning to caller program
rem Use goto.bat :EOF to return from a subroutine
rem (if you use exit /B here, the main program is terminated)
goto.bat :EOF
CALL.BAT
Code: Select all
rem Support code for "CALL command" in a text file redirected to cmd.exe
rem Get and store the return (current) offset
FilePointer 0 0 /C
set EOF=%errorlevel%
rem Set the offset to jump to the :subroutine
setlocal EnableDelayedExpansion
set "sub=%1"
FilePointer 0 !%sub:~1%!
rem Get and store the parameters
set params=%*
for /F "delims=" %%a in ("!params:*%1=!") do endlocal & set params=%%a
Code: Select all
rem Support code for "GOTO command" in a text file redirected to cmd.exe
if %1 equ :EOF (
rem Jump to last saved return offset
FilePointer 0 %EOF%
) else (
rem Jump to a normal label
setlocal EnableDelayedExpansion
FilePointer 0 !%1!
)
Code: Select all
<CON >CON set %*
Code: Select all
>CON echo(%*
Code: Select all
cmd < NotBatch.txt > NUL
Antonio