Get directory name from array in Batch [duplicate] - batch-file

This question already has answers here:
Arrays, linked lists and other data structures in cmd.exe (batch) script
(11 answers)
Closed 3 years ago.
I have a list of paths from which I want to extract folder name
I wrote:
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
(for %%p in (%paths%) do (
for %%F in (%%p) do echo Processing %%~nxF
))
but seems that nothing is shown.
I expected to see:
Processing test1
Processing test2
Processing test3

It makes a big difference if first " is specified on a set command line left to variable name or left to variable value. In most cases it is better to specify it left to the variable name, especially if a variable value holding a path should be concatenated later with a file name to a full qualified file name.
See also: Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The solution for this task is:
#echo off
set "paths[0]=C:\p\test1"
set "paths[1]=C:\p\test2"
set "paths[2]=C:\p\test3"
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
The command FOR with option /F and a set enclosed in ' results in starting one more command process running in background with %ComSpec% /c and the command line specified between the two ' appended as further arguments. So executed is in this case with Windows installed to C:\Windows:
C:\Windows\System32\cmd.exe /c set paths[ 2>nul
The command SET outputs all environment variables of which name starts with paths[ line by line using the format VariableName=VariableValue to handle STDOUT of started background command process.
It could be that there is no environment variable of which name starts with paths[ which would result in an error message output to handle STDERR by command SET which would be redirected from background command process to handle STDERR of the command process which is processing the batch file and for that reason would be displayed in console window. For that reason a possible error message is redirected by the background command process to device NUL to suppress it with using 2>nul.
Read the Microsoft article 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 set command line with using a separate command process started in background.
FOR captures in this case everything written to handle STDOUT of started background command process and process this output line by line after started cmd.exe terminated itself.
Empty lines are ignored by FOR which does not matter here as there are no empty lines to process.
FOR would split up a non-empty line into substrings using normal space and horizontal tab as string delimiters and would assign just first space/tab separated string to specified loop variable, if it does not start with default end of line character ;. This default line splitting behavior is not wanted here. For that reason the option delims== defines the equal sign as string delimiter.
The option tokens=1* instructs FOR to assign in this case the variable name to specified loop variable I and assign everything after the equal sign(s) after variable name without any further string splitting on equal signs to next loop variable according to ASCII table which is in this case J. That is the reason why loop variables are interpreted case-sensitive while environment variables are handled case-insensitive by the Windows command processor.
In this case only the variable value is of interest in the body of the FOR loop. For that reason just loop variable J is used on ECHO command line while I is not used at all.
The modifier %~nxJ results in removing surrounding double quotes from string value assigned to loop variable J and next get the string after last backslash or beginning of string in case of the string value does not contain a backslash at all. This is the name of the last folder in folder path string.
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 /?
for /?
set /?
UPDATE:
There is a big advantage of this solution in comparison to the other two solutions posted up to now here:
There is not used delayed environment variable expansion which is always problematic on working with file or folder names on not being 100% sure that no folder and no file contains ever an exclamation mark in its name.
Let us compare the three solutions with unusual folder names containing !.
#echo off
rem Make sure there is no environment variable defined of which name starts with
rem paths[ as suggested by Compo which is a very valuable addition on my code.
for /F "delims==" %%I in ('set paths[ 2^>nul') do set "%%I="
set "paths[0]=C:\p\test1!"
set "paths[1]=C:\p\!test2"
set "paths[2]=C:\p\!test!3"
echo/
echo Results of solution 1:
echo/
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
echo/
echo Results of solution 2:
echo/
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
endLocal
echo/
echo Results of solution 3:
echo/
Setlocal EnableDelayedExpansion
Call :process paths "!paths[0]!" "!paths[1]!" "!paths[2]!"
Endlocal
echo/
pause
goto :EOF
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF
The output on running this batch file is:
Results of solution 1:
Processing test1!
Processing !test2
Processing !test!3
Results of solution 2:
Processing test1
Processing test2
Processing 3
Results of solution 3:
Processing paths[0] = "C:\p\test1\p\\p\3"
Solution 1 as posted here works for all three folder names correct.
Solution 2 omits for first and second folder name the exclamation mark which will most likely cause errors on further processing. The third folder name is modified to something completely different. Enabled delayed expansion results in parsing a second time echo Processing %%~nxj after %~nxj being replaced by !test!3 with interpreting test in folder name now as environment variable name of which value is referenced delayed. There was no environment variable test defined on running this batch file and so !test!3 became just 3 before echo was executed by Windows command processor.
Solution 3 produces garbage on any folder name contains an exclamation mark, even on full qualified folder name defined before enabling delayed expansion and referenced with delayed expansion on calling the subroutine process.
Well, folder and file names with an exclamation mark in name are fortunately rare which makes the usage of delayed expansion usually no problem. But I want to mention here nevertheless the potential problems which could occur on any folder name containing one or more !.

Something like that should work :
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
pause

Define the Array within the function.
This approach can be used to define multiplay Arrays.
#ECHO OFF
Setlocal EnableDelayedExpansion
:: REM P_C is used to define the range of the Array. The -1 operations on P_C is to shift the paths parameter out of the Arrays working Index.
::REM the first parameter passed is used as the Arrays Name. all other parameters are assigned to index values 0 +
Call :process paths "C:\p\test1" "C:\p\test2" "C:\p\test3"
pause
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF

Related

remove only specific data from registry value REG_MULTI_SZ via batch file/command line

All;
I've been all around the Internet throughout the day, trying to get a batch file written/working.
Just to start off - I am looking to make this work ONLY via a batch file and/or command line.
I'm looking to remove a specific data (not case-sensitive - as in the data could be 'data' or 'Data' or 'DATA').
Most of the OS's that I've seen the actual data to be on are Win XP machines. The specific's are as follows:
I'm looking to specifically remove the data "browser" (again, it could also be "Browser" or "BROWSER) from a REG_MULTI_SZ registry value, which the subkey is found at:
HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
The actual value is:
NullSessionPipes
I've always seen multiple pieces of data within this value (with "browser" being somewhere in the mix). Whether browser is the only piece of data in the value, or multiple pieces of data, I need to be able to execute a .bat in order to remove this.
My research points me to querying the data, removing the "browser" data, then re-inserting the remaining - I'm just not able to successfully do this.
I've attempted to just create a dummy Test key/value on my machine (Win10) at the following:
HKCU\Temp\Test
which contains the data of:
Test #1
Test #2
browser
Browser
BROWSER
Test #3
Test #4
Everything I've done, I've been unable to remove only/any form of browser from this data, leaving the rest of the "Test #x" data's.
Please advise on any solution(s) that anyone can come up with.
UPDATE
Here is the code(s) that I've been able to put together throughout my research on this (I have put together several different batch files, without positive results):
(NOTE: I in no way take credit for any of this code, as the majority of it has been compiled from/across many different locations):
Test1.bat
This test kept deleting the entire value, which I could not figure out.
#echo off
for /f "tokens=*" %%a in ('
reg query "HKCU\Temp" /v "Test" /f "browser\0Browser\0BROWSER" /d /e
^| find "REG_MULTI_SZ"
') do (
setlocal enableDelayedExpansion
rem Split
set "line=%%a"
set "value=!line:*REG_MULTI_SZ=REG_MULTI_SZ!"
call set "name=%%line:!value!=%%"
rem Trim spaces
for /L %%b in (1,1,10) do if "!name:~-1!"==" " set "name=!name:~0,-1!"
echo Deleting !name!
reg delete "HKCU\Temp" /v "!name!" /f
endlocal
)
pause
Test2.bat
This was an attempt to export the data into a txt file, then remove "browser" - another failed attempt.
#echo off
reg query HKCU\Temp /v Test > c:\Temp\tmp01.txt
FOR /F "tokens=2,3*" %%a in (c:\Temp\tmp01.txt) do call :sub1 %%b
:sub1
if %1x==browser goto end
echo %1
REG ADD HKCU\Temp /f /v Test /t REG_MULTI_SZ /d %1\0\0\0
:end
goto :eof
Test3.bat
This attempt ended up replacing all data with "%b" for my 'Test' value
#echo off & setlocal ENABLEEXTENSIONS
set k="HKCU\Temp"
set v="Test"
for /f "tokens=*" %%a in ('reg query %k% /v %v%') do (
set "d=%%b"
)
set "d=%d:browser\0=%"
set "d=%d:\0\0=%"
reg add %k% /v %v% /t REG_MULTI_SZ /d "%d%" /f
I did come across some topics where users had stated that if the data was translated to HEX/binary, that this would be easier done?
I hope this helps to give some idea to someone as to what exactly I'm doing wrong here.
As long as none of the lines inside the multi string content are doublequoted, here's how I'd probably write it:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "_k=HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters"
Set "_v=NullSessionPipes"
Set "_s=Browser"
Set "_d="
For /F "EOL=H Tokens=2*" %%A In ('Reg Query "%_k%" /V "%_v%"'
) Do If "%%A"=="REG_MULTI_SZ" Set "_d=%%B"
If "%_d%"=="" Exit /B
Set "_d=%_d:\0\0=%"
Set "_m="
For %%A In ("%_d:\0=","%"
) Do Echo %%A|Find /I "%_s%">Nul||Call Set "_m=%%_m%%%%A\0"
If "%_m%"=="" Exit /B
Reg Add "%_k%" /V "%_v%" /T "REG_MULTI_SZ" /D %_m:~,-2% /F>Nul
Please note that this uses Find, (line 15), to match any line containing Browser, (case insensitive), if you're wanting to match lines containing only the string Browser, (case insensitive), then you may wish to take a look at the FindStr command instead, (enter FindStr /? at the Command Prompt for usage information)
There's one major caveat: the value data for a multi string registry entry can be very long indeed, if you're sure that the string data to be written back to the registry will not exceed maximum character length then you'll be okay. If not I'm afraid I cannot think of any other simple way to perform the task. (writing the data as hex into a registry file, *.reg and import/merging it).
Here is a commented batch file for this task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "skip=2 tokens=1,2*" %%A in ('%SystemRoot%\System32\reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters /v NullSessionPipes 2^>nul') do if /I "%%A" == "NullSessionPipes" if "%%B" == "REG_MULTI_SZ" set "NullSessionPipes=%%C" & goto ProcessValue
echo Registry value NullSessionPipes was not found in registry.
goto EndBatch
rem The goal is to remove all ENTIRE strings BROWSER written in any case.
rem Other strings containing by chance also BROWSER at beginning or end
rem of the string should not be removed from multi-string value. For that
rem reason run case-insensitive string substitution with \0browser\0 to
rem really match entire BROWSER string. But there can be multiple BROWSER
rem strings in sequence which are not all removed with one run on using
rem \0browser\0 and so a loop is required make the string substitution
rem with \0browser\0 until this string is not removed anymore from value.
rem But BROWSER could be exist also as first string in value with no
rem preceding \0. For that reason \0 is extra added at beginning and
rem then removed after removing all entire BROWSER strings.
rem REG ADD requires that there is no \0\0 at end of multi-string value
rem to successfully add the multi-string value to Windows registry. The
rem data must end with last character of last string or with just \0.
rem For that reason the last two characters being \0 on Windows XP and
rem Windows Server 2003 are removed before even starting processing the
rem multi-string value. REG of Windows Vista and later Windows versions
rem does not output multi-string value with \0\0 at end like REG of
rem Windows XP. It outputs the multi-string with no \0 at end. So it
rem is necessary to append \0 at end in case of last string is BROWSER.
rem It is also necessary to do nothing if the NullSessionPipes value exists
rem with no string resulting on Windows Vista and later Windows version in
rem nothing assigned to NullSessionPipes and so the environment variable
rem does not exit at all. On Windows XP is assigned in this case just \0
rem which on further processing results also in an empty string and
rem therefore deletion of environment variable NullSessionPipes after
rem removing this final end of multi-string marker.
rem On Windows Vista and later Windows versions it is also necessary to
rem append \0 at end of environment variable string as otherwise with an
rem existing string ending by chance with a backslash this backslash would
rem be interpreted by REG as escape character for the double quote used to
rem enclose the data argument string on command line in double quotes on
rem adding the final multi-line string value. So without appending \0 at
rem end string like TEST\ as last string of NullSessionPipes value would
rem become TEST" which of course is not wanted avoided by using TEST\\0
rem at end of environment variable string for this use case.
rem After removing all entire BROWSER strings from value it is checked
rem if the new value is not identical to value read from registry which
rem means at least one BROWSER string was really removed and so it is
rem necessary to write new value without BROWSER back to Windows registry.
:ProcessValue
if "%NullSessionPipes%" == "" goto EndBatch
if "%NullSessionPipes:~-2%" == "\0" set "NullSessionPipes=%NullSessionPipes:~0,-2%"
if "%NullSessionPipes%" == "" goto EndBatch
if not "%NullSessionPipes:~-2%" == "\0" set "NullSessionPipes=%NullSessionPipes%\0"
set "NewSessionPipes=\0%NullSessionPipes%"
:RemoveBrowser
set "TmpSessionPipes=%NewSessionPipes:\0browser\0=\0%"
if not "%TmpSessionPipes%" == "%NewSessionPipes%" set "NewSessionPipes=%TmpSessionPipes%" & goto RemoveBrowser
set "NewSessionPipes=%TmpSessionPipes:~2%"
if "%NewSessionPipes%" == "%NullSessionPipes%" echo Current NullSessionPipes value does not contain the string BROWSER.& goto EndBatch
echo Current NullSessionPipes value is:
echo/
echo %NullSessionPipes%
echo/
echo New NullSessionPipes value is:
echo/
echo %NewSessionPipes%
echo/
%SystemRoot%\System32\reg.exe add HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters /v NullSessionPipes /t REG_MULTI_SZ /d "%NewSessionPipes%"
:EndBatch
endlocal
The output of the command line
C:\Windows\System32\reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters /v NullSessionPipes 2>nul
executed by FOR in a separate command process started with cmd /C in background depends on version of REG.
On Windows Vista and Windows Server 2008 and all later Windows versions the output starts with an empty line, second line is the registry key, and third line contains registry value name, value type and the value data separated by spaces.
On Windows XP and Windows Server 2003 the output starts with an empty line, next version of REG, one more empty line, fourth line contains registry key and fifth line finally contains registry value name, value type and the value data with four indent spaces and separated by a horizontal tab character.
So from different output of REG it is possible only to skip the first two lines. The next line contains the data of interest on Windows Vista/Server 2008 and all later versions of Windows. But on Windows XP and Server 2003 it is necessary to process more lines from captured REG output until fifth line is reached with the data of interest. For that reason the two additional IF conditions are used to be 100% sure that the multi-string value of registry value NullSessionPipes is really assigned to environment variable NullSessionPipes before exiting the loop and processing the value.
Output of REG on Windows XP with NullSessionPipes existing but not containing any string:
 
! REG.EXE VERSION 3.0
 
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
····NullSessionPipes» REG_MULTI_SZ» \0
· in output example above and all others below represents an indenting/separating space character. » represents a separating horizontal tab character.
Output of REG on Windows XP with NullSessionPipes existing but not containing any string:
 
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
····NullSessionPipes····REG_MULTI_SZ····
Example output of REG on Windows XP with NullSessionPipes containing strings:
 
! REG.EXE VERSION 3.0
 
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
····NullSessionPipes» REG_MULTI_SZ» browser\0test #1\0BROWSER\0Browser\0Test\#2\0TEST\\0browser\0\0
Example output of REG on Windows 7 with NullSessionPipes containing strings:
 
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
····NullSessionPipes····REG_MULTI_SZ····browser\0test #1\0BROWSER\0Browser\0Test\#2\0TEST\\0browser
For the example with multiple strings the data string to add on command line is: "test #1\0Test\#2\0TEST\\0"
The command line with REG to add modified value to registry does not contain option /f to force an overwrite. That gives you the possibility to check new value before really writing it to registry. Insert parameter /f left to /d if there should be no prompt to overwrite existing value once you verified that the batch file works as expected by you.
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 /?
goto /?
if /?
reg /?
reg add /?
reg query /?
set /?
setlocal /?
See also Single line with multiple commands using Windows batch file for an explanation of operator &.

How to read and print contents of text file line by line?

So, I have no clue on how to have CMD echo lines from a *.txt text file one at a time with a tiny delay to make it seem like it's processing.
Is this even possible with a batch alone?
I've tried doing research, but I can't find sufficient text manipulation to be able to do this, but I do know how to make a pause between each command and how to do loops.
Let us assume the text file TestFile.txt should be output line by line which is an ANSI encoded text file with just ASCII characters containing this text:
Line 1 is with nothing special. Next line 2 is an empty line.
;Line 3 with a semicolon at beginning.
Line 4 has leading spaces.
Line 5 has a leading horizontal tab.
Line 6 is with nothing special. Next line 7 has just a tab and four spaces if used internet browser does not remove them.
Line 8 is ! with exclamation marks ! in line!
? Line 9 starts with a question mark.
: Line 10 starts with a colon.
] Line 11 starts with a closing square bracket.
The batch file below outputs this text file line by line with one second delay between each line with the exception of second line which is completely empty.
#echo off
title Read line by line with delay
setlocal EnableExtensions DisableDelayedExpansion
rem Use command TIMEOUT by default for 1 second delay. But use
rem PING in case of TIMEOUT does not exist as on Windows XP.
set "DelayCommand=%SystemRoot%\System32\timeout.exe /T 1 /NOBREAK"
if not exist %SystemRoot%\System32\timeout.exe set "DelayCommand=%SystemRoot%\System32\ping.exe 127.0.0.1 -n 2"
for /F "usebackq eol=¿ delims=" %%I in ("TestFile.txt") do (
echo(%%I
%DelayCommand% >nul
)
endlocal
pause
The strange looking character ¿ after eol= is an inverted question mark with hexadecimal Unicode value 00BF used to output third line correct. A line with an inverted question mark at beginning would not be output because of this redefinition of end of line character.
This batch file code is not designed to output any type of text file with any type of character encoding independent on which characters contains the text file. The Windows command line environment is not designed for output of any text file.
It is also possible to use a different, unquoted syntax to specify the FOR options delims, eol and usebackq to define an empty list of delimiters and no end of line character:
#echo off
title Read line by line with delay
setlocal EnableExtensions DisableDelayedExpansion
rem Use command TIMEOUT by default for 1 second delay. But use
rem PING in case of TIMEOUT does not exist as on Windows XP.
set "DelayCommand=%SystemRoot%\System32\timeout.exe /T 1 /NOBREAK"
if not exist %SystemRoot%\System32\timeout.exe set "DelayCommand=%SystemRoot%\System32\ping.exe 127.0.0.1 -n 2"
for /F usebackq^ delims^=^ eol^= %%I in ("TestFile.txt") do (
echo(%%I
%DelayCommand% >nul
)
endlocal
pause
Thanks goes to aschipfl for this alternate syntax of the three FOR options with using escape character ^ to escape the equal signs and spaces in not double quoted options string to get interpreted by cmd.exe the string usebackq delims= eol= as one argument string for for /F.
There is ( instead of a space as usually used to output also correct line 7 with just a tab and some normal spaces. See also DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/. echo/%%I does not correct output line 9 starting with a question mark.
It is not possible to define with an option that FOR does not ignore empty lines. But it is possible with FIND or FINDSTR to output a text file with all lines with a line number at beginning and so having no empty line anymore. The line number is enclosed in square brackets (FIND) or separated with a colon (FINDSTR) from rest of the line. It would be possible to assign to loop variable only the string after first sequence of ] or : after line number which in most cases means the entire line as in text file. But if a line in text file starts by chance with ] or :, FOR would remove this delimiter character too. The solution is this code:
#echo off
title Read line by line with delay
setlocal EnableExtensions DisableDelayedExpansion
rem Use command TIMEOUT by default for 1 second delay. But use
rem PING in case of TIMEOUT does not exist as on Windows XP.
set "DelayCommand=%SystemRoot%\System32\timeout.exe /T 1 /NOBREAK"
if not exist %SystemRoot%\System32\timeout.exe set "DelayCommand=%SystemRoot%\System32\ping.exe 127.0.0.1 -n 2"
for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" "TestFile.txt" 2^>nul') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
echo(!Line:*:=!
endlocal
%DelayCommand% >nul
)
endlocal
pause
FINDSTR searches in the specified file with the regular expression ^ for matching lines. ^ means beginning of a line. So FINDSTR does not really search for a string in the lines of the file because of every line in a file has a beginning, even the empty lines. The result is a positive match on every line in the file and therefore every line is output by FINDSTR with the line number and a colon at beginning. For that reason no line processed later by for /F is empty anymore because of all lines start now with a line number and a colon, even the empty lines in the text file.
2^>nul is passed to cmd.exe started in background as 2>nul and results in redirecting an error message output by FINDSTR to handle STDERR to the device NUL to suppress the error message. FINDSTR outputs an error message if the file to search does not exist at all or the file cannot be opened for read because of missing NTFS permissions which allow that or because of the text file is currently opened by an application which denies the read access to this file as long as being opened by the application.
cmd.exe processing the batch file captures all lines output by FINDSTR to handle STDOUT of cmd.exe started in background and FOR processes now really all lines in the file after FINDSTR finished and the background command process closed itself.
The entire line with line number and colon output by FINDSTR executed in a separate command processes started by FOR with %ComSpec% /c and the command line within ' as additional arguments is assigned to loop variable I which is assigned next to environment variable Line.
Then delayed expansion is enabled as needed for next line which results in pushing address of current environment variables list on stack as well as current directory path, state of command extensions and state of delayed expansion before creating a copy of the current environment variables list.
Next the value of environment variable Line is output, but with substituting everything up to first colon by nothing which results in the output of the real line as stored in text file without the line number and the colon inserted at beginning by FINDSTR.
Finally the created copy of environment variables list is deleted from memory, and previous states of delayed expansion and command extension are popped from stack and set as well as the current directory path is set again as current directory and previous address of environment variables list is restored to restore the list of environment variables.
It is of course not very efficient to run for each line in text file the commands setlocal EnableDelayedExpansion and endlocal doing much more than just enabling/disabling delayed expansion, but this is necessary here to get lines with an exclamation mark correct assigned to environment variable Line and process next correct the value of Line. The efficiency loss is not really problematic here because of the delay of one second between output of each line.
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 /?
findstr /?
for /?
if /?
ping /?
rem /?
set /?
setlocal /?
Despite your question being off topic, I have decided to include this because, there are already two answers and it can be achieved using a single line.
From a batch file:
#For /F Tokens^=1*Delims^=]^ EOL^= %%A In ('Find /N /V ""^<"C:\test.txt"') Do #Echo(%%B&>Nul PathPing 127.0.0.1 -n -q 1 -p 450
From the Command Prompt:
For /F Tokens^=1*Delims^=]^ EOL^= %A In ('Find /N /V ""^<"C:\test.txt"') Do #Echo(%B&>Nul PathPing 127.0.0.1 -n -q 1 -p 1350
Both examples do not omit empty lines from your source file, C:\test.txt, which can be changed as required.I have used PathPing for the 'tiny delay', because it seems more controllable; to adjust the delay all you need to do is change the last number until you find your most pleasing output.
Give a try for this batch script :
#echo off
Title Read line by line with delay
set "InputFile=TestFile.txt"
set "delay=1" Rem Delay one seconds, you can change it for your needs
setlocal enabledelayedexpansion
for /f "tokens=*" %%A in ('Type "%InputFile%"') do (
set /a N+=1
set "Line[!N!]=%%A"
)
for /l %%i in (1,1,%N%) do (
echo !Line[%%i]!
Timeout /T %delay% /nobreak>nul
)
pause

Convert configuration file into variables and convert forward slash to backslash

I have a configuration file which I need for my bash script which has a layout:
A=C:/Example1/A
B=C:/Example2/B
C=C:/Example3/C
I want to use the same configuration file for my windows batch file. I need to convert the above file into variables which I have done using:
for /f "delims=" %%x in (test.txt) do (set "%%x")
How do I go about converting this file into variables while also converting all the forward slashes into backslashes?
Thanks!
add after your for line,
for /f "delims==" %%x in (q888.txt) do call set "%%x=%%%%x:/=\%%"
or, as a replacement for your existing for,
for /f "tokens=1*delims==" %%x in (q888.txt) do set "%%x=%%y"&call set "%%x=%%%%x:/=\%%"
(I used a file called q888.txt for testing)
The first smply executes a substitution, using a parsing trick. The second combines the set and substitution into one cascaded command by tokenising on = into %%x and %%y
This could be done with the following batch code:
#echo off
if not exist "test.txt" goto :EOF
setlocal EnableDelayedExpansion
for /F "usebackq tokens=1* delims==" %%I in ("test.txt") do (
if not "%%~J" == "" (
set "Value=%%~J"
set "Value=!Value:/=\!"
set "_%~n0_%%~I=!Value!"
)
)
echo The variables set from file are:
echo/
set "_%~n0_"
echo/
pause
endlocal
The batch file first checks if the file to process exists in current directory at all. The batch file processing is exited with a jump to predefined label EOF (end of file, requires enabled extensions which are enabled by default) in case of the file test.txt does not exist at all.
Next the file is read line by line with skipping empty lines and lines starting with a semicolon by command FOR which splits each line up into two strings.
The first string left of first equal sign is assigned to loop variable I. Everything right of first equal sign is assigned next loop variable J according to ASCII table.
The IF condition in the loop checks if a value is also defined for a variable. The value is assigned to an environment variable on which a string substitution is executed using delayed expansion to replace all / by \.
Then the modified value is assigned to an environment variable with a name starting with _, the name of the batch file, one more underscore and the string assigned to loop variable I read from the file.
For demonstration the variables with their values are finally output before the local variables are discarded on execution of last command ENDLOCAL.
I strongly recommend not assigning the values read from the file directly to environment variables whose name is completely also read from the file as this makes the batch file easy to manipulate by just modifying the contents of the text file. For example path=C:\Temp in text file would otherwise result in set "Path=C:\Temp" and from this point of batch file execution the running Windows command process would not find anymore any standard executable in directories defined by default in environment variable PATH like %SystemRoot%\System32.
A second variant which incorporates answer posted by Magoo with above batch code:
#echo off
if not exist "test.txt" goto :EOF
setlocal DisableDelayedExpansion
for /F "usebackq tokens=1* delims==" %%I in ("test.txt") do if not "%%~J" == "" set "_%~n0_%%~I=%%~J" & call set "_%~n0_%%~I=%%_%~n0_%%~I:/=\%%"
echo The variables set from file are:
echo/
set "_%~n0_"
echo/
pause
endlocal
The advantage of this variant is that delayed expansion is not needed for this solution which makes it possible to correct process also lines from file containing 1 or more exclamation marks on which first variant fails. And it is also a little bit faster, not noticeable faster for a human, but nevertheless a bit faster.
In both batch code blocks _%~n0_ can be replaced by (nearly) anything including also nothing although that is not recommended. Using just an underscore would be also possible as there are no environment variables defined by default by Windows which start with an underscore.
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 /? ... explains %~n0 (name of argument 0 - the batch file name - without path and without file extension).
echo /?
endlocal /?
for /?
goto /?
if /?
pause /?
set /?
setlocal /?
The simplest solution is to let the ~f FOR variable modifier put the full path in canonical form (including conversion of forward slashes to back slashes). I use the DELIMS and TOKENS options to split each line into the variable name and path so that I can apply the ~f to the path. It is important to use tokens=1* instead of tokens=1,2 just in case the path includes a = character.
for /f "delims== tokens=1*" %%A in (test.txt) do (set "%%A=%%~fB")
Note, however, that this strategy only works if your "test.txt" already contains full, absolute paths. If the file contains relative paths, then the ~f modifier will add drive and or folder values from the current directory to turn the relative path into an absolute path.

Batch file log function for printing command output to both log file and screen

I found there are some topics similar to this problem but not exactly what I want, so I raise this topic.
I want to create a log function for printing message to both formatted log file and console output. The function is as below:
:LOGDEBUG
#echo DEBUG: %~1
if NOT EXIST %LOG_FILE% exit /b 1
#echo [%date - %time%] DEBUG: %~1 >> %LOG_FILE% 2>&1
exit /b 0
And I try to use it for printing the command execution output and if the output contains special character like "<" and ">", this function doesn't work well and prompt "The system cannot find the file specified". My code for executing a command is below:
for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
CALL :LOGDEBUG "%%a"
)
However, when I use "echo" command directly instead of the log function, the output can be printed correctly on the console. Like the following code:
for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
echo %%a
)
May I know what is the problem, and how can I print the output correctly by using the log function? Thanks
You have answered your question by own: when I use "echo" command directly...
#ECHO OFF
SETLOCAL EnableExtensions
set "LOG_FILE=D:\tempx\program.log" my testing value
rem type NUL>"%LOG_FILE%"
for /f "usebackq delims=" %%a in (`dir d:\temp 2^>NUL`) do (
CALL :LOGDEBUG "%%a"
)
rem type "%LOG_FILE%"
ENDLOCAL
exit /b
:LOGDEBUG
FOR %%A in ("%~1") do (
#echo DEBUG: %%~A
if NOT EXIST "%LOG_FILE%" exit /b 1
#echo [%date% - %time%] DEBUG: %%~A >> "%LOG_FILE%" 2>&1
)
exit /b 0
Resources (required reading):
(command reference) An A-Z Index of the Windows CMD command line
(additional particularities) Windows CMD Shell Command Line Syntax
(%~A etc. special page) Command Line arguments (Parameters)
(special page) EnableDelayedExpansion
Here is the batch code which should work.
#echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do call :LOGDEBUG "%%a"
endlocal
goto :EOF
:LOGDEBUG
set "StringToOutput=%~1"
echo DEBUG: !StringToOutput!
echo [%DATE% - %TIME%] DEBUG: !StringToOutput!>>"%LOG_FILE%"
goto :EOF
First delayed environment variable expansion is enabled and a copy of existing environment table is made. It is explained below why this is done.
Next the name of the log file with full path is assigned to an environment variable in local variable table. This path can be with or without 1 or more spaces in path. The log file is deleted in case of existing already from a previous run. This code can be removed if you want to append new lines to already existing file. But you should add in this case code to avoid that the log file permanently increases until no free storage space anymore.
The FOR command executes the command DIR and processes each line of the output of DIR written to stdout. Blank lines are skipped. The default delimiters are space, tab and newline characters. As wanted here are the entire lines of DIR, the default delimiter list is replaced by nothing which means only newline characters remain and loop variable %a gets assigned always an entire non blank line.
The output of command DIR contains < and > which are interpreted as redirection operators if found by command processor within a line not quoted. Therefore the line for DIR output is passed quoted to subroutine LOGDEBUG. Which characters must be usually quoted are listed on last help page printed into a command prompt window when executing cmd /? in a command prompt window.
When the loop has finished, the local environment table is deleted which means LOG_FILE and StringToOutput are also removed, and previous environment is restored which usually means the delayed expansion is turned off again before batch execution exits with a jump to predefined label to end of file.
The subroutine LOGDEBUG first assigns the passed string to an environment variable without surrounding quotes just needed because of special characters in line like < and >.
Next the line is written to console window without quotes using delayed expansion as otherwise < and > would be interpreted as redirecting operators and not literally.
The same line is written also to the log file with the difference of date and time inserted at beginning of line. You missed the percent sign after date in your code. Again delayed expansion is used to get the line with the characters < and > written to file without being interpreted as redirection operators.
Important is also that there is no space before >> as otherwise each line in log file would have a trailing space. 2>&1 is useless here as command echo does not write something to stderr.
The subroutine is exited with a jump to end of file resulting in command FOR processes next line.
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 /?
del /?
dir /?
for /?
goto /?
set /?
It would be of course possible to do all the output directly in body of command FOR without using a subroutine.
#echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do (
echo DEBUG: %%a
echo [!DATE! - !TIME!] DEBUG: %%a>>"%LOG_FILE%"
)
endlocal
Delayed variable expansion is nevertheless required here as otherwise %DATE% and %TIME% would be expanded by command processor like %LOG_FILE% already on parsing entire block defined by ( and ) before command FOR is executed at all which would result in same date and time written for all lines to the log file.

