Batch File: How to read only sections in a INI file - batch-file

I am very much a novice at Batch Scripting and i'm trying to write a simple script to READ from an INI file based on the Parameters that is passed when the batch file is called.
This is an example for what the INI file would look like:
[SETTING1]
Value1=Key1
Value2=Key2
Value3=Key3
[SETTING2]
Value1=Key1
Value2=Key2
Value3=Key3
[SETTING3]
Value1=Key1
Value2=Key2
Value3=Key3
I am running into a problem when it comes to reading ONLY the section that is called. It will read from any section that matches the "Value" and "Key" and i don't know how to limit it to only read the section with the settings.
The file is being called with this Parameter: run.bat run.ini setting2. My code below is what I have so far and I feel as if i have officially hit a wall. Can anyone help with this? Any help would greatly be appreciated.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET INIFile="%~f1"
for /f "usebackq delims=" %%a in (!INIFile!) do (
if %%a==[%2] (
SET yesMatch=%%a
for /f "tokens=1,2 delims==" %%a in (!yesMatch!) do (
if %%a==Value1 set Key1=%%b
if %%a==Value2 set Key2=%%b
if %%a==Value3 set Key3=%%b
)
ECHO !yesMatch!
ECHO !Key1!
ECHO !Key2!
ECHO !Key3!
pause
)
)
pause
exit /b

#ECHO OFF
SETLOCAL
SET INIFile="%~f1"
SET "FLAG="
for /f "usebackq tokens=1,*eol=|delims==" %%a in (%INIFile%) do (
IF "%%b"=="" (
REM No "=" so section
IF /i "%%a"=="[%2]" (SET flag=Y) ELSE (SET "flag=")
) ELSE IF defined flag (
REM data line - only if FLAG is defined
REM set values defined
SET "%%a=%%b"
REM pick particular values
if /i "%%a"=="Value1" set "Key1=%%b"
if /i "%%a"=="Value2" set "Key2=%%b"
if /i "%%a"=="Value3" set "Key3=%%b"
)
)
SET key
SET val
GOTO :EOF
Here's a way to get your values.
The data in the file is either [section] or name=value so settling delims to = will assign either section-only to %%a or name to %%a and value to %%b.
The flag is only set (defined) after its appropriate section is encountered, and cleared on the next section. Only of it is defined will the assignment take place.
The advantage of the simple set %%a=%%b is that it results in setting whatever values are defined in the section - no changes to the code need to take place if new values are added. Your original version has the advantage of picking particular values and setting only those. You pays your money, you takes your choice.
Note that the /i switch on an if statement makes the comparison case-insensitive.
Nota also the use of set "value=string" which ensures that trailing spaces on a line are not included in the value assigned.
edit : By default, the end of line character is ; so any line starting ; is ignored by for/f. The consequence is that the values for the ;-commented-out section would override the values set for the previous section.
Setting eol to | should cure the problem. It really doesn't matter what eol is set to; it's exactly one character which may not appear anywhere in the INI-file (else that line would appear truncated.)
It is possible, if necessary, to set eol to control-Z but selecting an unused character is easier...
Consequently, a one-line change - the eol parameter is included in the for /f options.

Related

Batch program - delete all dividing symbols in numbers

