How to get the name of calling bat script - batch-file

I need to have my 2.bat script behaviour depending on the name of calling script.
Scenario:
2.bat is invoked from many other external scripts, which I am not entitled to change. Only 2.bat is under my thumb.
1.bat:
...
call 2.bat
2.bat:
...here place something extracting "1.bat"...

As you cant change the calling bat there will be almost impossible to get its name if it is triggered through the cmd console (may be a memory dump could help?) as then the ProcessId will hold information only for the cmd.exe. The command prompt history could give you some information but it would be unreliable (and requires a dump to a temporary file)
If the calling bat is double clicked you can use this:
setlocal enableDelayedExpansion
for /f "tokens=2* delims= " %%a in ("%cmdcmdline%") do (
if /i "%%~a" equ "/c" (
for %%# in (%%~b) do (
echo calling bat : %%~#
)
) else (
doskey /history >"%tmp%\cmd.history"
for /f "usebackq tokens=* delims=" %%# in ("%tmp%\cmd.history") do (
set "last_command=%%#"
)
echo probably this is the calling bat: !last_command!
del /q /f "%tmp%\cmd.history"
)
)
pause

You can get the name of a calling batch with a trick.
Assuming you have a first.bat(you can't controll) it could look like this
#echo off
set caller=empty
echo This is %~0
for /L %%n in (1 1 3) do (
echo(
echo #1 before calling, n=%%n
call second %%n
)
echo Back to %~0
And your second.bat detects the caller
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** Get the name of the caller
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:mainFunc\..%~pnx0" %*
) ELSE (
set "callType=cmd-line"
cmd /c "call "%~d0\:mainFunc\..%~pnx0" %*"
)
echo BACK
endlocal
)
echo NEVER REACHED
exit /b
:mainFunc
echo :mainFunc of %~nx0 arg1=%1 is called from '%caller%'/%callType%
exit /b