Setting up variable in for loop in batch

I am fighting with little piece of code for last two days.
In this I am not able to set variable in a for loop.
I want to assign a filename to a variable for string manipulation.
echo off
for /f %%a IN ('dir /b *_ah.ttf') DO (
set /a fName=%%~na
echo %fName%
)
When I echo fName variable I get only last filename repeatedly number of times for for loop count.
(I want to pass this variable as an argument to some batch file as follows
ttfhnt --strong-stem-width=D -i %%a %fName:~0,-3%.ttf
but its failing due to above problem)
Can somebody help me please?
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
And set /A is only used for arithmetic operations
setlocal enabledelayedexpansion
for /f "delims=" %%a IN ('dir /b *_ah.ttf') DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
edited to adapt to comments
While for command is able to execute a command (in the OP code, the dir...), retrieve its output and then iterate over the lines in this output, the original reason for the command is to iterate over a set of files. In this form, the code can be written as
setlocal enabledelayedexpansion
for %%a IN ("*_ah.ttf") DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
Now, the for command replaceable parameter will iterate over the indicated set of files. (execute for /? for a list of all the command options).
But as foxidrive points, the problem with delayed expansion are the exclamation signs. Without delayed expansion, they are another normal character, but with delayed expansion they frequently become a problem when a value containig them is assigned/echoed.
A quick test
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
echo normal : %test%
for /f "delims=" %%a in ("!test!") do echo for : %%a
Will show
---------------------
test=this is a test!
---------------------
delayed : this is a test!
normal : this is a test
for : this is a test
Obviously when the value is a file name, this behaviour will make the code find or not the file.
Depending on the case different solutions can be used, but usually it involves the activation / desactivation of the delayed expansion behaviour (beware, the endlocal removes any change in environment variables from the previous setlocal).
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
rem Commuted to no delayed expansion
setlocal disabledelayedexpansion
echo normal : %test%
endlocal
rem Cancelled the initial enable delayed expansion
for /f "delims=" %%a in ("!test!") do endlocal & echo for : %%a
rem The last endlocal has removed the changes to the variable
echo no data : [%test%]

Resources