I've been wrestling trying to get the syntax right on this batch file and I cannot figure out why some things aren't working.
The variable i is not getting incremented each time I do it.
Concatenation on strc doesn't seem to concatenate.
Here is my code:
set i=0
set "strc=concat:"
for %%f in (*.mp4) do (
set /a i+=1
set "str=intermediate%i%.ts"
set strc="%strc% %str%|"
ffmpeg -i "%%f" -c copy -bsf:v h264_mp4toannexb -f mpegts "%str%"
)
set strc="%strc:-1%"
ffmpeg -i "%strc%" -c copy -bsf:a aac_adtstoasc Output.mp4
You are not the first, who fell into the famous "delayed expansion trap" (and you won't be the last).
You need delayed expansion if you want to use a variable, that you changed in the same block (a block is a series of commands within parentheses (and )).
Delayed variables are referenced with !var! instead of %var%.
Reason is the way, cmd parses the code. A complete line or block is parsed at once, replacing normal variables with their value at parse time. Delayed variables are evaluated at runtime.
Two simple batch files to demonstrate:
setlocal EnableDelayedExpansion
set "var=hello"
if 1==1 (
set "var=world"
echo %var% !var!
)
setlocal EnableDelayedExpansion
for /L %%i in (1,1,5) do (
echo %random% !random!
)
Note: A line is also treated as a block:
set "var=old"
set "var=new" & echo %var%
With delayed expansion:
setlocal EnableDelayedExpansion
set "var=old"
set "var=new" & echo !var!
Delayed expansion is per default turned off at the command prompt. If you really need it, you can do:
cmd /V:ON /C "set "var=hello" & echo !var!"
Also there is a way to do the same without delayed expansion (but call costs some time, so it's slower, but if for some reason you can't / don't want to use delayed expansion, it's an alternative):
setlocal DISabledelayedexpansion
for /L %%i in (1 1 5) do (
call echo %random% %%random%%
)
Both methods can also be used to display array-like variables:
(This is often asked like "variable which contains another variable" or "nested variables")
Here is a collection for using such array-like variables in different situations:
With delayed expansion:
setlocal ENableDelayedExpansion
set "num=4"
set "var[%num%]=HELLO"
echo plain delayed: !var[%num%]!
for /L %%i in (4 1 4) do (
echo for delayed: !var[%%i]!
set a=%%i
call echo for delayed with variable: %%var[!a!]%%
)
without delayed expansion:
setlocal DISableDelayedExpansion
set "num=4"
set "var[%num%]=HELLO"
call echo plain called: %%var[%num%]%%
for /L %%i in (4 1 4) do (
call echo FOR called: %%var[%%i]%%
set a=%%i
call echo FOR called with variable: %%var[%a%]%%
)
Note: setlocal has no effect outside of batchfiles, so delayedexpansion works only:
In batch files
When the cmd was started with delayed expansion enabled (cmd /V:ON) (by default, the cmd runs with delayed expansion disabled)
(Follow the links, when you are interested in the technical background or even the advanced technical stuff)
Related
I've been wrestling trying to get the syntax right on this batch file and I cannot figure out why some things aren't working.
The variable i is not getting incremented each time I do it.
Concatenation on strc doesn't seem to concatenate.
Here is my code:
set i=0
set "strc=concat:"
for %%f in (*.mp4) do (
set /a i+=1
set "str=intermediate%i%.ts"
set strc="%strc% %str%|"
ffmpeg -i "%%f" -c copy -bsf:v h264_mp4toannexb -f mpegts "%str%"
)
set strc="%strc:-1%"
ffmpeg -i "%strc%" -c copy -bsf:a aac_adtstoasc Output.mp4
You are not the first, who fell into the famous "delayed expansion trap" (and you won't be the last).
You need delayed expansion if you want to use a variable, that you changed in the same block (a block is a series of commands within parentheses (and )).
Delayed variables are referenced with !var! instead of %var%.
Reason is the way, cmd parses the code. A complete line or block is parsed at once, replacing normal variables with their value at parse time. Delayed variables are evaluated at runtime.
Two simple batch files to demonstrate:
setlocal EnableDelayedExpansion
set "var=hello"
if 1==1 (
set "var=world"
echo %var% !var!
)
setlocal EnableDelayedExpansion
for /L %%i in (1,1,5) do (
echo %random% !random!
)
Note: A line is also treated as a block:
set "var=old"
set "var=new" & echo %var%
With delayed expansion:
setlocal EnableDelayedExpansion
set "var=old"
set "var=new" & echo !var!
Delayed expansion is per default turned off at the command prompt. If you really need it, you can do:
cmd /V:ON /C "set "var=hello" & echo !var!"
Also there is a way to do the same without delayed expansion (but call costs some time, so it's slower, but if for some reason you can't / don't want to use delayed expansion, it's an alternative):
setlocal DISabledelayedexpansion
for /L %%i in (1 1 5) do (
call echo %random% %%random%%
)
Both methods can also be used to display array-like variables:
(This is often asked like "variable which contains another variable" or "nested variables")
Here is a collection for using such array-like variables in different situations:
With delayed expansion:
setlocal ENableDelayedExpansion
set "num=4"
set "var[%num%]=HELLO"
echo plain delayed: !var[%num%]!
for /L %%i in (4 1 4) do (
echo for delayed: !var[%%i]!
set a=%%i
call echo for delayed with variable: %%var[!a!]%%
)
without delayed expansion:
setlocal DISableDelayedExpansion
set "num=4"
set "var[%num%]=HELLO"
call echo plain called: %%var[%num%]%%
for /L %%i in (4 1 4) do (
call echo FOR called: %%var[%%i]%%
set a=%%i
call echo FOR called with variable: %%var[%a%]%%
)
Note: setlocal has no effect outside of batchfiles, so delayedexpansion works only:
In batch files
When the cmd was started with delayed expansion enabled (cmd /V:ON) (by default, the cmd runs with delayed expansion disabled)
(Follow the links, when you are interested in the technical background or even the advanced technical stuff)
I've created bat file named vk.bat . The code is as following :-
SET "tcs="
FOR %%A IN (%*) DO (
Set "tcs=%tcs% -t %%A"
)
Echo %tcs%
I am executing this bat from cmd as following :-
c:\vk.bat Apple Cat Play
I want the final string as " -t Apple -t Cat -t Play"
But I am getting final string as "-t Play" . I am not able to find out that why and how it's overwriting the previous contents of string tcs.
DelayedExpansion-free Solution
Using the call command, we can emulate the delayed expansion. Take a look at this example
call command %%foo%% = setlocal enableDelayedExpansion
command !foo!
According to your case, the code should be changed to this:
SET "tcs="
FOR %%G IN (%*) DO (
CALL SET "tcs=%%tcs%% -t %%G"
)
ECHO %tcs%
Some Buggy Behaviour With CALL
Call doesn't handle redirection characters and some special characters properly:
&
|
<
>
"foo^bar"
The redirection of the first 4 examples won't function as intended, while in the last example, the caret(^) will be doubled. Source: SS64
You have fallen into the delayed expansion trap, like Squashman mentioned.
What Is Delayed Expansion?
In earlier batch files, variables are expanded(changed to it's value) when each line is phrased.
The command processor treats the entire for loop as one command, so the variable %tcs% is treated as nothing. (Because tcs was previously set to nothing.
How To Make Batch Variables Get Expanded At Run-Time?
To preserve compatibility with older batch files, delayed expansion feature is added. To allow the processor to do so, add this line to the start of the batch file:
setlocal enableDelayedExpansion
and we also need to tell the processor which variables to be expanded at run-time, to do so, change the following:
From -> To
%var% -> !var!
Note only %n% variables can be changed to !n!. Metavariables like %%G/%G and %0 cannot be changed.
Here's is the fixed code using delayed expansion.:
SETLOCAL EnableDelayedExpansion
SET "tcs="
FOR %%G IN (%*) DO (
SET "tcs=!tcs! -t %%G"
)
ECHO %tcs%
I have a use case where I want to replace %%20 which is part of a string for example: "Calculation%%20One". I want to replace this with "Calculation One".
This where I am stuck:
#echo off
setlocal enableextensions disabledelayedexpansion
>"temp" (
echo !Option1!
echo !Option2!
echo !Option3!
)
set "pattern=%%20"
for /f "usebackq delims=" %%a in ("temp") do (
echo data: %%a
set "line=%%a"
setlocal enabledelayedexpansion
if "!line:%pattern%=!"=="!line!" (
set string=!Option1!
set string2=%!string1!:%%20= %
) else (
set string2=%%a
)
endlocal
)
del /q tempFile
Can someone please help me with this? I have a program which is a combination of batch and python.
Thanks
It is unclear for me why the options are written into a temporary file for processing the values of the environment variables Option1, Option2 and Option3. And it would have been good to see the lines which define the environment variables Option1, Option2 and Option3 for the real values assigned to them.
So I suggest here a batch file which replaces %20 as found for example in HTML files or emails by a space character in all Option environment variables.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "Option1=Calculation%%20One"
set "Option2=Calculation %%%%20Two!"
set "Option3=Any other string"
cls
echo The options before processing:
echo/
set Option
for /F "delims==" %%I in ('set Option 2^>nul') do call :ProcessOption "%%I"
echo/
echo The options after processing:
echo/
set Option
echo/
endlocal
pause
goto :EOF
:ProcessOption
setlocal EnableDelayedExpansion
set "ModifiedOption=!%~1:%%20= !"
endlocal & set "%~1=%ModifiedOption%"
goto :EOF
It is necessary to use delayed expansion on replacing all occurrences of %20 by a space character because otherwise Windows command interpreter would not know where the environment variable reference with string substitution ends. Replacing a string with percent sign is not possible using immediate expansion for that reason.
The command set Option outputs alphabetically sorted all environment variables starting case-insensitive with the string Option in format variable=value as it can be seen twice on running this batch file.
FOR executes this command with using a separate command process started in background and captures all lines written by command SET to handle STDOUT.
The string 2^>nul is executed finally as 2>nul and redirects the error message output by command SET to handle STDERR to the device NUL to suppress it. SET would output an error message in case of no environment variable starting with Option is defined in current environment. This error condition does not occur with this batch file. Read the Microsoft article about Using Command Redirection Operators. The redirection operator > must be escaped here with ^ to be interpreted first as literal character when Windows command interpreter processes the entire FOR command line before executing the command FOR which executes cmd.exe /c set Option 2>nul in background.
FOR processes the captured output of set Option line by line and splits each line up into substrings (tokens) with using the equal sign as delimiter as specified with delims==. The string left to first equal sign on each line is assigned to loop variable I. This is the name of the environment variable, i.e. Option1, Option2 and Option3 for this batch file.
With each OptionX environment variable name the subroutine ProcessOption is called for replacing all %20 in its value by a space character.
Here is a solution for environment variables with one or more exclamation marks in variable name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "!Option1!=Calculation%%%%20One"
set "!Option2!=City%%20Country"
set "!Option3!=State%%%%20Country"
set "!Option4!=Any other address/string"
cls
echo The options before processing:
echo/
set !Option
for /F "delims==" %%I in ('set !Option 2^>nul') do call :ProcessOption "%%I"
echo/
echo The options after processing:
echo/
set !Option
echo/
endlocal
pause
goto :EOF
:ProcessOption
call set "ModifiedOption=%%%~1%%"
setlocal EnableDelayedExpansion
set "ModifiedOption=!ModifiedOption:%%%%20= !"
set "ModifiedOption=!ModifiedOption:%%20= !"
endlocal & set "ModifiedOption=%ModifiedOption%"
set "%~1=%ModifiedOption%"
set "ModifiedOption="
goto :EOF
This batch code replaces %20 AND %%20 in all environment variables starting with !Option in name by a space character in comparison to above replacing just %20 by a space in environment variables beginning with Option in name.
It is of course a bad idea to use characters in variable names which have a special meaning for Windows command interpreter like the characters &()[]{}^=;!'+,`~|<>%. It is possible as demonstrated above, but it is definitely not a good idea.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
cls /?
echo /?
endlocal /?
for /?
goto /?
set /?
setlocal /?
See also answer on Single line with multiple commands using Windows batch file for an explanation of & operator.
And read DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ why it is better to use echo/ instead of echo. to output an empty line.
Using Server 2016 here with simple batch-file programming (for now).
I have what I think is an easy problem, just that I am not seeing a workable solution in front of me right now. I'm going to limit my request right down to a testing scenario to keep it simple. What I am trying to do is trim a string from the right until it hits the current year in the format "_%year%-" where the year does come from a variable elsewhere, but is set static in my example. Where I am running into issues is referring to a variable from within the code I have.
This is a working example NOT using a variable on the rem line, and gives the desired output of "Machined_Cam-2286:"
#ECHO OFF
SETLOCAL DisableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set testString=%testString:_2017-=&rem.%
echo %testString%
pause
You can see that "_2017-" hard-coded in on the 10th line. What I am looking to do in a purely logical sense on the rem line specifically:
set testString=%testString:%yearModified%=&rem.%
Because of the way this command modifies testString in-line, it makes it difficult to inject a variable into it. I have tried a huge combination of escapes and expansion settings to get the variable to take with no success so far. I have also tried to "build" the command as a string and attempt to call it and pipe the output to a variable:
#ECHO OFF
SETLOCAL DisableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set "callCMD=%%testString:%yearModified%=^&rem.%%"
call %callCMD% > %testString%
echo %testString%
pause
This seems like such a simple issue, but my lack of understanding of string manipulation under DOS is apparent. As is stands, the rest of the script is running perfectly with the "_2017-" hard-coded. It would be nice to eliminite that bit of maintenance.
Any help or direction is appreciated.
Thank you.
May I show you another method of splitting. Replace your "Delimiterstring" with a proper Delimiter and use a for to split (for uses single-letter delimiters). Enabling delayed expansion helps, but is not neccessary. As you explicitely disabled it (you may have your reasons), I'll show you both:
#echo off
set "TestString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "CutHere=_2017"
setlocal enabledelayedexpansion
for /f "delims=§" %%a in ("!TestString:%CutHere%=§!") do set Result=%%~a
echo enabled: %Result%
endlocal
setlocal disabledelayedexpansion
for /f "delims=§" %%a in ('call echo "%%TestString:%CutHere%=§%%"') do set Result=%%~a
echo disabled: %Result%
endlocal
Just be sure, you use a delimiter (§ here) that surely won't be in your string(s).
you are trying to control what you call using the contents of a variable
try using a temp file instead -
echo %%testString:%yearModified%=^&rem.%%" > temp.bat
call temp.bat > %testString%
You NEED delayed expansion for this type of operation, so enabe it.
I suggest to first get the remainder of the string
and remove this from the original string.
#ECHO OFF
SETLOCAL EnableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set tempString=!testString:*%yearModified%=!
set testString=!testString:%yearModified%%tempstring%=!
echo %testString%
pause
Sample output:
Machined_Cam-2286
#echo off
setlocal EnableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set testString=!testString:%yearModified%=^&rem.!
echo %testString%
pause
You shoud use a carrot(^) in Delayed Expansion mode.
I need to read all the text files from a directory and then replace a certain type of string (that comes in a line having 'volumelabel') with another (replacewith) in each of them. Following is the code snippet:
for /r %%g in (*.txt) do (
set filename=%%~nxg
for /F "tokens=3 delims=<>" %%i in ('findstr "volumelabel" !filename!') do (
set tobereplaced=%%i
)
echo !filename! has !tobereplaced! to be replaced by %replacewith%
for /F "tokens=*" %%a in (!filename!) do (
set str=%%a
set str=!str:!tobereplaced!=%replacewith%!
echo !str!>>new!filename!
)
)
Now the problem I am facing is it prints only tobereplaced (literally) in every line of the new files when
set str=!str:!tobereplaced!=%replacewith%!
echo !str!>>new!filename!
is used and prints tobereplaced=replacewith (values) when
set str=%str:!tobereplaced!=%replacewith%%
echo !str!>>new!filename!
is used. Can someone help me?
The least convoluted solution (in my humble opinion) is to use a subroutine to set str. Going more than one level deep of delayed expansion tends to cause severe brain hurt. Oh, you could probably fix your set str line by doing something like
call call call set str=%%%%str:%%tobereplaced%%=%replacewith%%%%%
...or similar. See what I mean about brain hurt? It's hard to follow the recursion.
So here's my suggestion for a solution. I also fixed another potential problem or two while I was at it. Since you're doing a recursive search for *.txt, I made the for loops able to work with whatever text files they find within subdirectories. I haven't tested this, so let me know if you get any grotesque errors.
#echo off
setlocal enabledelayedexpansion
set replacewith=whatever
for /r %%g in (*.txt) do (
set newfile=%%~dpng.new%%~xg
for /F "tokens=3 delims=<>" %%i in ('findstr "volumelabel" "%%g"') do (
set "tobereplaced=%%i"
echo %%~nxg has !tobereplaced! to be replaced by %replacewith%
rem combining your for loops this way makes the second only fire if the first is true
rem using "findstr /n" in your for loop preserves blank lines
for /F "delims=" %%a in ('findstr /n "^" "%%g"') do (
rem ...but you have to strip off the line numbers
set "str=%%a" && set "str=!str:*:=!"
rem "call :repl" to work around the delayed expansion conundrum
call :repl "!str!" "!tobereplaced!" "%replacewith%" str
echo !str!>>!newfile!
)
)
)
goto :EOF
:repl <line> <find> <replace> <var>
setlocal enabledelayedexpansion
set "line=%~1"
set "line=!line:%~2=%~3!"
set "%4=%line%"
goto :EOF
Caveat: If your text file contains exclamation marks, equal signs or carats, they might not make it into textfile.new.txt.
For what it's worth, if I were in your position, instead of using a batch file I would probably use sed (the binaries should be all you need). You wouldn't even need a script. You could do it as a one liner like this:
for /r %I in (*.txt) do sed -r "s/volumelabel/replacement/ig" "%I" > "%~dpnI.new%~xI"
By the way, see the last couple of pages of help for for an explanation of the %~dpnI sort of notation.