Batch file, string variable comparison, if statement - windows

I am trying to compare the logs of a file with a plain string, but it is not getting into if comparison. I am getting " Connect failed" as my 2nd token in echo statement, but not getting any result of IF statement.
#echo off
rem start cmd.exe
for /f "tokens=2 delims=:" %%n IN (C:\Users\rohit.bagjani\Desktop\result\telnetresult.txt) DO (
SET str1 = " Connect failed"
echo %%n
if \i %str1%==%%n echo "true"
)
echo.
pause

The first mistake is in line:
SET str1 = " Connect failed"
This line defines an environment variable with name str1  with a space at end of name with the value  " Connect failed" assigned to it. The leading space and the two double quotes are also assigned to the variable as part of the string.
As the answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? explains in detail, the right syntax would be:
set "str1=Connect failed"
This command line defines an environment variable str1 with the value Connect failed assigned to it.
Run in a command prompt window set /? to get displayed the help for this command on several display pages.
The second mistake is in line:
if \i %str1%==%%n echo "true"
Options/switches are on Windows specified with / and \ is used as directory separator. So the switch for case-insensitive comparison must be /i and not \i.
Run in a command prompt window if /? for help on IF command.
The third mistake is the attempt to define an environment variable within a command block with assigning a string value to the environment variable and reference the value of this environment variable not using delayed expansion in same command block.
Whenever Windows command interpreter encounters an opening round bracket ( being interpreted as begin of a command block, it parses everything to matching parenthesis ) and replaces all environment variable references done with %VariableName% by current value of the environment variable.
In posted code this means the line
if \i %str1%==%%n echo "true"
is changed by Windows command interpreter to
if \i == %n echo "true"
before FOR is executed at all because of environment variable str1 is not defined above the FOR command block.
This can be easily seen by changing echo off to echo on or remove the line with echo off or comment it out with command rem and run the batch file from within a command prompt window. Then Windows command interpreter outputs each command block and each command line after preprocessing before execution.
Double clicking on a batch file to execute it is not good as the window is automatically closed on an exit of batch processing because of a syntax error like this one. The usage of pause is no help as this command line is not reached at all on detecting a syntax error by cmd.exe.
A solution would be:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq tokens=2 delims=:" %%I in ("%USERPROFILE%\Desktop\result\telnetresult.txt") do (
set "str1=Connect failed"
echo %%I
if /I "!str1!" == "%%~I" echo true
)
endlocal
echo/
pause
But much easier and also working would be:
#echo off
for /F "usebackq tokens=2 delims=:" %%I in ("%USERPROFILE%\Desktop\result\telnetresult.txt") do (
echo %%I
if /I "Connect failed" == "%%~I" echo true
)
echo/
pause
For the reason using echo/ instead of echo. to output an empty line see What does an echo followed immediately by a slash do in a Windows CMD file?
The usage of I or any other upper case letter instead of n as loop variable is more safe. Why? Run in a command command window for /? and read the output help explaining also %~nI. On usage of %%~n in a batch file it could be unclear for Windows command interpreter if the current value of loop variable n should be used with surrounding double quotes removed or there is a syntax error as the loop variable is missing after modifier ~n. Loop variables are case-sensitive. The usage of upper case letters avoids conflicts in interpretation of loop variables with modifiers.
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.
echo /?
endlocal /?
for /?
if /?
pause /?
set /?
setlocal /?
See Wikipedia article about Windows Environment Variables for a list of predefined environment variables with description like USERPROFILE.

Related

Reading lines from a txt file into variables in batch

