batch script that updates the version number values in a text file - batch-file

Hi I am trying to write a script that updates the MAJOR and MINOR version numbers in a header/text file, I am trying to use the "FOR /F " command but out of ideas.
Below is the Header file to which I need to update the values corresponding to "MAJOR_VERSION" and "MINOR_VERSION "
#ifndef _VERSION_H_
#define _VERSION_H_
//---------- Lock Firmware Package Version ------------
//Lock firmware package
#define MAJOR_VERSION 3 //0-255
#define MINOR_VERSION 58 //0-255
#define REVISION_VERSION 0 //0-255 should generally always be 0 unless the meaning change
//---------- VERSION ----------------------

The following script -- let us call it update-versions.bat -- should do what you want:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Parse command line arguments:
set "DIRN=#define"
set "FILE=%~1"
if not defined FILE (>&2 echo ERROR: too few arguments!) & exit /B 1
shift /1
set /A "IDX=0"
:ARGS
set "ARGV=%~1"
if not defined ARGV goto :SKIP
set /A "IDX+=1"
set "KEY[%IDX%]=%ARGV::=" & rem "%"
if not defined KEY[%IDX%] set /A "IDX-=1" & goto :SKIP
set "VER[%IDX%]=10000%ARGV:*:=%"
set /A "VER[%IDX%]%%=10000"
shift /1
goto :ARGS
:SKIP
rem // Process file:
for /F "delims=" %%L in ('findstr /N /R "^" "%FILE%" ^& ^> "%FILE%" rem/') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
(
for /F "tokens=1-3*" %%A in ("!LINE:*:=!") do (
endlocal
if /I "%%A"=="%DIRN%" (
set "VER=" & set "ITEM=%%B" & set "REST=%%D"
setlocal EnableDelayedExpansion
for /L %%I in (1,1,%IDX%) do (
if /I "!ITEM!"=="!KEY[%%I]!" (
set "KEY=!KEY[%%I]!"
set "VER=!VER[%%I]!"
)
)
if defined VER (
>> "!FILE!" echo(!DIRN! !KEY! !VER! !REST!
) else (
>> "!FILE!" echo(!LINE:*:=!
)
) else (
setlocal EnableDelayedExpansion
>> "!FILE!" echo(!LINE:*:=!
)
)
) || (>> "!FILE!" echo/)
endlocal
)
endlocal
exit /B
To use it, you need to provide the file to modify and the version information as command line arguments:
update-versions.bat "\path\to\file.h" MAJOR_VERSION:3 MINOR_VERSION:59 REVISION_VERSION:0
As you can see the first argument is the path to the file, which is mandatory. Each of the remaining arguments specifies a version number keyword (appearing after the #defined directive) and the related new version number, separated by a colon; all these are optional arguments.

This is my answer to an identical question That was deleted by the owner "Dinesh" after I'd answered it yesterday.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q40733227.txt"
SET "itemlist=major_version minor_version"
FOR /f "usebackqtokens=1-3*" %%a IN ("%filename1%") DO (
FOR %%i IN (%itemlist%) DO IF /i "%%b"=="%%i" SET "%%b=%%c"
)
FOR %%i IN (%itemlist%) DO SET %%i
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q40733227.txt containing your data for my testing.
Tokenise each line using the defaults. The string in the second token (for the values selected by itemlist) should take on the value in the third token.
You could extend the values extracted in the obvious way, extend itemlist

Here is a simple script that uses JREPL.BAT - a regular expression find/replace utility. JREPL is pure scrpt (hybrid batch/JScript) that runs natively on any Windows machine from XP onward - no 3rd party exe file required. Full documentation is available from the command line via jrepl /?, or jrepl /?? for paged help.
updateHeader.bat
#echo off
setlocal
set "var="
:loadVar
set "%~2=%~3"
set "var=%Var%%~2|"
shift /2
shift /2
if "%~2" neq "" goto :loadVar
call jrepl "(#define +(%var:~0,-1%) +)\d+" "$txt=$1+env($2)" /jq /f %1 /o -
The batch script parses the command line arguments. It builds a list of variable names to be replaced (simple regular expression alternation), and also sets an environment variable for each specified variable. The JREPL call replaces each value with the value of the corresponding environment variable.
The syntax for using the above script is updateHeader File Variable=Value[ Variable=Value].... The Variable names are case sensitive.
For example, to update file "version.h" with major version=4 and minor version=01:
updateHeader version.h MAJOR_VERSION=4 MINOR_VERSION=01
Or you could update just the revision version to 1, and specify a different path for the file:
updateHeader "c:\somePath\version.h" REVISION_VERSION=1
You can specify as many replacements as you like, as long as each line to be replaced has the form #define VARIABLE value .... The number of spaces can vary.

Related

rename a file removing part of the filename batch script

I have some files in the form:
filename1 1 extra1.ext
filename1 2.ext
filename1 3 extra2.ext
...
filename2 1.ext
filename2 100 extra3.ext
...
filename20 1.ext
filename20 15 extra100.ext
(etc.)
...where filename1, filename2, etc., can contain spaces, symbol ' but not numbers. And extra1, extra2, etc, can contain anything. The number in the file name enclosed by spaces does not repeat per same filename1, filename2, etc.
What i want is to remove the extra things of the files that contain it. That is, to get from filename20 15 extra100.ext to filename20 15.ext
My first attempt is this:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FILE=file name 11 con sosas extras 2.txt"
set "ext=txt"
set "folder=."
for /F "tokens=1,* delims=0123456789" %%A in ("!FILE!") do (set "EXTRA=%%B")
set "FIRST=!FILE:%EXTRA%=!"
set "filename=!FIRST!.!ext!"
echo !EXTRA!
echo !filename!
echo rename "!folder!\!FILE!" "!filename!"
that seems to work, but if i change it to receive parameters, it doesn't:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FILE=%1"
set "ext=%2"
set "folder=%3"
for /F "tokens=1,* delims=0123456789" %%A in ("!FILE!") do (set "EXTRA=%%B")
set "FIRST=!FILE:%EXTRA%=!"
set "filename=!FIRST!.!ext!"
echo !EXTRA!
echo !filename!
echo rename "!folder!\!FILE!" "!filename!"
where %1 is the filename, %2 is the extension and %3 is the folder in which the files are. Probably, the extension can be extracted inside the batch, but i don't know how to do it.
On another hand, i plan to use this batch into another one. There, there will be a for loop in (*.txt) and i don't know how to differentiate between files that have extra things (and then call this batch) from files that doesn't (and then not call this batch).
Regards,
use your method to extract the "extra-portion". In a second step, remove that extra-portion:
#echo off
setlocal enabledelayedexpansion
set "FILE=file name 11 con sosas extras 2.txt"
for /f "tokens=1,* delims=1234567890" %%a in ("%file%") do set new=!file:%%b=!%%~xb
echo %new%
%%~xb gives you the extension.
Here is a batch script that seeks the first purely numeric string portion enclosed within SPACEs, or in case it appears at the end, preceded by a SPACE, that occurs after some other text not consisting of SPACEs only. The part in front of the found number followed by a SPACE followed by the number itself are used for building the new file name.
This approach handles all valid characters for file names properly, even ^, &, %, !, ( and ).
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=.\test"
for /F "eol=| delims=" %%F in ('
dir /B "%_SOURCE%\*.ext" ^| findstr /R /I ^
/C:"^..* [0123456789][0123456789]*\.ext$" ^
/C:"^..* [0123456789][0123456789]* .*\.ext$"
') do (
set "FILE=%%F"
call :SPLIT FIRST NUM REST "%%~nF"
if defined NUM (
setlocal EnableDelayedExpansion
ECHO rename "!_SOURCE!\!FILE!" "!FIRST! !NUM!%%~xF"
endlocal
)
)
endlocal
exit /B
:SPLIT rtn_first rtn_num rtn_rest val_string
setlocal DisableDelayedExpansion
set "RMD=" & set "NUM=" & set "STR=%~4"
:LOOP
for /F "tokens=1,2,* delims= " %%I in ("%STR%") do (
if not "%%J"=="" (
(for /F "delims=0123456789" %%L in ("%%J") do rem/) && (
if not "%%K"=="" (
set "STR=%%J %%K"
goto :LOOP
)
) || (
set "NUM=%%J"
if not "%%K"=="" (
set "RMD=%%K"
)
)
)
)
set "STR=%~4"
if not defined NUM goto :QUIT
set "STR=%STR% "
call set "STR=%%STR: %NUM% =|%%"
for /F "delims=|" %%L in ("%STR:^^=^%") do set "STR=%%L"
:QUIT
(
endlocal
set "%~1=%STR%"
set "%~2=%NUM%"
set "%~3=%RMD%"
)
exit /B
After having tested the script, remove the upper-case ECHO command to actually rename any files.

In a batch file ran from Windows, select latest application version using directory name

I use a portable application that have updates quite often. The problem is that each version of the application has a folder named "processing-x.y.z". Each time I install a new version, I need to associate the files with the new version which is in a different folder. So to workaround this annoyance, I want to associate the "*.pde" file type to a batch file.
The folder names go as follow
processing-3.2.1
processing-3.2.2
etc.
I have created this small batch script that get the executable from the latest version.
#echo off
for /f "delims=" %%D in ('dir processing* /a:d /b /o-n') do (
set currentFolder=%%~fD
:: Check if environment variable already set
if not %currentFolder%==%processing% (
:: Set environment variable processing
setx processing %currentFolder%
)
%currentFolder%/processing.exe %1
goto :eof
)
It works when launching it from the command-line, but not within Windows. Is there a specific reason? Also, is there a way to optimize this code?
Thanks
Supposing the version numbers always consist of a single digit each, I would do it the following way:
#echo off
rem // Reset variable:
set "currentFolder="
rem /* Loop through the folders in ascending order, overwrite the variable
rem in each iteration, so it holds the highest version finally: */
for /f "delims=" %%D in ('dir /B /A:D /O:N "processing-*.*.*"') do (
set "currentFolder=%%~fD"
)
rem // Check if environment variable is already set:
if not "%processing%"=="%currentFolder%" (
rem // Set environment variable `processing`:
setx processing "%currentFolder%"
)
rem // Execute `processing.exe`:
"%currentFolder%/processing.exe" "%~1"
If the individual version numbers can consist of more than one digit (four at most here), use this:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem /* Assign each found folder to a variable called `$ARRAY_X_Y_Z`, where `X`, `Y`, `Z`
rem are zero-padded variants of the original numbers `x`, `y`, `z`, so for instance,
rem a folder called `processing-4.7.12` is stored in variable `$ARRAY_0004_0007_0012`: */
for /F "tokens=1-4 delims=-. eol=." %%A in ('
dir /B /A:D "processing-*.*.*" ^| ^
findstr /R /I "^processing-[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*$"
') do (
rem // Perform the left-side zero-padding here:
set "MAJ=0000%%B" & set "MIN=0000%%C" & set "REV=0000%%D"
set "$ARRAY_!MAJ:~-4!_!MIN:~-4!_!REV:~-4!=%%A-%%B.%%C.%%D"
)
rem // Reset variable:
set "currentFolder="
rem /* Loop through the output of `set "$ARRAY_"`, which returns all variables beginning
rem with `$ARRAY_` in ascending alphabetic order; because of the zero-padding, where
rem alphabetic and alpha-numeric orders become equivalent, the item with the greatest
rem version number item is iterated lastly, therefore the latest version is returned: */
for /F "tokens=1,* delims==" %%E in ('set "$ARRAY_"') do (
set "currentFolder=%%F"
)
endlocal & set "currentFolder=%currentFolder%"
rem // The rest of the script os the same as above...
You can also find similar approaches here:
How to get latest version number using batch (this approach also relies on the sorting featurre of the set command)
How to sort lines of a text file containing version numbers in format major.minor.build.revision numerical? (this uses the sort command upon a temporary file or on piped (|) data)
not tested (edited: should handle the cases when minor versions have more digits):
#echo off
setlocal enableDelayedExpansion
set /a latest_n1=0
set /a latest_n2=0
set /a latest_n3=0
for /f "tokens=2,3* delims=-." %%a in ('dir processing* /a:d /b /o-n') do (
set "current_v=%%a.%%b.%%c"
set /a "current_n1=%%a"
set /a "current_n2=%%b"
set /a "current_n3=%%c"
if !current_n1! GTR !latest_n1! (
set /a latest_n1=!current_n1!
set /a latest_n2=!current_n2!
set /a latest_n3=!current_n3!
set "latest_v=!current_v!"
) else if !current_n1! EQU !latest_n1! if !current_n2! GTR !latest_n2! (
set /a latest_n1=!current_n1!
set /a latest_n2=!current_n2!
set /a latest_n3=!current_n3!
set "latest_v=!current_v!"
) else if !current_n1! EQU !latest_n1! if !current_n2! EQU !latest_n2! if !current_n3! GTR !latest_n3! (
set /a latest_n1=!current_n1!
set /a latest_n2=!current_n2!
set /a latest_n3=!current_n3!
set "latest_v=!current_v!"
)
)
echo latest version=processing-%latest_v%
#echo off
for /f "delims=" %%D in ('dir processing* /a:d /b /o-n') do (
if "%processing%" neq "%cd%\%%F" setx processing "%cd%\%%F" >nul
.\%%F\processing.exe %1
goto :eof
)
is equivalent code - almost.
First problem - the :: form of comments should not be used within a code block (parenthesised series of statements) as :: is in fact a label that itself stars with a colon. Since it is a label, it terminates the block.
Next problem - within a code block, %var% refers to the value ofvar**as it stood when thefor` was encountered** - not as it changes within the loop.
Next problem - as noted by others, /o-n produces a by-name sequence, so 10 is likely to sort after 9 (since the sort is reversed). I've not changed this in the replacement code, but /o-d to sort by reverse-date may be better suited to your application.
Now to how your code works.
First, processing is set to whatever the last run established. If that is different from the value calculated from this run, then setx the new value. Bizarrely, setx does not set the value in the current cmd instance, only for instances created in the future.
You then attempt to execute your process and then exit the batch with the goto :eof. Only problem is that %currentfolder% is not the value as changed by the loop, because it's supposed to be in the code block. It appears to change because the ::-comments have broken the block and where currentfolder is used in your code, it is outside of the block.
Instead, use .\%%F\executablename which means "from the current directoryname (.);subdirectory %%F;filenametoexecute" - note that "\" is a directory-separator in windows, '/' indicates a switch.

Extract Part of a text file in BAT

I am capturing a m3U file on a daily basis but wish to parse part of it to another file with the few channels I need.
For example I have renamed my m3U to Test.txt file which say has the following fictional structure:
#EXTINF:0,ABC
#live link 1
#EXTINF:0,XYZ
#live link 2
#EXTINF:0,UVW
#live link 3
I would just like to capture say the line staring from "#EXTINF:0,XYZ" and say the line beneath it to end up with a Output.txt as follows:
#EXTINF:0,XYZ
#live link 2
I know that one needs to use the For loop but I am a bit of a noob on this area.
Put this code into the file filter.cmd.
#echo off
set INPUT=%1&set MATCH=%2& set MATCHED=0
for /f "delims=" %%a in (%INPUT%) do call :line "%%~a"
goto :eof
:line
set EXT=&TITLE=&
for /f "tokens=1 delims=:" %%a in ("%~1") do set EXT=%%~a
for /f "tokens=1,2,* delims=:," %%a in ("%~1") do set TITLE=%%~c
if "%EXT%" == "#EXTM3U" echo %~1
if "%EXT%" == "#EXTINF" (
set MATCHED=0
echo %TITLE%| findstr /l %MATCH% >nul && set MATCHED=1
)
if %MATCHED%==1 echo %~1
Use example:
filter.cmd input_file.m3u XYZ > output_file.m3u
Here is some explanation:
Every input line is split using for /f with tokens and delims.
MATCHED is set if the line begins with #EXTINF and the rest contains the string to match (second argument).
if MATCHED is set, the lines are output until next #EXTINF.
I would do it like this, supposing the .m3u file does not contain trailing white-spaces in the lines preceded by #EXTINF, like your sample data does:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "FILE=%~1"
set "HEADER=#EXTM3U"
set "PREFIX=#EXTINF"
set "MATCH=%~2"
set "FLAG="
for /F usebackq^ delims^=^ eol^= %%L in ("%FILE%") do (
if defined FLAG (
echo(%%L
set "FLAG="
)
for /F "delims=:" %%P in ("%%L") do (
if "%%P"=="%HEADER%" (
echo(%%L
) else if "%%P"=="%PREFIX%" (
set "LINE=%%L"
setlocal EnableDelayedExpansion
if /I "!LINE:*,=!"=="!MATCH!" (
echo(!LINE!
endlocal
set "FLAG=#"
) else endlocal
)
)
)
endlocal
exit /B
Call the script like this, supposing it is saved as extract-entry.bat:
extract-entry.bat "input_file.m3u" "XYZ" > "output_file.m3u"
The script walks through the given .m3u file line by line. It returns the current line unedited and resets variable FLAG, if variable FLAG is set, which is not the case at the beginning.
Then it looks for #EXTINF. If found (e. g., #EXTINF:0,XYZ), the string after the comma (XYZ) is compared against the given search string. If matched, the current line is output and FLAG variable is set now in order to get the following line too.
The header line #EXTM3U is always output.
Toggling delayed expansion makes this script robust against all characters that have special meaning to the command interpreter without losing them.

batch: get last folder name from path from txt

i need take paths from txt and get last folder name and then use it.
setlocal EnableDelayedExpansion
set InputFile=somar.txt
for /f "tokens=* delims=" %%x in ('Type "%InputFile%"') do (
set path=%%x
:shift
for /f "tokens=1* delims=\/" %%i in ( "!path!" ) do (
set folder=%%i
set path=%%j
)
if not [!path!] == [] goto :shift
echo folder: !folder!
)
endlocal
problem is that it works only for first line in txt. where is the problem?
You have a number of problems:
1) Your FOR loop is broken the moment you issue a GOTO. You will not get any more FOR loop iterations after GOTO. You could fix this by moving your GOTO loop into a subroutine and then CALL the subroutine from within your DO loop.
2) The PATH environment variable has a reserved meaning for Windows. You should never use that variable name for your own purposes, even if it is localized as you have it. It isn't worth tempting fate. Simply use a different variable name.
3) Perhaps not an issue with your data, but ! is a valid character for a file or folder name. Your expansion of FOR variables will corrupt names containing ! if delayed expansion is enabled. This can be fixed by toggling delayed expansion on and off as needed.
You also have a minor inefficiency - There is no need to use TYPE with your FOR loop. You can simply let FOR read the file directly. (unless the file is unicode)
You could take all the recommendations above, but there is a much simpler solution :-)
EDIT - change made to handle a path that ends with \
#echo off
set "InputFile=somar.txt"
for /f "usebackq eol=: delims=" %%A in ("%inputFile%") do (
for %%F in ("%%A\.") do echo folder: %%~nxF
)
The ~nx FOR variable modifiers directly give you the name and extension of the end of the path. Type HELP FOR from the command line to read about all the modifiers that are available to FOR variables.
For variable filenames:
#Echo OFF
:: By Elektro H#cker
set "File=File.ext"
Call :Get_paths "%InputFile%"
Pause&exit
:Get_paths
Set "AbsolutePath=%~dp1"
set "AbsolutePath=%AbsolutePath:\= %"
FOR %%# in (%AbsolutePath%) do (
Set "LastFolder=%%#"
Echo Folder: %%#
)
Echo Last Folder: %LastFolder%
GOTO:EOF
Output:
Folder: C:
Folder: Users
Folder: Administrador
Folder: Desktop
Last Folder: Desktop
For files:
#Echo OFF
:: By Elektro H#cker
set "File=test.txt"
For /F "Tokens=* usebackq" %%# in ("%FILE%") DO (
Set "AbsolutePath=%%~dp#"
Call set "AbsolutePath=%%AbsolutePath:\= %%"
CMD /Q /C "#FOR %%# in (%%AbsolutePath%%) do (Echo Folder: %%#)"
)
Pause&Exit
InputFile content must contains filename or folder.
ex)
D:\Test1 <= folder
D:\Test2\file.txt <= file
D:\Test3\01. folder <= folder but recognize file. that contain extension.
My code is:
SETLOCAL EnableDelayedExpansion
SET lastFolder=
SET InputFile=somar.txt
FOR /F %%F IN (%InputFile%) DO (
CALL :__GetFolderName "%%F"
#ECHO lastFolder: !lastFolder!
)
GOTO :EOF
:: ******************** Inner Batch
:__GetFolderName
IF "%~x1"=="" SET lastFolder=%~n1 & GOTO :EOF
SET dp=%~dp1
CALL :__GetFolderName "%dp:~0,-1%"
GOTO :EOF
:: ********************
ENDLOCAL
Result is:
lastFolder: Test1
lastFolder: Test2
lastFolder: Test3

Make an environment variable survive ENDLOCAL

I have a batch file that computes a variable via a series of intermediate variables:
#echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
endlocal
%scripts%\activate.bat
The script on the last line isn't called, because it comes after endlocal, which clobbers the scripts environment variable, but it has to come after endlocal because its purpose is to set a bunch of other environment variables for use by the user.
How do I call a script who's purpose is to set permanent environment variables, but who's location is determined by a temporary environment variable?
I know I can create a temporary batch file before endlocal and call it after endlocal, which I will do if nothing else comes to light, but I would like to know if there is a less cringe-worthy solution.
The ENDLOCAL & SET VAR=%TEMPVAR% pattern is classic. But there are situations where it is not ideal.
If you do not know the contents of TEMPVAR, then you might run into problems if the value contains special characters like < > & or|. You can generally protect against that by using quotes like SET "VAR=%TEMPVAR%", but that can cause problems if there are special characters and the value is already quoted.
A FOR expression is an excellent choice to transport a value across the ENDLOCAL barrier if you are concerned about special characters. Delayed expansion should be enabled before the ENDLOCAL, and disabled after the ENDLOCAL.
setlocal enableDelayedExpansion
set "TEMPVAR=This & "that ^& the other thing"
for /f "delims=" %%A in (""!TEMPVAR!"") do endlocal & set "VAR=%%~A"
Limitations:
If delayed expansion is enabled after the ENDLOCAL, then the final value will be corrupted if the TEMPVAR contained !.
values containing a lineFeed character cannot be transported
If you must return multiple values, and you know of a character that cannot appear in either value, then simply use the appropriate FOR /F options. For example, if I know that the values cannot contain |:
setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "tokens=1,2 delims=|" %%A in (""!temp1!"|"!temp2!"") do (
endLocal
set "var1=%%~A"
set "var2=%%~B"
)
If you must return multiple values, and the character set is unrestricted, then use nested FOR /F loops:
setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "delims=" %%A in (""!temp1!"") do (
for /f "delims=" %%B in (""!temp2!"") do (
endlocal
set "var1=%%~A"
set "var2=%%~B"
)
)
Definitely check out jeb's answer for a safe, bullet proof technique that works for all possible values in all situations.
2017-08-21 - New function RETURN.BAT
I've worked with DosTips user jeb to develop a batch utility called RETURN.BAT that can be used to exit from a script or called routine and return one or more variables across the ENDLOCAL barrier. Very cool :-)
Below is version 3.0 of the code. I most likely will not keep this code up-to-date. Best to follow the link to make sure you get the latest version, and to see some example usage.
RETURN.BAT
::RETURN.BAT Version 3.0
#if "%~2" equ "" (goto :return.special) else goto :return
:::
:::call RETURN ValueVar ReturnVar [ErrorCode]
::: Used by batch functions to EXIT /B and safely return any value across the
::: ENDLOCAL barrier.
::: ValueVar = The name of the local variable containing the return value.
::: ReturnVar = The name of the variable to receive the return value.
::: ErrorCode = The returned ERRORLEVEL, defaults to 0 if not specified.
:::
:::call RETURN "ValueVar1 ValueVar2 ..." "ReturnVar1 ReturnVar2 ..." [ErrorCode]
::: Same as before, except the first and second arugments are quoted and space
::: delimited lists of variable names.
:::
::: Note that the total length of all assignments (variable names and values)
::: must be less then 3.8k bytes. No checks are performed to verify that all
::: assignments fit within the limit. Variable names must not contain space,
::: tab, comma, semicolon, caret, asterisk, question mark, or exclamation point.
:::
:::call RETURN init
::: Defines return.LF and return.CR variables. Not required, but should be
::: called once at the top of your script to improve performance of RETURN.
:::
:::return /?
::: Displays this help
:::
:::return /V
::: Displays the version of RETURN.BAT
:::
:::
:::RETURN.BAT was written by Dave Benham and DosTips user jeb, and was originally
:::posted within the folloing DosTips thread:
::: http://www.dostips.com/forum/viewtopic.php?f=3&t=6496
:::
::==============================================================================
:: If the code below is copied within a script, then the :return.special code
:: can be removed, and your script can use the following calls:
::
:: call :return ValueVar ReturnVar [ErrorCode]
::
:: call :return.init
::
:return ValueVar ReturnVar [ErrorCode]
:: Safely returns any value(s) across the ENDLOCAL barrier. Default ErrorCode is 0
setlocal enableDelayedExpansion
if not defined return.LF call :return.init
if not defined return.CR call :return.init
set "return.normalCmd="
set "return.delayedCmd="
set "return.vars=%~2"
for %%a in (%~1) do for /f "tokens=1*" %%b in ("!return.vars!") do (
set "return.normal=!%%a!"
if defined return.normal (
set "return.normal=!return.normal:%%=%%3!"
set "return.normal=!return.normal:"=%%4!"
for %%C in ("!return.LF!") do set "return.normal=!return.normal:%%~C=%%~1!"
for %%C in ("!return.CR!") do set "return.normal=!return.normal:%%~C=%%2!"
set "return.delayed=!return.normal:^=^^^^!"
) else set "return.delayed="
if defined return.delayed call :return.setDelayed
set "return.normalCmd=!return.normalCmd!&set "%%b=!return.normal!"^!"
set "return.delayedCmd=!return.delayedCmd!&set "%%b=!return.delayed!"^!"
set "return.vars=%%c"
)
set "err=%~3"
if not defined err set "err=0"
for %%1 in ("!return.LF!") do for /f "tokens=1-3" %%2 in (^"!return.CR! %% "") do (
(goto) 2>nul
(goto) 2>nul
if "^!^" equ "^!" (%return.delayedCmd:~1%) else %return.normalCmd:~1%
if %err% equ 0 (call ) else if %err% equ 1 (call) else cmd /c exit %err%
)
:return.setDelayed
set "return.delayed=%return.delayed:!=^^^!%" !
exit /b
:return.special
#if /i "%~1" equ "init" goto return.init
#if "%~1" equ "/?" (
for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do #echo(%%A
exit /b 0
)
#if /i "%~1" equ "/V" (
for /f "tokens=* delims=:" %%A in ('findstr /rc:"^::RETURN.BAT Version" "%~f0"') do #echo %%A
exit /b 0
)
#>&2 echo ERROR: Invalid call to RETURN.BAT
#exit /b 1
:return.init - Initializes the return.LF and return.CR variables
set ^"return.LF=^
^" The empty line above is critical - DO NOT REMOVE
for /f %%C in ('copy /z "%~f0" nul') do set "return.CR=%%C"
exit /b 0
#ECHO OFF
SETLOCAL
REM Keep in mind that BAR in the next statement could be anything, including %1, etc.
SET FOO=BAR
ENDLOCAL && SET FOO=%FOO%
The answer of dbenham is a good solution for "normal" strings, but it fails with exclamation marks ! if delayed expansion is enabled after ENDLOCAL (dbenham said this too).
But it will always fail with some tricky contents like embedded linefeeds,
as the FOR/F will split the content into multiple lines.
This will result into strange behaviour, the endlocal will executed multiple times (for each line feed), so the code isn't bullet proof.
There exists bullet proof solutions, but they are a bit messy :-)
A macro version exists SO:Preserving exclamation ..., to use it is easy, but to read it is ...
Or you could use a code block, you can paste it into your functions.
Dbenham and I developed this technic in the thread Re: new functions: :chr, :asc, :asciiMap,
there are also the explanations for this technic
#echo off
setlocal EnableDelayedExpansion
cls
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
set LF=^
rem TWO Empty lines are neccessary
set "original=zero*? %%~A%%~B%%~C%%~L!LF!one&line!LF!two with exclam^! !LF!three with "quotes^&"&"!LF!four with ^^^^ ^| ^< ^> ( ) ^& ^^^! ^"!LF!xxxxxwith CR!CR!five !LF!six with ^"^"Q ^"^"L still six "
setlocal DisableDelayedExpansion
call :lfTest result original
setlocal EnableDelayedExpansion
echo The result with disabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!
call :lfTest result original
echo The result with enabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!
echo ------------------
echo !original!
goto :eof
::::::::::::::::::::
:lfTest
setlocal
set "NotDelayedFlag=!"
echo(
if defined NotDelayedFlag (echo lfTest was called with Delayed Expansion DISABLED) else echo lfTest was called with Delayed Expansion ENABLED
setlocal EnableDelayedExpansion
set "var=!%~2!"
rem echo the input is:
rem echo !var!
echo(
rem ** Prepare for return
set "var=!var:%%=%%~1!"
set "var=!var:"=%%~2!"
for %%a in ("!LF!") do set "var=!var:%%~a=%%~L!"
for %%a in ("!CR!") do set "var=!var:%%~a=%%~3!"
rem ** It is neccessary to use two IF's else the %var% expansion doesn't work as expected
if not defined NotDelayedFlag set "var=!var:^=^^^^!"
if not defined NotDelayedFlag set "var=%var:!=^^^!%" !
set "replace=%% """ !CR!!CR!"
for %%L in ("!LF!") do (
for /F "tokens=1,2,3" %%1 in ("!replace!") DO (
ENDLOCAL
ENDLOCAL
set "%~1=%var%" !
#echo off
goto :eof
)
)
exit /b
I want to contribute to this too and tell you how you can pass over an array-like set of variables:
#echo off
rem clean up array in current environment:
set "ARRAY[0]=" & set "ARRAY[1]=" & set "ARRAY[2]=" & set "ARRAY[3]="
rem begin environment localisation block here:
setlocal EnableExtensions
rem define an array:
set "ARRAY[0]=1" & set "ARRAY[1]=2" & set "ARRAY[2]=4" & set "ARRAY[3]=8"
rem `set ARRAY` returns all variables starting with `ARRAY`:
for /F "tokens=1,* delims==" %%V in ('set ARRAY') do (
if defined %%V (
rem end environment localisation block once only:
endlocal
)
rem re-assign the array, `for` variables transport it:
set "%%V=%%W"
)
rem this is just for prove:
for /L %%I in (0,1,3) do (
call echo %%ARRAY[%%I]%%
)
exit /B
The code works, because the very first array element is queried by if defined within the setlocal block where it is actually defined, so endlocal is executed once only. For all the successive loop iterations, the setlocal block is already ended and therefore if defined evaluates to FALSE.
This relies on the fact that at least one array element is assigned, or actually, that there is at least one variable defined whose name starts with ARRAY, within the setlocal/endlocal block. If none exist therein, endlocal is not going to be executed. Outside of the setlocal block, no such variable must be defined, because otherwise, if defined evaluates to TRUE more than once and therefore, endlocal is executed multiple times.
To overcome this restrictions, you can use a flag-like variable, according to this:
clear the flag variable, say ARR_FLAG, before the setlocal command: set "ARR_FLAG=";
define the flag variable inside of the setlocal/endlocal block, that is, assign a non-empty value to it (immediately before the for /F loop preferrably): set "ARR_FLAG=###";
change the if defined command line to: if defined ARR_FLAG (;
then you can also do optionally:
change the for /F option string to "delims=";
change the set command line in the for /F loop to: set "%%V";
Something like the following (I haven't tested it):
#echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
pushd %scripts%
endlocal
call .\activate.bat
popd
Since the above doesn't work (see Marcelo's comment), I would probably do this as follows:
set uniquePrefix_base=compute directory
set uniquePrefix_pkg=compute sub-directory
set uniquePrefix_scripts=%uniquePrefix_base%\%uniquePrefix_pkg%\Scripts
set uniquePrefix_base=
set uniquePrefix_pkg=
call %uniquePrefix_scripts%\activate.bat
set uniquePrefix_scripts=
where uniquePrefix_ is chosen to be "almost certainly" unique in your environment.
You could also test on entry to the bat file that the "uniquePrefix_..." environment variables are undefined on entry as expected - if not you can exit with an error.
I don't like copying the BAT to the TEMP directory as a general solution because of (a) the potential for a race condition with >1 caller, and (b) in the general case a BAT file might be accessing other files using a path relative to its location (e.g. %~dp0..\somedir\somefile.dat).
The following ugly solution will solve (b):
setlocal
set scripts=...whatever...
echo %scripts%>"%TEMP%\%~n0.dat"
endlocal
for /f "tokens=*" %%i in ('type "%TEMP%\%~n0.dat"') do call %%i\activate.bat
del "%TEMP%\%~n0.dat"
For surviving multiple variables: If you choose to go with the "classic"
ENDLOCAL & SET VAR=%TEMPVAR% mentioned sometimes in other responses here (and are satisfied that the drawbacks shown in some of the responses are addressed or are not an issue), note that you can do multiple variables, a la ENDLOCAL & SET var1=%local1% & SET var2=%local2%.
I share this because other than the linked site below, I have only seen the "trick" illustrated with a single variable, and like myself some may have incorrectly assumed that it only "works" for a single variable.
Docs: https://ss64.com/nt/endlocal.html
To answer my own question (in case no other answer comes to light, and to avoid repeats of the one I already know about)...
Create a temporary batch file before calling endlocal that contains the command to call the target batch file, then call and delete it after endlocal:
echo %scripts%\activate.bat > %TEMP%\activate.bat
endlocal
call %TEMP%\activate.bat
del %TEMP%\activate.bat
This is so ugly, I want to hang my head in shame. Better answers are most welcome.
How about this.
#echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
(
endlocal
"%scripts%\activate.bat"
)

Resources