I've adjusted jikou's and jeb's code a little bit so it can also detect the caller function from the caller script.
detectCallerScript.bat:
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
REM *** Get the name of the caller
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~f0"
call set _caller=%%caller:*%%~f0=%%
if defined _caller (
set "callType=batch"
call "%~d0\:mainFunc\..%~pnx0" %%0 %*
) else (
set "callType=cmd-line"
cmd /c "call "%~d0\:mainFunc\..%~pnx0" %%0 %*"
)
endlocal
)
echo(NEVER REACHED
exit /b
:mainFunc
set "source=%~1"
shift /1
:mainFuncLoop
set args=%args%%1
if "%~2" neq "" shift /1&goto:mainFuncLoop
if defined args set "args=%args:~0,-1%"
echo(:mainFunc of %~nx0 source=%source% with args="%args%" is called from '%caller%'/%callType%
exit /b
scriptCaller.bat:
#echo off
set caller=empty
echo(%~0: call detectCallerScript hi there
call detectCallerScript hi there
echo(Back to %~0
echo(
call:someFunc
exit /b
:someFunc
set caller=empty
echo(%~n0 %~0: call detectCallerScript hi there
call detectCallerScript hi there
echo(Back to %~0
echo(
Output:
scriptCaller.bat: call detectCallerScript hi there
:mainFunc of detectCallerScript.bat source=scriptCaller.bat with args="hi there" is called from '{path}\scriptCaller.bat'/batch
Back to scriptCaller.bat
scriptCaller :someFunc: call detectCallerScript hi there
:mainFunc of detectCallerScript.bat source=:someFunc with args="hi there" is called from '{path}\scriptCaller.bat'/batch
Back to :someFunc

Related

Pass Named Parameters to Batch Script with Special Characters

I found and modified a code snippet to allow passing unlimited named parameters to a batch script.
Accessing unknown number of commands (parameters) in batch file
Everything was working great, but now I'm building in Wildcard checking into the script and I found if I pass a value like this "FILEPATH=C:\tmp\test *.txt" that FILEPATH doesn't get defined by my code snippet. As I didn't truly create it I am partly unaware of how it works and could be modified to allow special characters.
Here is the code snippet to allow named params that I'd like guidance on modifiying:
::Set Named Arguments
set argCount=0
for %%x in (%*) do (
set /A argCount+=1
set "argVec[!argCount!]=%%~x"
set %%x
)
Update:
I changed the for loop to for /F delims^=^"^ tokens^=* %%x in (%*) do ( and it will now define the FILEPATH with a WILDCARD, but it strips the first " and then makes all the arguments into one line and also strips the final ". Perhaps I need a way to use the argcount to correlate the alphanumeric position of the set %%x line?
Another thought, since the above change to the for loop does accept the wildcard, but creates a single long variable containing all params passed to script.cmd, perhaps I can loop over it (the long variable) again and split up the named arguments.
Update:
Example usage:
script.cmd:
#ECHO OFF
CLS
::Set Named Arguments
set argCount=0
for %%x in (%*) do (
set /A argCount+=1
set "argVec[!argCount!]=%%~x"
set %%x
)
ECHO %FILEPATH%
ECHO %VAR%
EXIT /B
test.cmd:
#ECHO OFF
CLS
::Doesn't Work
CALL "C:\tmp\script.cmd" "FILEPATH=C:\tmp\tes*.txt" "VAR=2"
PAUSE
::Works Fine
CALL "C:\tmp\script.cmd" "FILEPATH=C:\tmp\test.txt"
PAUSE
Using your current method by defining FILEPATH= as a parameter.
Note:
I need to express that this is trending a little on the dangerous side. Reason being, if any of the input variables contains something like PATH=Somepath it will break the immediate environment while the script is running. So ensure you check the input types that will be passed.
#echo off & setlocal enabledelayedexpansion
(set "%~1" & set "%~2" & set "%~3" & set "%~4")>nul
set argCount=0
if defined FILEPATH (
for %%x in ("%FILEPATH%") do (
set /A argCount+=1
set "argVec[!argCount!]=%%~x"
echo argVec[!argCount!]
)
echo %FILEPATH%
) else (
echo FILEPATH not defined
)
My full solution based on #Gerhard's awesome answer. This still allows me to take an unlimited amount of variables input in unknown order in "VALUE=KEY" format, and not know the FILEPATH positional argument, but as batch has limitations on using only %1-->%9 I felt it easiest/best to handle/allow that FILEPATH be any of the first 9 PARAMS. This really taught me about the things you take for granted in shells like BASH and also, what BASH is doing "behind the scenes". The idea was to build in wildcard searching as my script.cmd will always be called by a "parent script" w/ params and I want it to be similar to BASH (allow end users to use wildcards).
script.cmd:
#ECHO OFF
CLS
::SET Named Arguments
SET argCount=0
for %%x in (%*) do (
SET /A argCount+=1
SET "argVec[!argCount!]=%%~x"
SET %%x
)
::Wildcards in FilePath?
(SET "%~1" & SET "%~2" & SET "%~3" & SET "%~4" & SET "%~5" & SET "%~6" & SET "%~7" & SET "%~8" & SET "%~9")>nul
SET argCount=0
IF DEFINED FILEPATH (
FOR %%x IN ("%FILEPATH%") DO (
SET /A argCount+=1
SET "argVec[!argCount!]=%%~x"
)
CALL :FindFileWildCard "%FILEPATH%" FILEPATH
) ELSE (
ECHO No "FILEPATH=C:\path\print.doc" Defined!
PAUSE
GOTO:EOF
)
ECHO %FILEPATH%
ECHO %VAR%
ECHO %VAR2%
ECHO %VAR3%
ECHO %VAR4%
ECHO %VAR5%
ECHO %VAR6%
ECHO %VAR7%
ECHO %VAR8%
ECHO %VAR9%
ECHO %VAR10%
GOTO :EOF
::Functions
:FindFileWildCard
::Does Path contain WildCards?
ECHO "%~1" | FIND /i "*" >nul
IF %ERRORLEVEL% EQU 0 (
FOR /F "Tokens=*" %%F IN ('DIR /B /S "%~1"') DO (
SET %2=%%F
EXIT /B
)
)
ECHO "%~1" | FIND /i "?" >nul
IF %ERRORLEVEL% EQU 0 (
FOR /F "Tokens=*" %%F IN ('DIR /B /S "%~1"') DO (
SET %2=%%F
EXIT /B
)
)
EXIT /B
:EOF
test.cmd:
#ECHO OFF
CLS
CALL "C:\tmp\script.cmd" "VAR=VAR" "VAR2=VAR2" "VAR3=VAR3" "FILEPATH=C:\tmp\tmp space\te*.txt" "VAR4=VAR4" "VAR5=VAR5" "VAR6=VAR6" "VAR7=VAR7" "VAR8=VAR8" "VAR9=VAR9" "VAR10=VAR10"
PAUSE
CALL "C:\tmp\script.cmd" "VAR=VAR" "VAR2=VAR2" "VAR3=VAR3" "FILEPATH=C:\tmp\tmp space\test with spa?*.txt" "VAR4=VAR4" "VAR5=VAR5" "VAR6=VAR6" "VAR7=VAR7" "VAR8=VAR8" "VAR9=VAR9" "VAR10=VAR10"
PAUSE
CALL "C:\tmp\script.cmd" "VAR=VAR" "VAR2=VAR2" "VAR3=VAR3" "FILEPATH=C:\tmp\test.txt" "VAR4=VAR4" "VAR5=VAR5" "VAR6=VAR6" "VAR7=VAR7" "VAR8=VAR8" "VAR9=VAR9" "VAR10=VAR10"
PAUSE
Result:
C:\tmp\tmp space\test with space.txt
VAR
VAR2
VAR3
VAR4
VAR5
VAR6
VAR7
VAR8
VAR9
VAR10
Press any key to continue . . .

Trouble with using For variables within for loop Batch

I am having trouble with a bit of code, I don't really know how to describe it
but I can explain what doesn't work
FOR /D /r "%cd%\files\" %%G in ("*") DO (
echo In folder: %%~nxG
set /a count=1
echo %%~fG
For /R "%%~fG" %%B in ("*.mp3") do (
call :subroutine "%%~nB"
) & echo. >>%archive%.txt
)
just if you want to know what the subroutine does:
:subroutine
echo %count%:%1>>%archive%.txt
echo %count%: %1
set /a count+=1
GOTO :eof
I figured out that it doesn't read the %%~fG inside the second for loop.
Can someone please help me.
I am using SETLOCAL EnableDelayedExpansion
Thank you in advance.
Unfortunately you'll need another subroutine as for options are parsed before outer for tokens. Check the following example:
#echo off
echo :::ATTEMPT 1:::
for %%a in (z) do (
rem the expected delimiter is z and result should be ++::%%a
for /f "delims=%%a tokens=1,2" %%A in ("++z%%%%az--") do echo %%A::%%B
)
echo :::ATTEMPT 2:::
for %%a in (z) do (
call :subr "%%~a"
)
exit /b
:subr
rem the expected delimiter is z and result should be ++::%%a
for /f "delims=%~1 tokens=1,2" %%A in ("++z%%%%az--") do echo %%A::%%B
the output is:
:::ATTEMPT 1:::
++z::zz--
:::ATTEMPT 2:::
++::%%a
As you can see in the first attempt the %%a symbols are taken as delimiters. But subroutine arguments are parsed imminently so they can be used.
To make your code work you can try with:
FOR /D /r "%cd%\files\" %%G in ("*") DO (
echo In folder: %%~nxG
set /a count=1
echo %%~fG
call ::innerFor "%%~fG"
)
...
exit /b %errorlevel%
:innerFor
For /R "%~1" %%B in ("*.mp3") do (
call :subroutine "%%~nB"
) & echo. >>%archive%.txt
For /R "%%~fG" %%B in ("*.mp3") do (
Sadly, for/r can't be run with a variable as the dirname.
I'd suggest
call :anothersubroutine "%%~fG"
and
:anothersubroutine
For /R "%~1" %%B in ("*.mp3") do (
but I've not tried it. Perhaps you'd need to set %%~fG into a variable and use %var% (not tried that either...)

How to use delayedexpansion to handle value in batch script

I am facing issue with reading contents of CSV files in batch script. I have a series of files say My_A_File.csv, My_B_File.csv ... My_Z_File.csv. The issue I was facing is reading special characters in them. Hence, wanted to read the values with delayedexpansion turned off.
When I read the values in the block with disabledelayedexpansion, they are empty! How can I handle this?
Script:
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
echo Inner-%fileToReadFrom%
echo Inner-%codeval%
endlocal
)
)
Output:
Outer-My_A_File.csv
Outer-A
Inner-
Inner-
This how the delayed expansion is supposed to work.However you can access the variables with CALL but this will the performance (mind that you cant CALL FOR ):
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
call echo Inner-%%fileToReadFrom%%
call echo Inner-%%codeval%%
endlocal
)
)
or you can use pipes.Which also will hit the performance (now you can use
break|for "usebackq" %%a in ("Inner-%%fileToReadFrom%%") do #echo %%~a):
#echo off
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
setlocal disabledelayedexpansion
break|echo Inner-%%fileToReadFrom%%
break|echo Inner-%%codeval%%
endlocal
)
)
Use a subroutine to process code with delayed expansion disabled as follows:
#echo off
rem skip subroutine code
goto :toMain
:toProcessDDE
rem subroutine to process delayed expansion disabled
setlocal disabledelayedexpansion
echo Inner-%fileToReadFrom%
echo Inner-%codeval%
endlocal
exit /B
:toMain
setlocal enabledelayedexpansion
for /L %%g in (65,1,90) do (
cmd /c exit /b %%g
set codeval=!=ExitCodeAscii!
set fileToReadFrom=My_!codeval!_File.csv
if exist My_!codeval!_File.csv (
echo Outer-!fileToReadFrom!
echo Outer-!codeval!
call :toProcessDDE
)
)
Read
CALL: Call one batch program from another, or call a subroutine and
EXIT: … quit the current subroutine …

The syntax of the command is incorrect : for loop batch

I'm making a batch script to stop several services , however i get the syntax is incorrect error at second FOR which loops in serviceList.TEMP
setlocal EnableDelayedExpansion
setlocal enableExtensions
::queryex output file
sc queryex>services.TEMP
find /i /N "DISPLAY_NAME: Hotspot" services.TEMP>tmp1.TEMP
FOR /F "skip=2" %%G in (tmp1.TEMP) do (
set num=%%G
set num=!num:~1,3!
echo !num!>serviceList.TEMP
)
FOR %%X in (serviceList.TEMP) do (
set /a "SKIP_LINES=%%G+7"
set secondForFilter="skip=%SKIP_LINES%"
FOR /F %secondForFilter% %%Z in (services.TEMP) do (
call debug.cmd REM debug.cmd -> echo debug pause>nul
set serv=%%Z
set "serv=!serv: =!" REM Extract PID
set "serv=!serv::=!" REM Extract PID
set procID=!serv!
taskkill /pid %procID% /f >>debug.txt 2>>debug.txt
goto secondLoopEnd
)
:secondLoopEnd
)
del /S *.TEMP >>debug.txt 2>>debug.txt
the problem is here:
FOR %%X in (serviceList.TEMP) do (
set /a "SKIP_LINES=%%G+7"
set secondForFilter="skip=%SKIP_LINES%"
FOR /F %secondForFilter% %%Z in (services.TEMP) do (
call debug.cmd REM debug.cmd -> echo debug pause>nul
the usual approach when you set value in brackets context is to use delayed expansion but it wont work for parametrized for options.
Here you'll need a subroutine.
And you have GOTO in the for loop. GOTO breaks for context and the loops will be not called after goto is executed.
And rem cannot be used on the same line as the code without "&"
Consider something like this (though I cannot check the logic of the bat):
setlocal EnableDelayedExpansion
setlocal enableExtensions
::queryex output file
sc queryex>services.TEMP
find /i /N "DISPLAY_NAME: Hotspot" services.TEMP>tmp1.TEMP
FOR /F "skip=2" %%G in (tmp1.TEMP) do (
set num=%%G
set num=!num:~1,3!
echo !num!>serviceList.TEMP
)
FOR %%X in (serviceList.TEMP) do (
call :subroutine %%G
)
del /S *.TEMP >>debug.txt 2>>debug.txt
exit /b %errorlevel%
:subroutine
setlocal enableDelayedExpansion
set /a skiplines=%~1+7
set "filter="skip=%skiplines%""
FOR /F %filter% %%Z in (services.TEMP) do (
call debug.cmd
set serv=%%Z
rem extract PID
set "serv=!serv: =!"
set "serv=!serv::=!"
set procID=!serv!
taskkill /pid !procID! /f >>debug.txt 2>>debug.txt
goto :break_for
)
:break_for
endlocal
exit /b
Should the > in your echo to serviceList.TEMP be a >> so that you append to the file?
echo !num!>>serviceList.TEMP
In which case, you should also ensure that the file is deleted prior to the appending operations.
Also, I assume you missed the /F from your FOR loop, as you're trying to read the lines of the serviceList.TEMP file, yes?
FOR %%X in (serviceList.TEMP) do (
Should be...
FOR /F %%X in (serviceList.TEMP) do (
?
Also, you can append to the same file with both std out and err by doing this...
someprocesshere 1> out.log 2>&1

Only use first word of input batch file

I am making a program that needs to run the commands that users put in.
If the command doesn't exist it opens an error.
:cmd
set /p cmd="Command:"
if not exist %cmd% goto nocommand
%cmd%
:noCommand
echo Error, command doesn't exist..
goto cmd
But if I type "echo text" it says text isn't a command. I need it to only read the first word.
Checks if its is possible to execute the command as an internal,from given path or from %PATH%. It uses also solution from dbenham from here : Check if command is internal in CMD
#echo off
:cmd
set /p "cmd=Command:"
for /f "tokens=1 delims= " %%a in ("%cmd%") do set "fcmd=%%~na"
setlocal
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
set path=
>nul 2>nul %fcmd% /?
if not errorlevel 9009 (
popd
rd "%empty%"
echo %fcmd% is internal command
endlocal
goto :execute
) else (
popd
rd "%empty%"
endlocal
)
color
for %%# in (%PATHEXT%;"" ) do (
rem echo --%fcmd%%%~#--
if exist %fcmd%%%~# (
echo the command/executable/script will be executed from given location
goto :execute
)
for /f "tokens=1 delims= " %%a in ("%fcmd%%%~#") do (
if "%%~$PATH:a" NEQ "" (
echo the command/executable/script is defined in %%PATH%%
rem
goto :execute
)
)
)
echo command does not exist
exit /b 1
:execute
%cmd%
set /p cmd="Command:"
for /f "tokens=1" %%i in ("%cmd%") do set firstword=%%i
echo %firstword%

Resources