I am trying to figure out how to read IP addresses from a file named "IPList.txt) into individual variables in a batch script. Here's what I have so far.
:DEFINITIONS
set LOGFILE=IPScript.log
set IPLIST=C:\IPLIST.txt
echo Script Started >> %LOGFILE%
goto SetIP
:SetIP
for /f "tokens=*" %%a in (%IPLIST%) do (
set FirstIP=%%a
)
echo The first IP is %FirstIP% >> %LOGFILE%
exit
The output I'm getting in "IPscript.log" is "The First IP is: " with no IP listed, just a space. Also, is there a way for me to set multiple IPs like this, in just one for loop?
Here's a quick example to assist you:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
:DEFINE_LOCAL_VARIABLES
Set "IPLIST=C:\IPLIST.txt"
Set "LOGFILE=IPScript.log"
:CHECK_SOURCE_EXISTS
For %%G In ("%IPLIST%") Do If "%%~aG" Lss "-" (
Echo The file %IPLIST% does not exist.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
) Else If "%%~aG" GEq "d" (
Echo Expected a file, but %IPLIST% is a directory.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
)
:UNDEFINE_LOCAL_VARIABLES
For /F "Delims==" %%G In ('"(Set IP[) 2> NUL"') Do Set "%%G="
:START_MAIN
Set "i=1000"
(
Echo Script Started
For /F UseBackQ^ Delims^=^ EOL^= %%G In ("%IPLIST%") Do (
Set /A i += 1
SetLocal EnableDelayedExpansion
For %%H In ("!i:~-3!") Do (
EndLocal
Set "IP[%%~H]=%%G"
Echo IP[%%~H] is %%G
)
)
) 1> "%LOGFILE%"
:CHECK_IP_VARIABLES_EXIST
If Not Defined IP[001] (
Echo %IPLIST% had no readable file content.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
)
:VIEW_IP_VARIABLES
Set IP[
Pause & GoTo :EOF
If you have an existing %LOGFILE%, and you intend to append to it, (as opposed to overwrite/create one), change 1> "%LOGFILE%" to 1>> "%LOGFILE%".
If you didn't really need %LOGFILE%, e.g. it was used by you just for testing, it would look a little more like this:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
:DEFINE_LOCAL_VARIABLES
Set "IPLIST=C:\IPLIST.txt"
:CHECK_SOURCE_EXISTS
For %%G In ("%IPLIST%") Do If "%%~aG" Lss "-" (
Echo The file %IPLIST% does not exist.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
) Else If "%%~aG" GEq "d" (
Echo Expected a file, but %IPLIST% is a directory.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
)
:UNDEFINE_LOCAL_VARIABLES
For /F "Delims==" %%G In ('"(Set IP[) 2> NUL"') Do Set "%%G="
:START_MAIN
Set "i=1000"
Echo Script Started
For /F UseBackQ^ Delims^=^ EOL^= %%G In ("%IPLIST%") Do (
Set /A i += 1
SetLocal EnableDelayedExpansion
For %%H In ("!i:~-3!") Do (
EndLocal
Set "IP[%%~H]=%%G"
)
)
:CHECK_IP_VARIABLES_EXIST
If Not Defined IP[001] (
Echo %IPLIST% had no readable file content.
Echo Press any key to end this script.
Pause 1> NUL
GoTo :EOF
)
:VIEW_IP_VARIABLES
Set IP[
Pause & GoTo :EOF
The last line in both examples is for display purposes. If you're testing/running this script from within cmd.exe, you may omit it.
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r ".*" "%filename1%"') DO set "IP%%a=%%b"
)
set IP
findstr reads the file in filename1 and produces a list of the format n:content of line n.
The for /f reads this list, and partitions it using 2 tokens - %%a gets the first token (1) and %%b the remainder of the line (*) using : as a delimiter.
So simply set the IP variables from there.
set ip displays all variables that start ip
Probability is that your file contains empty line(s) after the last IP. Your original code would have reported the LAST IP, not the FIRST as the value in firstip is overwritten on each iteration, so it would be cleared by being set to nothing when the empty lines are read.
The solution above would simply execute (eg) set "IP6=" under these circumstances, clearing the variable.
You could have obtained the first IP by using
if not defined firstip set "FirstIP=%%a"
I'm assuming a clean environment here - that is, that each batch you run includes a setlocal after the #echo off (which restores the initial environment when the batch finishes) and the variables used are known-empty.
Bonus:
changing the set command to
set "IP%%a=%%b"&if "%%b" neq "" set "ipmax=%%a"
would set ipmax to the number of the last non-empty line, as %%b is empty for an empty line.
The batch file could have following command lines:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LOGFILE=%~dp0IPScript.log"
set "IPLIST=%~dp0IPLIST.txt"
set "AddressCount=0"
echo Script started>"%LOGFILE%"
for /F "delims==" %%I in ('set IP_Address_ 2^>nul') do set "%%I="
if exist "%IPLIST%" for /F "useback delims=" %%I in ("%IPLIST%") do (
set /A AddressCount+=1
call set "IP_Address_%%AddressCount%%=%%I"
)
if not %AddressCount% == 0 (
if %AddressCount% == 1 (
echo The IP address is:
) else echo The IP addresses are:
echo/
set IP_Address_
) >>"%LOGFILE%"
endlocal
The batch file first two command line define the execution environment which means:
Disable command echo mode.
Push current command extension state on stack and enable command extensions.
Push current delayed expansion state on stack and disable delayed environment variable expansion.
Push path of current directory on stack.
Push pointer to current list of environment variables on stack and create a copy of the entire current environment variables list to use next.
The third and fourth line define two environment variables with the name of the log file and the name of the IP address list file with full qualified file name. The file path of both files is defined as path of the directory containing the batch file referenced with %~dp0. This path always ends with \ and for that reason no additional backslash is needed on concatenating this path with the two file names.
The fifth line define the environment variable AddressCount with value 0.
The sixth line creates the log file in current directory with overwriting an already existing log file. There is no space left to redirection operator > as this space would be output by command ECHO and therefore written as trailing space also into the log file.
The first FOR command with option /F starts in background with %ComSpec% /c one more command process with the command line between ' appended as additional arguments. So executed is in background with Windows installed into C:\Windows:
C:\Windows\System32\cmd.exe /c set IP_Address_ 2>nul
Windows creates a copy of current list of environment variables for the command process started in background. The background command process runs command SET to output all environment variables with name, an equal sign and the string value assigned to the variable line by line of which name starts with IP_Address_. This output to handle STDOUT of background command process is captured by FOR respectively the command process which is processing the batch file. The error message output by SET on no environment variable define with a name starting with IP_Address_ is redirected from handle STDERR to device NUL to suppress this error message.
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.
FOR processes the captured output line by line after started background command process closed itself after execution of command SET. Empty lines are always ignored by FOR which can be ignored as there are no empty lines output by SET.
FOR would split up by default the current line into substrings using normal space and horizontal tab as delimiters. This default line splitting behavior is not wanted here. The option delims== defines the equal sign as string delimiter to split the line on = which is the character between variable name and variable value.
FOR would next ignore the line if the first substring would start with a semicolon which is the default end of line character. The command SET outputs only lines starting with IP_Address_ and for that reason the default eol=; can be kept in this case.
FOR assigns just the first substring to the specified loop variable I as tokens=1 is the default. That is exactly the wanted behavior in this case.
So FOR assigns one environment variable name starting with IP_Address_ to loop variable I and runs next the command SET to delete this environment variable in current list of environment variables of command process processing the batch file.
In other words the first FOR is for deletion of all environment variables of which name starts with IP_Address_ defined by chance outside the batch file.
The next line first checks if the file with the list of environment variables exists at all in directory of the batch file. In this case once again FOR is used to process lines, but this time read line by line from the specified list file instead of captured output of a background command process. The usage of " instead of ' with the option usebackq makes the difference.
There is used the option delims= to define an empty list of delimiters resulting in getting each non-empty line not starting with ; assigned completely to the specified loop variable I.
For each string assigned to loop variable I the current value of environment variable AddressCount is incremented by one using an arithmetic expression evaluated by command SET.
This value is used on next command line to define an environment variable of which name starts with IP_Address_ and has appended the current address count value with line read from file assigned to the environment variable.
There is usually used delayed expansion for such tasks on which the second command line in command block of second FOR loop would be:
set "IP_Address_!AddressCount!=%%I"
But the code above uses the alternative method with command call to parse set "IP_Address_%%AddressCount%%=%%I" a second time which was already modified to set "IP_Address_%AddressCount%=%I" before the IF condition left to FOR was executed at all.
The next IF condition checks if any line was read from the list file with the IP addresses. In this case first an information line is output depending on having read exactly one line from the file or more than one line. Then an empty line is output and last all environment variables of which name starts with IP_Address_ with = and the line (IP address) assigned to the environment variable. All this output is appended to the log file.
The last command restores previous execution environment which means:
Discard the current list of environment variables and pop from stack the pointer to initial list of environment variables resulting in restoring the initial list of environment variables. In other words all environment variables defined or modified by the batch file after command SETLOCAL in second command line are lost forever.
Pop path of current directory from stack and make this directory again the current directory. The current directory between setlocal and endlocal was not changed by the code between and so this does not matter here.
Pop delayed expansion state from stack and enable or disable delayed environment variable expansion accordingly to restore initial delayed expansion behavior.
Pop current command extension state from stack and enable or disable command extensions accordingly to restore initial command extension behavior.
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 /?
echo /?
endlocal /?
for /?
if /?
set /?
setlocal /?
See also:
Variables are not behaving as expected
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Microsoft's documentation for the Windows Commands
SS64.com - A-Z index of Windows CMD commands

How to not lose special characters when processing a text file in a bat?

I have this text document (txt1.txt):
&()[]{}^=;!'+,`~.mp3
¿¡áéíóú!.mp3
When processing it with a FOR command I lose characters like "^" and "!".
The bat file is as follows (it has two FOR commands that maybe can be simplified into just one, and the second one has an ECHO command at the end that I don't know if it can be implemented better):
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
CHCP 1252
SET TXTFILE1=txt1.txt
SET TXTFILE2=txt2.txt
(
FOR /F "usebackq delims=" %%I IN (%TXTFILE1%) DO (
SET LINE=%%I
ECHO !LINE:.mp3=!
)
) > %TXTFILE2%
SET LINENUMBER=1
SET TXTFILE3=txt3.bat.txt
FOR /F "usebackq eol=| delims=" %%J IN (%TXTFILE2%) DO (
ECHO SET TRACK!LINENUMBER!=%%J>> %TXTFILE3%
SET /A LINENUMBER+=1
)
ECHO EXIT /B>> %TXTFILE3%
ENDLOCAL
PAUSE > NUL | SET /P =Presione una tecla para salir . . . & ECHO(
EXIT
Use this code for the batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFile=txt1.txt"
set "OutputFile=txt2.txt"
set "LineNumber=0"
(
for /F usebackq^ delims^=^ eol^= %%I in ("%SourceFile%") do (
set "Line=%%I"
set /A LineNumber+=1
setlocal EnableDelayedExpansion
echo set TRACK!LineNumber!=!Line:.mp3=!
endlocal
)
) >"%OutputFile%"
endlocal
For an explanation of the FOR loop see my answer on How to read and print contents of text file line by line? Empty lines in source file are ignored by FOR which should be no problem here as it looks like the source file contains a list of MP3 file names.
The question does not contain an example content of source file and an example of expected content of output file. So the code was tested with a source file created by myself which are the lines as posted at How to read and print contents of text file line by line? with some lines with .mp3 appended and one line with ^ added.
If all lines end with .mp3 it would be better to use:
echo set TRACK!LineNumber!=!Line:~0,-4!
The line read from file is in this case output without the last four characters.
It is important to have delayed expansion NOT enabled on assigning the line read from file to environment variable Line. Otherwise this line is parsed by cmd.exe a second time as explained by How does the Windows Command Interpreter (CMD.EXE) parse scripts? On second parsing of SET LINE=%%I each ^ in line read from file is interpreted as escape character and each ! is interpreted as begin/end of an environment variable reference.
For that reason it is necessary to assign first the line read from file to environment variable Line while delayed expansion is disabled to avoid parsing the line before execution command SET by Windows command processor. Then delayed environment variable expansion is enabled which results in creating a copy of current list of environment variables, pushing current directory path on stack and pushing also current states of command extensions and delayed environment variable expansion on stack before enabling also delayed expansion. Then the line can be output with the additional text at beginning with line number and with case-insensitive removing all .mp3 occurrences from line. Finally command ENDLOCAL must be used to discard the copy of all environment variables, pop current directory path back from stack and pop also the states of command extensions and delayed expansion from stack and set those two features accordingly which means here disabling delayed expansion. Read this answer for details about the commands SETLOCAL and ENDLOCAL.
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.
echo /?
endlocal /?
for /?
set /?
setlocal /?

Batch file seems to be setting a variable to 2 for no reason

I'm making a simple batch script to figure out arrays in batch script.
The code:
#echo off
setlocal EnableDelayedExpansion
set inputCount=0
set outputCount=0
:input
cls
set /p !number%inputCount%!=Input %inputCount%:
set /a inputCount=%inputCount%+1
if %inputCount% geq 3 goto output
goto input
:output
cls
echo !number%outputCount%!
set /a outputCount=%outputCount%+1
if %outputCount% geq 3 goto exit
goto output
:exit
pause
echo exit
On line 4, I set outputCount to 0, I then don't change outputCount until line 16 where I add 1 to it.
I expected the output of line 16 to be outputCount=0+1=1 therefore making outputCount=1. However, when I run the code with echo on to see exactly what it's doing, the output for line 16 is outputCount=2+1=3 setting outputCount to 3.
It seems that the program is setting outputCount to 2 instead of 0 at some point before line 16 but I can't see why.
First, take a look on Debugging a batch file as this is a lesson you need to learn on coding a batch file.
Second, read the answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? which offers additional information to the help output on running in a command prompt window set /?.
It looks like you want to define the environment variables number0, number1 and number2 with a string assigned to them by user input.
The command to use to prompt a user for a string is either
set /P "variable=prompt text: "
or
set /P variable="prompt text: "
The first variant is in general recommended although most often not used by batch file coding newbies because of not knowing how to use the double quotes right on assigning a string to an environment variable. The second variant is specific for set /P also possible and in some very rare cases really needed, but in my point of view should be avoided to use because of the double quotes are interpreted different on using set without /P.
So let us look on your code with commenting out with rem four lines, appending one more line with set at end of the batch file and run that batch file from within a command prompt window:
rem #echo off
setlocal EnableDelayedExpansion
set inputCount=0
set outputCount=0
:input
rem cls
set /p !number%inputCount%!=Input %inputCount%:
set /a inputCount=%inputCount%+1
if %inputCount% geq 3 goto output
goto input
:output
rem cls
echo !number%outputCount%!
set /a outputCount=%outputCount%+1
if %outputCount% geq 3 goto exit
goto output
:exit
rem pause
echo exit
set number
Output is at end just a line with NUMBER_OF_PROCESSORS=2. There are no environment variables number0, number1, number2 which would be also output by set number. And the command line echo !number%outputCount%! in file results three times in the information that ECHO is OFF.
The reason can be seen on looking on the output command lines really executed after preprocessing each line by Windows command interpreter.
set /p !number%inputCount%!=Input %inputCount%:
The string entered by the user, if not just RETURN or ENTER was hit by the user on prompt, should be assigned to the environment variables of which name are stored in the environment variables number0, number1 and number2. But the environment variables number0, number1 and number2 are never defined by your batch file as your intention is to store the input strings into the variables with name number0, number1 and number2. So this command line is finally on execution:
set /p =Input 0:
set /p =Input 1:
set /p =Input 2:
Those command lines would result in an exit of batch processing because of a syntax error, but this does not occur here because of usage of delayed expansion as it can be seen in the console window.
The solution is a batch code as follows:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Index=0"
:Input
cls
set /A Index+=1
set /P "Number%Index%=Input %Index%: "
if not %Index% == 3 goto Input
cls
set "Index=0"
:Output
set /A Index+=1
if defined Number%Index% echo Number%Index%=!Number%Index%!
if not %Index% == 3 goto Output
endlocal
The output of this batch file on entering on first prompt Hello!, on second prompt nothing and on third prompt Bye! is:
Number1=Hello!
Number3=Bye!
Okay, we have not entered a number as we have the freedom to type anything from nothing to bad strings like Double quote " or | or < or > are bad inputs on user prompt on being prompted for an input. But the batch file works as expected now.
Further please note that the string after set /A is an arithmetic expression which is parsed completely different to any other string on a command line. For examples the current values of environment variables can be referenced by using just the variable name without surrounding percent signs or exclamation marks. That make it possible to use variables in arithmetic expression within an IF or FOR command block without usage of delayed expansion. The help output on running set /? in a command prompt window explains this different parsing behavior quite good as well as which operators can be used like += in the arithmetic expression.
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.
cls /?
echo /?
endlocal /?
goto /?
if /?
rem /?
set /?
setlocal /?
Some more hints:
Don't use just exit in a batch file, use exit /B or goto :EOF, see Where does GOTO :EOF return to?
After verification that the user entered anything at all and the entered string is really a number (decimal, octal or hexadecimal), make sure to process the number right according to your and the users' expectations. What I mean here is demonstrated with:
#echo off
set "Number=020"
set /A Number+=1
echo Result=%Number%
pause
What do you expect as result, 21 or the real output result 17?
A number string starting with 0 is interpreted as octal number. A number string starting with 0x or 0X is interpreted as hexadecimal number. Change 020 to 008 and the result is 1. Why? 008 is invalid for an octal number and therefore replaced by 0 which is incremented by one.

Extracting file name from array element in batch

I have an environment variable like this
set BINARY[0]=C:\binary.bin
From which I'm trying to extract the full file name
set "x=0"
:binloop
if defined BINARY[%x%] (
call echo %%BINARY[%x%]%%
FOR %%i IN ("%%BINARY[%x%]%%") DO (
set FNAME=%%~nxi
)
set /a "x+=1"
GOTO binloop
)
rem ...
However for some reason, it tries to do:
set FNAME=%BINARY[0]%
instead of
set FNAME=binary.bin
What's wrong with the code and why?
Open a command prompt window, run set /? and read the output help pages explaining when and how to use delayed expansion in a code block for the commands IF and FOR.
%% in a batch file is interpreted as literal percent character which is the reason why a loop variable in a command executed directly in a command prompt window must be specified with just one percent sign while the same loop in a batch file requires two percent signs on referencing the loop variable.
When the Windows command processor encounters an opening parenthesis which marks the beginning of a command block, it searches for the matching closing parenthesis and replaces all environment variables references with syntax %VariableName% by the current value of the variable or nothing in case of variable does not exist. Then after the entire command block was parsed the IF or FOR is executed and used is once or more times the already preprocessed command block.
You could use
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "BINARY[1]=C:\binary1.bin"
set "BINARY[0]=C:\binary0.bin"
set "x=0"
:binloop
if defined BINARY[%x%] (
call echo %%BINARY[%x%]%%
for %%i in ("!BINARY[%x%]!") do (
set FNAME=%%~nxi
set FNAME
)
set /a "x+=1"
goto binloop
)
endlocal
which outputs
C:\binary0.bin
FNAME=binary0.bin
C:\binary1.bin
FNAME=binary1.bin
The command line
call echo %%BINARY[%x%]%%
is something special. This line is preprocessed before execution of command IF to
call echo %BINARY[0]%
respectively on second run to
call echo %BINARY[1]%
By usage of command CALL the single command line is processed like a subroutine or another batch file which means the line is preprocessed once more resulting in execution of
echo C:\binary0.bin
and on second run in execution of
echo C:\binary1.bin
which is the reason why the output is as expected here. But there is no double preprocessing for the environment variable reference in FOR.
Much better would be most likely the following code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "BINARY[1]=C:\binary1.bin"
set "BINARY[0]=C:\binary0.bin"
for /F "tokens=1* delims==" %%I in ('set "BINARY[" 2^>nul') do (
set "FNAME=%%~nxJ"
set FNAME
)
endlocal
The command set outputs all variables with their name and equal sign and their values which start with the specified string when there is whether parameter /A or /P used and the parameter does not contain an equal sign in an alphabetically sorted list. So the output of
set "BINARY[" 2>nul
as used in the command FOR is
BINARY[0]=C:\binary0.bin
BINARY[1]=C:\binary1.bin
which is processed by the FOR loop which splits each line into two strings based on first occurrence of the equal sign because of tokens=1* delims==. The first string is the variable name assigned to loop variable I. And the second string is everything after first equal sign assigned to loop variable J being the next character in ASCII table.
2>nul is used to suppress the error message output by command SET to STDERR by redirecting it to device NUL if there is no environment variable defined with a name starting with BINARY[ in any case. The redirection operator > must be escaped with ^ as otherwise command processor would exit batch processing on this line because of 2>nul resulting in a syntax error on FOR command line at this position.
Note: Because of alphabetically sorted output by command SET the environment variable BINARY[10] is output after BINARY[0] and before BINARY[1] and BINARY[2]. So if the order is important, the first batch solution is needed or the environment variables are created with number in square brackets have all same number of digits with leading zeros, i.e. 00000, 00001, ..., 00002, 00010, 00011, ...
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 /?
echo /?
endlocal /?
for /?
goto /?
if /?
set /?
setlocal /?
And see also Microsoft article about Using command redirection operators.

Hidden features of Windows batch files

Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
What are some of the lesser know, but important and useful features of Windows batch files?
Guidelines:
One feature per answer
Give both a short description of the feature and an example, not just a link to documentation
Limit answers to native funtionality, i.e., does not require additional software, like the Windows Resource Kit
Clarification: We refer here to scripts that are processed by cmd.exe, which is the default on WinNT variants.
(See also: Windows batch files: .bat vs .cmd?)
Line continuation:
call C:\WINDOWS\system32\ntbackup.exe ^
backup ^
/V:yes ^
/R:no ^
/RS:no ^
/HC:off ^
/M normal ^
/L:s ^
#daily.bks ^
/F daily.bkf
PUSHD path
Takes you to the directory specified by path.
POPD
Takes you back to the directory you "pushed" from.
Not sure how useful this would be in a batch file, but it's a very convenient command to use in the command prompt:
C:\some_directory> start .
This will open up Windows Explorer in the "some_directory" folder.
I have found this a great time-saver.
I have always found it difficult to read comments that are marked by a keyword on each line:
REM blah blah blah
Easier to read:
:: blah blah blah
Variable substrings:
> set str=0123456789
> echo %str:~0,5%
01234
> echo %str:~-5,5%
56789
> echo %str:~3,-3%
3456
The FOR command! While I hate writing batch files, I'm thankful for it.
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do #echo %i %j %k
would parse each line in myfile.txt, ignoring lines that begin with a semicolon, passing the 2nd and 3rd token from each line to the for body, with tokens delimited by commas and/or spaces.
Notice the for body statements reference %i to get the 2nd token, %j to get the 3rd token, and %k to get all remaining tokens after the 3rd.
You can also use this to iterate over directories, directory contents, etc...
Rather than litter a script with REM or :: lines, I do the following at the top of each script:
#echo OFF
goto :START
Description of the script.
Usage:
myscript -parm1|parm2 > result.txt
:START
Note how you can use the pipe and redirection characters without escaping them.
The path (with drive) where the script is : ~dp0
set BAT_HOME=%~dp0
echo %BAT_HOME%
cd %BAT_HOME%
The %~dp0 piece was mentioned already, but there is actually more to it:
the character(s) after the ~ define the information that is extracted.
No letter result in the return of the patch file name
d - returns the drive letter
p - returns the path
s - returns the short path
x - returns the file extension
So if you execute the script test.bat below from the c:\Temp\long dir name\ folder,
#echo off
echo %0
echo %~d0
echo %~p0
echo %~dp0
echo %~x0
echo %~s0
echo %~sp0
you get the following output
test
c:
\Temp\long dir name\
c:\Temp\long dir name\
.bat
c:\Temp\LONGDI~1\test.bat
\Temp\LONGDI~1\
And if a parameter is passed into your script as in
test c:\temp\mysrc\test.cpp
the same manipulations can be done with the %1 variable.
But the result of the expansion of %0 depends on the location!
At the "top level" of the batch it expands to the current batch filename.
In a function (call), it expands to the function name.
#echo off
echo %0
call :test
goto :eof
:test
echo %0
echo %~0
echo %~n0
The output is (the batchfile is started with myBatch.bat )
myBatch.bat
:test
:test
myBatch
By using CALL, EXIT /B, SETLOCAL & ENDLOCAL you can implement subroutines with local variables.
example:
#echo off
set x=xxxxx
call :sub 10
echo %x%
exit /b
:sub
setlocal
set /a x=%1 + 1
echo %x%
endlocal
exit /b
This will print
11
xxxxx
even though :sub modifies x.
Sneaky trick to wait N seconds (not part of cmd.exe but isn't extra software since it comes with Windows), see the ping line. You need N+1 pings since the first ping goes out without a delay.
echo %time%
call :waitfor 5
echo %time%
goto :eof
:waitfor
setlocal
set /a "t = %1 + 1"
>nul ping 127.0.0.1 -n %t%
endlocal
goto :eof
Escaping the "plumbing":
echo ^| ^< ^> ^& ^\ ^^
Being able to run commands and process the output (like backticks of '$()' in bash).
for /f %i in ('dir /on /b *.jpg') do echo --^> %i
If there are spaces in filenames, use this:
for /f "tokens=*" %i in ('dir /on /b *.jpg') do echo --^> %i
Creating an empty file:
> copy nul filename.ext
To hide all output from a command redirect to >nul 2>&1.
For example, the some command line programs display output even if you redirect to >nul. But, if you redirect the output like the line below, all the output will be suppressed.
PSKILL NOTEPAD >nul 2>&1
EDIT: See Ignoring the output of a command for an explanation of how this works.
PAUSE
Stops execution and displays the following prompt:
Press any key to continue . . .
Useful if you want to run a batch by double-clicking it in Windows Explorer and want to actually see the output rather than just a flash of the command window.
The equivalent of the bash (and other shells)
echo -n Hello # or
echo Hello\\c
which outputs "Hello" without a trailing newline. A cmd hack to do this:
<nul set /p any-variable-name=Hello
set /p is a way to prompt the user for input. It emits the given string and then waits, (on the same line, i.e., no CRLF), for the user to type a response.
<nul simply pipes an empty response to the set /p command, so the net result is the emitted prompt string. (The variable used remains unchanged due to the empty reponse.)
Problems are: It's not possible to output a leading equal sign, and on Vista leading whitespace characters are removed, but not on XP.
Search and replace when setting environment variables:
> #set fname=%date:/=%
...removes the "/" from a date for use in timestamped file names.
and substrings too...
> #set dayofweek=%fname:~0,3%
Integer arithmetic:
> SET /A result=10/3 + 1
4
Command separators:
cls & dir
copy a b && echo Success
copy a b || echo Failure
At the 2nd line, the command after && only runs if the first command is successful.
At the 3rd line, the command after || only runs if the first command failed.
Output a blank line:
echo.
You can chain if statements to get an effect like a short-circuiting boolean `and'.
if foo if bar baz
To quickly convert an Unicode text file (16bit/char) to a ASCII DOS file (8bit/char).
C:\> type unicodeencoded.txt > dosencoded.txt
as a bonus, if possible, characters are correctly mapped.
if block structure:
if "%VS90COMNTOOLS%"=="" (
echo: Visual Studio 2008 is not installed
exit /b
)
Delayed expansion of variables (with substrings thrown in for good measure):
#echo off
setlocal enableextensions enabledelayedexpansion
set full=/u01/users/pax
:loop1
if not "!full:~-1!" == "/" (
set full2=!full:~-1!!full2!
set full=!full:~,-1!
goto :loop1
)
echo !full!
endlocal
Doesn't provide much functionality, but you can use the title command for a couple of uses, like providing status on a long script in the task bar, or just to enhance user feedback.
#title Searching for ...
:: processing search
#title preparing search results
:: data processing
Don't have an editor handy and need to create a batch file?
copy con test.bat
Just type away the commands, press enter for a new line.
Press Ctrl-Z and Enter to close the file.
example of string subtraction on date and time to get file named "YYYY-MM-DD HH:MM:SS.txt"
echo test > "%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%_%time:~3,2%_%time:~6,2%.txt"
I use color to indicate if my script end up successfully, failed, or need some input by changing color of text and background. It really helps when you have some machine in reach of your view but quite far away
color XY
where X and Y is hex value from 0 to F, where X - background, Y - text, when X = Y color will not change.
color Z
changes text color to 'Z' and sets black background, 'color 0' won't work
for names of colors call
color ?
Total control over output with spacing and escape characters.:
echo. ^<resourceDir^>/%basedir%/resources^</resourceDir^>
TheSoftwareJedi already mentioned the for command, but I'm going to mention it again as it is very powerful.
The following outputs the current date in the format YYYYMMDD, I use this when generating directories for backups.
for /f "tokens=2-4 delims=/- " %a in ('DATE/T') do echo %c%b%a

Resources