I have a homework task which needed to be done using just batch script. I need to rewrite all the numbers in .txt file if they have dividing symbols . or , but those strings may contain both words and numbers. Also the result should stay in the same file.
For example:
Lorem ipsum dolor 12.3254
2556,4646 ex commodo
would become
Lorem ipsum dolor 123254
25564646 ex commodo
I started with some code that looks like this:
#echo off
setlocal EnableDelayedExpansion
SET verfile=%1
FOR /f "tokens=* delims= " %%A IN (%verfile%) DO (
SET "Var=%%A"
FOR /l %%i IN (0, 1, 9) DO (
echo !Var! | findstr "."
IF %ERRORLEVEL% EQU 0 (
)
)
And now I have no idea how to continue it.
Can you help me please?
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory and filename are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q73553463.txt"
:: remove variables starting #
FOR /F "delims==" %%b In ('set # 2^>Nul') DO SET "%%b="
SET /a linecount=0
:: Read entire file into memory
FOR /f "usebackqdelims=" %%e IN ("%filename1%") DO (
rem next line number
SET /a linecount +=1
rem record in memory
SET "#!linecount!=%%e"
)
:: process each line removing [,.] following a digit
:: and report to original file
(
FOR /L %%e IN (1,1,%linecount%) DO (
FOR %%c IN ("." ",") DO FOR /L %%y IN (0,1,9) DO SET "#%%e=!#%%e:%%y%%~c=%%y!"
ECHO !#%%e!
)
)>"%filename1%"
TYPE "%filename1%"
GOTO :EOF
The set # command will generate a list like
#whatever=something
#whateverelse=somethingelse
for all variables that are currently set and start #.BUT it would be unusual to have any variable set that starts # so set would generate an error. The 2^>nul sends any error-report (on standard device stderr, which is device 2) to nul that is, nowhere. The caret ^ is required because cmd needs to distinguish between a part of the set command and a part of the for.
The for/f...%%b using delims of = generates "tokens" from the list generated by the set. Tokens are separated by any sequence of any of the delimiters specified between = and ", and by default, "token1" is selected, so the strings applied to %%b are
#whatever
#whateverelse
and these variables need to be set to nothing.
See for /? from the prompt for documentation on for or browse thousands of examples on SO.
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces which can cause chaos. Once bitten, twice shy.
Then we read the file. Using "delims=" sets no delimiters, hence the whole line forms "token 1" which is assigned to %%e. The usebackq changes the meaning of " so that a double-quoted filename may be used. The filename I've used includes a Space but if there are no separator characters in the name, the quotes and usebackq can be omitted (again, ref for/?)
Then add 1 to the linecount and record the line in memory by assigning it to #!linecount!. The !linecount! is required because linecount is varying within the block (parenthesised sequence of lines) - and with delayedexpansion enabled, %linecount% yields the value when the block was encountered, and !linecount! the run-time or instantaneous value - as it changes within the block.
Stephan's DELAYEDEXPANSION link
So - having established #1, #2..#%linecount% with the lines from the file, we can process those variables and produce a replacement file.
Note that there is a block formed by ( for...%%e...)>filename. This allows any echoed data within the block to be redirected to the file. > redirects to a new version whereas >> would append to any existing file.
So - we iterate through all of the #linenumber lines using a for /L on %%e. For each of these, we set %%c to "." and %%y to 0 to 9 and then replace any string %%y%%c with %%y (3. gets replaced by 3 for example). Then repeat with %%c set to ",". set /? provides documentation and browsing SO examples.
But Woah, Neddie! There's a little trick here. , is a list-separator so (. ,) won't work - it will be treated as (.). Using the quotes allows cmd to read each character separately, and we need to use %%~c instead of %%c to dump the quotes.
So - take a look around. You can do a lot with batch if you're devious enough. And no doubt you'll be challenged if you present this solution. Be ready to explain it. A really good way to follow what's happening is to turn echo on and watch the magic step-by-step. Use pause liberally and perhaps remove the >"%filename1%" to prevent the report going to the file while you're observing what's happening.
#echo off
setlocal enabledelayedexpansion
set "verfile=%~1"
echo before:
type "%verfile%"
for /f "usebackq tokens=*" %%a in ("%verfile%") do (
set strline=%%a
set strline=!strline:.=!
set strline=!strline:,=!
echo !strline!>>"%verfile%.tmp"
)
echo.
echo after:
type "%verfile%.tmp"
del /f /q "%verfile%.tmp"

BATCH: How to properly use a %% variable within a for loop

This is the very first time i tried batch scripting so please bear with me.
I just wanted to read each line of my hosts file, and replace the line if it contains/matches a substring. I've seen a lot of answered questions about substrings here but I just can't make it work by using the provided solutions.
I have this code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "hostspath=%SystemRoot%\System32\drivers\etc\hosts"
set "hostsbackuppath=c:\hosts"
>"%hostsbackuppath%.new" (
rem Parse the hosts file, skipping the already present hosts from our list.
rem Blank lines are preserved using findstr trick.
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set str1=%%b
if not x!str1:mydomainname=!==x!str1! (
rem Match found, replace this line.
echo "match!"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
I was trying out this solution to check for substring match among other else: Batch file: Find if substring is in string (not in a file)
Can someone help me? Thanks
SETLOCAL ENABLEDELAYEDEXPANSION
set "matched=true"
>"%hostsbackuppath%.new" (
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set "str1=%%b"
if not "!str1:mydomainname=!"=="!str1!" (
rem Match found, replace this line.
echo "match at %%b in line %%a"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
Hooley-dooley! Someone needs to learn to name variables appropriately.
First, you need to use setlocal enabledelayedexpansion - please see a thousand-and-one SO articles about delayed expansion.
Since str1 is varied within the loop, you need to use setlocal enabledelayedexpansion and !var! to access the varying value of var as %var% is the value at the time the for was encountered.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can safely be used "quoteless".
FOr the same reason, quoting each side of a comparison is preferred as it makes a single token of a string containing separators like spaces.
Then you have a comment "match found" after which you set matched to false ?? Therefore you need to initialise match (to true)
Now quite what you want to do is obscure. On re-reading, you probably want to set "matched=true" as the first line within the loop, not outside as I have it, so that the value is re-set to true for each line found and then set to false if a match is found.
All this negative logic is insane. I need a strong cup of coffee.

Batch File - Find two lines then copy everything between those lines

I need to parse a text file.
I want to find the firstline in the text file
: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
and find the last line
:: the last line to find
set lastLine=*******************************************************************************************************************
Then I need to export to a new file everything between those two line.
echo >> M:\TESTING\Output.txt
I'm a beginner with this and I've searched for days, but am not finding how to do this.
I looked at for loops and if statements, but I'm still puzzled.
for /f "tokens=1 delims= " %%f in (M:\TESTING\*.txt) do (
:: sets then the line variable to the line just read
set line=%%f
:: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
:: the last line to find
set lastLine=*******************************************************************************************************************
Then if %line% = %fistLine% start the export.....
Any direction will be appreciated. thanks.
#DennisvanGils' approach is a good start and will do well in many cases.
However, it will not produce an exact copy of the text file content between the given lines:
leading whitespaces (SPACE and TAB) will be removed (due to tokens=* option),
lines starting with ; will be skipped (due to the default option eol=; of for /F), and
empty lines will be skipped as well (as for /F always skips such).
To get an exact copy of the text file portion, you could use the following code snippet:
#echo off
set "INFILE=M:\TESTING\input.txt"
set "OUTFILE=M:\TESTING\output.txt"
set "FIRSTLINE=------------------------------------------------------------------------------------------------------------------"
set "LASTLINE=*******************************************************************************************************************"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%OUTFILE%" (
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(!LINE!
endlocal
) else (
endlocal
)
)
)
:CONTINUE
endlocal
Core function here is findstr, which is configured so that every line in the file is returned, prefixed with a line number and a colon :. Its output is then parsed by a for /F loop. Because of the prefix, no line appears to be empty and therefore every one is iterated by the loop. In the body of the loop, the prefix is removed by the set "LINE=!LINE:*:=!" command for each line.
The variable FLAG is used to decide whether or not the current line is to be output; if it is defined, that is, a value is assigned, the command echo !LINE! is executed; otherwise it is not. FLAG is set if the current line matches the string in %FIRSTLINE%; if the line matches the string in %LASTLINE%, a goto command is executed which breaks the loop. This means also that only the first block between %FIRSTLINE% and %LASTLINE% matches is output.
If there might occur multiple %FIRSTLINE% and %LASTLINE% matches within the text file and you want to output every block, replace the goto command line by set "FLAG=".
Note that this approach does not check whether %FIRSTLINE% occurs before %LASTLINE%, nor does it even check for existence of %LASTLINE% (all remaining lines to the end of file are output in case). If all this is important, the logic need to be improved and even a second loop will be required most likely.
What you should do in this case is use a variable like a boolean to know if you encountered the startline and endline yet, and to know if you have to output the lines.
Also, you should use setlocal ENABLEDELAYEDEXPANSION with ! instead of % so you can change variables in loops and ifs (for more information about that, see this. The usage of () after if is not needed in this case, since the if is on one line, but they make things easier to read in my opinion. If you want to output the start and endline too, switch the checks for the start and endlines.
#echo off & setlocal ENABLEDELAYEDEXPANSION
set start=0
:: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
:: the last line to find
set lastLine=*******************************************************************************************************************
for /F "tokens=*" %%A in (TEST.txt) do (
:: sets then the line variable to the line just read
set line=%%A
if "!line!"=="!lastLine!" (set start=0)
if !start! equ 1 (echo !line!>>TESTOUTPUT.txt)
if "!line!"=="!firstLine!" (set start=1)
)
This should do what you want. Note that when you encounter a startline a second time it starts reading again.

print specific lines from a batch file

I am trying to print Line 4, Col 21-50 out of a text file, can this be simply done under Windows somehow? I've been trying to do this:
FOR /F "usebackq tokens=1 delims=-" %G IN (%COMPUTERNAME%.txt) DO ECHO %G
This is just working out terribly. Can't I just print a specific set of lines?
I need this script to be run on multiple computers, ideally I'd like to convert it to a variable for use with slmgr -ipk, maybe someone has a better suggestion?
Contents of text file (I want the XXXXX-XXXXX-XXXXX-XXXXX-XXXXX portion):
==================================================
Product Name : Windows 7 Professional
Product ID : 00371-OEM-9044632-95844
Product Key : XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
Installation Folder : C:\Windows
Service Pack : Service Pack 1
Computer Name : LIBRA
Modified Time : 6/4/2015 7:26:54 PM
==================================================
if you want only the "Product Key" line you can try with
type %COMPUTERNAME%.txt|find /i "Product Key"
or
for /f "tokens=2 delims=:" %%# in (' type %COMPUTERNAME%.txt^|find /i "Product Key"') do echo %%#
For the task at hand, npocmaka's answer is the best suitable approach, as it does not insist on a fixed position of the string to extract from the file.
However, I want to provide a variant that sticks to a certain position.
The following code extracts the string placed at columns 21 to 50 in line 4 of file list.txt (the result is echoed (enclosed in "") and stored in variable LINE_TXT (without ""):
#echo off
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" "list.txt"'
) do (
if %%L equ 4 (
set "LINE_TXT=%%M"
goto :NEXT
)
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
The goto :NEXT command terminates the for /F loop at the given line; this is not mandatory but will improve performance for huge files (as long as the given line number is quite small).
To be more flexible, the following code can be used (define the string position in the initial set block):
#echo off
rem Define the string position here:
set FILE_TXT="list.txt"
set LINE_NUM=4
set COL_FROM=21
set COL_UPTO=50
setlocal EnableDelayedExpansion
set /A COL_UPTO-=COL_FROM
set /A COL_FROM-=1
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" %FILE_TXT%'
) do (
if %%L equ %LINE_NUM% (
set "LINE_TXT=%%M"
if defined LINE_TXT (
set "LINE_TXT=!LINE_TXT:~%COL_FROM%,%COL_UPTO%!"
)
goto :NEXT
)
)
:NEXT
endlocal & set "LINE_TXT=%LINE_TXT%"
echo."%LINE_TXT%"
Both of the above code snippets rely on the output of findstr /N /R ".*", which returns every line that matches the regular expression .*, meaning zero or more characters, which in turn is actually true for every line in the file; however, the switch /N defines to prefix each line with its line number, which I extract and compare with the originally defined one.
Here is another variant which uses for /F to directly loop through the content (lines) of the given text file, without using findstr:
#echo off
for /F "usebackq skip=3 eol== delims=" %%L in (
"list.txt"
) do (
set "LINE_TXT=%%L"
goto :NEXT
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
This method has got the better performance, because there is the skip option which skips parsing of and iterating through all lines (1 to 3) before the line of interest (4), opposed to the findstring variant.
However, there is one disadvantage:
for /F features an eol option which defines a character interpreted as line comment (and defaults to ;); there is no way to switch this option off as long as delims= defines no delimiters (last position in option string), which is mandatory here to return the line as is; so you have to find a character that does not appear as the first one in any line (I defined = here because your sample text file uses this as header/footer character only).
To extract a string from line 1, remove the skip option as skip=0 results in a syntax error.
Note that goto :NEXT is required here; otherwise, the last (non-empty) line of the file is extracted.
Although for /F does not iterate any empty lines in the file, this is no problem here as the skip option does not check the line content and skip over empty lines as well.
Finally, here is one more approach using more +3 where no text parsing is done. However, a temporary file is needed here to pass the text of the desired line to the variable LINE_TXT:
#echo off
set LINE_TXT=
more +3 "list.txt" > "list.tmp"
set /P LINE_TXT= < "list.tmp"
del /Q "list.tmp"
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
exit /B 0
This method avoids for /F and therefore the problem with the unwanted eol option as mentioned in the above solution. But this does not handle tabs correctly as more substitutes them with spaces (8 indent spaces as per default and configurable by the /Tn switch where n is the number of spaces).

Windows shell: get parameter value from file

I have a file C:\parameters.txt that contains different parameters, for example:
env_user=username123
env_pw=password123
env_url=example.com
Now I created a .cmd file that needs to get these values and put them in a variable, for example:
SET var_user=<Here I need 'username123'>
SET var_pw=<Here I need 'password123'>
SET var_url=<Here I need 'example.com'>
How do I write this in my cmd script to get the correct values for my variables?
You need to set a delimiter for = character so that words before/after = will be separated. Besides that, you need an array to set each of the parameters. You can do it like this:
#echo off
setlocal enabledelayedexpansion
set increment=0
for /f "tokens=1* delims==" %%a in (C:\parameters.txt) do (
set parameters_array[!increment!]=%%b
set /a increment+=1
)
echo %parameters_array[0]%
echo %parameters_array[1]%
echo %parameters_array[2]%
pause >nul
Keep in mind, array always starts from 0. You could change to set increment=1 if you prefer the array starts from 1.
Just a slight alternative to dark fang's solution, since your parameters.txt file's contents are already in the format of variable=value, you could
#echo off
setlocal
for /f "usebackq delims=" %%I in ("c:\parameters.txt") do set "%%I"
rem // display env_* variables
set env_
pause
The usebackq option allows you to quote the file name, which might be needed if you ever move c:\parameters.txt to a location containing spaces, ampersands, or other tricksy characters. It's a good habit to follow when reading the contents of text files with for /f.
Also, it's better not to use delayed expansion if you don't need it, as delayed expansion can sometimes corrupt values containing exclamation marks -- a situation that is reasonably possible when dealing with passwords.
I've found the solution thanks to different inputs.
#echo off
For /F "tokens=1* delims==" %%A IN (C:\parameters.txt) DO (
IF "%%A"=="env_user" set var_user=%%B
IF "%%A"=="env_pw" set var_pw=%%B
IF "%%A"=="env_url" set var_url=%%B
)
This will set the correct variables (not local) once the specific code name (before the = in parameters.txt) has been found.

Resources