Windows batch script to read an .ini file
I'm trying to read an .ini
file with the following format:
[SectionName]
total=4
[AnotherSectionName]
total=7
[OtherSectionName]
total=12
Basically I want to print out certain values from the .ini
file, for example the total under OtherSectionName
followed by the total from AnotherSectionName
.
Solution 1:
Here's a command file (ini.cmd
) you can use to extract the relevant values:
@setlocal enableextensions enabledelayedexpansion
@echo off
set file=%~1
set area=[%~2]
set key=%~3
set currarea=
for /f "usebackq delims=" %%a in ("!file!") do (
set ln=%%a
if "x!ln:~0,1!"=="x[" (
set currarea=!ln!
) else (
for /f "tokens=1,2 delims==" %%b in ("!ln!") do (
set currkey=%%b
set currval=%%c
if "x!area!"=="x!currarea!" if "x!key!"=="x!currkey!" (
echo !currval!
)
)
)
)
endlocal
And here's a transcript showing it in action (I've manually indented the output to make it easier to read):
c:\src>type ini.ini
[SectionName]
total=4
[AnotherSectionName]
total=7
[OtherSectionName]
total=12
c:\src>ini.cmd ini.ini SectionName total
4
c:\src>ini.cmd ini.ini AnotherSectionName total
7
c:\src>ini.cmd ini.ini OtherSectionName total
12
To actually use this in another cmd
file, just replace the echo %val%
line below with whatever you want to do with it):
for /f "delims=" %%a in ('call ini.cmd ini.ini AnotherSectionName total') do (
set val=%%a
)
echo %val%
Solution 2:
I know I'm a little late to the party, but I decided to write a general purpose ini file utility batch script to address this question.
The script will let you retrieve or modify values in an ini-style file. Its searches are case-insensitive, and it preserves blank lines in the ini file. In essence, it allows you to interact with an ini file as a sort of very rudimentary database.
This script will work fine if you're reading / writing only alphanumeric values or symbols that have no special meaning to the cmd
interpreter. If you need something capable of handling values containing ampersands, percents, etc, see the Update section below.
:: --------------------
:: ini.bat
:: ini.bat /? for usage
:: --------------------
@echo off
setlocal enabledelayedexpansion
goto begin
:usage
echo Usage: %~nx0 /i item [/v value] [/s section] inifile
echo;
echo Take the following ini file for example:
echo;
echo [Config]
echo password=1234
echo usertries=0
echo allowterminate=0
echo;
echo To read the "password" value:
echo %~nx0 /s Config /i password inifile
echo;
echo To change the "usertries" value to 5:
echo %~nx0 /s Config /i usertries /v 5 inifile
echo;
echo In the above examples, "/s Config" is optional, but will allow the selection of
echo a specific item where the ini file contains similar items in multiple sections.
goto :EOF
:begin
if "%~1"=="" goto usage
for %%I in (item value section found) do set %%I=
for %%I in (%*) do (
if defined next (
if !next!==/i set item=%%I
if !next!==/v set value=%%I
if !next!==/s set section=%%I
set next=
) else (
for %%x in (/i /v /s) do if "%%~I"=="%%x" set "next=%%~I"
if not defined next (
set "arg=%%~I"
if "!arg:~0,1!"=="/" (
1>&2 echo Error: Unrecognized option "%%~I"
1>&2 echo;
1>&2 call :usage
exit /b 1
) else set "inifile=%%~I"
)
)
)
for %%I in (item inifile) do if not defined %%I goto usage
if not exist "%inifile%" (
1>&2 echo Error: %inifile% not found.
exit /b 1
)
if not defined section (
if not defined value (
for /f "usebackq tokens=2 delims==" %%I in (`findstr /i "^%item%\=" "%inifile%"`) do (
echo(%%I
)
) else (
for /f "usebackq delims=" %%I in (`findstr /n "^" "%inifile%"`) do (
set "line=%%I" && set "line=!line:*:=!"
echo(!line! | findstr /i "^%item%\=" >NUL && (
1>>"%inifile%.1" echo(%item%=%value%
echo(%value%
) || 1>>"%inifile%.1" echo(!line!
)
)
) else (
for /f "usebackq delims=" %%I in (`findstr /n "^" "%inifile%"`) do (
set "line=%%I" && set "line=!line:*:=!"
if defined found (
if defined value (
echo(!line! | findstr /i "^%item%\=" >NUL && (
1>>"%inifile%.1" echo(%item%=%value%
echo(%value%
set found=
) || 1>>"%inifile%.1" echo(!line!
) else echo(!line! | findstr /i "^%item%\=" >NUL && (
for /f "tokens=2 delims==" %%x in ("!line!") do (
echo(%%x
exit /b 0
)
)
) else (
if defined value (1>>"%inifile%.1" echo(!line!)
echo(!line! | find /i "[%section%]" >NUL && set found=1
)
)
)
if exist "%inifile%.1" move /y "%inifile%.1" "%inifile%">NUL
Example
Contents of example.ini
:
[SectionName]
; This is a comment.
total=4
[AnotherSectionName]
# This is another comment.
total=7
[OtherSectionName]
And it should work with non-standard comments as well.
total=12
Test session:
C:\Users\me\Desktop>ini /s AnotherSectionName /i total example.ini
7
C:\Users\me\Desktop>ini /s othersectionname /i Total /v f00 example.ini
f00
C:\Users\me\Desktop>type example.ini
[SectionName]
; This is a comment.
total=4
[AnotherSectionName]
# This is another comment.
total=7
[OtherSectionName]
And it should work with non-standard comments as well.
Total=f00
Update
Apparently the pure batch solution chokes when it encounters characters like &
(and probably %
and others). So here's a more robust batch + JScript hybrid script that addresses that problem. The syntax and output are the same (but with an added /d
switch to delete item=value
pairs).
This script sets %ERRORLEVEL%=0
for success, and %ERRORLEVEL%=1
on error.
@if (@a==@b) @end /* -- batch / JScript hybrid line to begin JScript comment
:: --------------------
:: ini.bat
:: ini.bat /? for usage
:: --------------------
@echo off
setlocal enabledelayedexpansion
goto begin
:: color code by jeb -- https://stackoverflow.com/a/5344911/1683264
:c
set "param=^%~2" !
set "param=!param:"=\"!"
findstr /p /A:%1 "." "!param!\..\X" nul
<nul set /p ".=%DEL%%DEL%%DEL%%DEL%%DEL%%DEL%%DEL%"
exit /b
:: but it doesn't handle slashes. :(
:s
<NUL set /p "=/"&exit /b
:usage
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do set "DEL=%%a"
<nul > X set /p ".=."
echo Usage:
call :c 07 " query:"
call :c 0F " %~nx0 "&call :s&call :c 0F "i item ["&call :s&call :c 0F "s section] inifile"&echo;
call :c 07 " create or modify:"
call :c 0F " %~nx0 "&call :s&call :c 0F "i item "&call :s&call :c 0F "v value ["&call :s&call :c 0F "s section] inifile"&echo;
call :c 07 " delete:"
call :c 0F " %~nx0 "&call :s&call :c 0F "d item ["&call :s&call :c 0F "s section] inifile"&echo;
echo;
echo Take the following ini file for example:
echo;
echo [Config]
echo password=1234
echo usertries=0
echo allowterminate=0
echo;
echo To read the "password" value:
call :c 0F " %~nx0 "&call :s&call :c 0F "s Config "&call :s&call :c 0F "i password inifile"&echo;
echo;
echo To modify the "usertries" value to 5:
call :c 0F " %~nx0 "&call :s&call :c 0F "s Config "&call :s&call :c 0F "i usertries "&call :s&call :c 0F "v 5 inifile"&echo;
echo;
echo To add a "timestamp" key with a value of the current date and time:
call :c 0F " %~nx0 "&call :s&call :c 0F "s Config "&call :s&call :c 0F "i timestamp "&call :s&call :c 0F "v ""%DEL%%%%%date%%%% %%%%time%%%%""%DEL% inifile"&echo;
echo;
echo To delete the "allowterminate" key:
call :c 0F " %~nx0 "&call :s&call :c 0F "s Config "&call :s&call :c 0F "d allowterminate inifile"&echo;
echo;
call :c 07 "In the above examples, "&call :s
call :c 0F "s Config "
echo is optional, but will allow the selection of
echo a specific item where the ini file contains similar items in multiple sections.
del X
goto :EOF
:begin
if "%~1"=="" goto usage
for %%I in (item value section found) do set %%I=
for %%I in (%*) do (
if defined next (
if !next!==/i set "item=%%~I"
if !next!==/v (
set modify=true
set "value=%%~I"
)
if !next!==/d (
set "item=%%~I"
set modify=true
set delete=true
)
if !next!==/s set "section=%%~I"
set next=
) else (
for %%x in (/i /v /s /d) do if "%%~I"=="%%x" set "next=%%~I"
if not defined next (
set "arg=%%~I"
if "!arg:~0,1!"=="/" (
1>&2 echo Error: Unrecognized option "%%~I"
1>&2 echo;
1>&2 call :usage
exit /b 1
) else set "inifile=%%~I"
)
)
)
for %%I in (item inifile) do if not defined %%I goto usage
if not exist "%inifile%" (
1>&2 echo Error: %inifile% not found.
exit /b 1
)
cscript /nologo /e:jscript "%~f0" "%inifile%" "!section!" "!item!" "!value!" "%modify%" "%delete%"
exit /b %ERRORLEVEL%
:: Begin JScript portion */
var inifile = WSH.Arguments(0),
section = WSH.Arguments(1),
item = WSH.Arguments(2),
value = WSH.Arguments(3),
modify = WSH.Arguments(4),
del = WSH.Arguments(5),
fso = new ActiveXObject("Scripting.FileSystemObject"),
stream = fso.OpenTextFile(inifile, 1),
// (stream.ReadAll() will not preserve blank lines.)
data = [];
while (!stream.atEndOfStream) { data.push(stream.ReadLine()); }
stream.Close();
// trims whitespace from edges
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/,'') }
// trim + toLowerCase
String.prototype.unify = function() { return this.trim().toLowerCase(); };
// unquotes each side of "var"="value"
String.prototype.splitEx = function(x) {
for (var i=0, ret = this.split(x) || []; i<ret.length; i++) {
ret[i] = ret[i].replace(/^['"](.*)['"]$/, function(m,$1){return $1});
};
return ret;
}
// splices a new element into an array just after the last non-empty element. If first arg is a number, start at that position and look backwards.
Array.prototype.cram = function() {
for (var args=[], i=0; i<arguments.length; i++) { args.push(arguments[i]); }
var i = (typeof args[0] == "number" && Math.floor(args[0]) == args[0]) ? args.shift() : this.length;
while (i>0 && !this[--i].length) {};
for (var j=0; j<args.length; j++) this.splice(++i, 0, args[j]);
}
function saveAndQuit() {
while (data && !data[data.length - 1].length) data.pop();
var stream = fso.OpenTextFile(inifile, 2, true);
stream.Write(data.join('\r\n') + '\r\n');
stream.Close();
WSH.Quit(0);
}
function fatal(err) {
WSH.StdErr.WriteLine(err);
WSH.Quit(1);
}
if (section && !/^\[.+\]$/.test(section)) section = '[' + section + ']';
if (modify) {
if (section) {
for (var i=0; i<data.length; i++) {
if (data[i].unify() == section.unify()) {
for (var j=i + 1; j<data.length; j++) {
if (/^\s*\[.+\]\s*$/.test(data[j])) break;
var keyval = data[j].splitEx('=');
if (keyval.length < 2) continue;
var key = keyval.shift(), val = keyval.join('=');
if (key.unify() == item.unify()) {
if (del) data.splice(j, 1);
else {
data[j] = item + '=' + value;
WSH.Echo(value.trim());
}
saveAndQuit();
}
}
if (del) fatal(item + ' not found in ' + section + ' in ' + inifile);
data.cram(j ,item + '=' + value);
WSH.Echo(value.trim());
saveAndQuit();
}
}
if (del) fatal(section + ' not found in ' + inifile);
data.cram('\r\n' + section, item + '=' + value);
WSH.Echo(value.trim());
saveAndQuit();
}
else { // if (!section)
for (var i=0; i<data.length; i++) {
var keyval = data[i].splitEx('=');
if (keyval.length < 2) continue;
var key = keyval.shift(), val = keyval.join('=');
if (key.unify() == item.unify()) {
if (del) data.splice(i, 1);
else {
data[i] = item + '=' + value;
WSH.Echo(value.trim());
}
saveAndQuit();
}
}
if (del) fatal(item + ' not found in ' + inifile);
data.cram(item + '=' + value);
WSH.Echo(value.trim());
saveAndQuit();
}
}
else if (section) { // and if (!modify)
for (var i=0; i<data.length; i++) {
if (data[i].unify() == section.unify()) {
for (var j=i + 1; j<data.length; j++) {
if (/^\s*\[.+\]\s*$/.test(data[j])) fatal(item + ' not found in ' + section + ' in ' + inifile);
var keyval = data[j].splitEx('=');
if (keyval.length < 2) continue;
var key = keyval.shift(), val = keyval.join('=');
if (key.unify() == item.unify()) {
WSH.Echo(val.trim());
WSH.Quit(0);
}
}
}
}
fatal(section + ' not found in ' + inifile);
}
else { // if (item) and nothing else
for (var i=0; i<data.length; i++) {
var keyval = data[i].splitEx('=');
if (keyval.length < 2) continue;
var key = keyval.shift(), val = keyval.join('=');
if (key.unify() == item.unify()) {
WSH.Echo(val.trim());
WSH.Quit(0);
}
}
fatal(item + ' not found in ' + inifile);
}
Solution 3:
I have short proposition for read config.ini file in current directory form windows batch (.bat):
Near end of batch file we paste that code:
:ini
@for /f "tokens=2 delims==" %%a in ('find "%~2=" "%~1"') do @set %~3=%%a
@goto:eof
And near start of batch file we call it by:
@call:ini "config.ini" IniFieldName batchVarName
@echo IniFieldName is: %batchVarName%
Solution 4:
config.ini
foo=string
bar=123
baz=spaces work too!
windows_batch.cmd
for /F "tokens=*" %%I in (config.ini) do set %%I
Solution 5:
Old question but I just recently needed that and found @paxdiablo answer. I needed something more so I enriched his answer and I'm now giving back.
What I also needed was finding which key held a specific value. Also, explicitly support root section (no section name).
Here's my code, a function I put into a library (CMDLib variable) I call when I need it (among other functions).
:ReadINI
REM ReadINI - Get value from [Section]; Key from an INI File.
REM Arguments:
REM File INI-file to read from
REM Key Name of the entry
REM Section Name of the [Section] under which the Value is.
REM Optional, will find a value from the root section if empty.
REM For root section, set to "-" to also use "Value"
REM Value If Key is set to "-", will find which Key has "Value"
REM
REM Returns: A string of text will be echoed, ready for logging.
REM An echo of the value.
REM
REM Call example:
REM for /f "delims=" %%a in ('Call "%CMDLib%" ReadINI "Inifile" Key Section') do ( set Value=%%a)
REM
REM Original: http://stackoverflow.com/a/2866328/151152
rem ------- Function header -------
Setlocal ENABLEDELAYEDEXPANSION
:: Logging formatting
if not defined nest (set /a nest=0) else set /a Nest=%nest%+1
if %nest% GEQ 1 if not defined _tab (set _tab= ) else for /l %%i in (0, %nest%,1) do set _tab=%_tab%
rem ------- Function body -------
set file=%~1
set key=%~2
set Section=[%~3]
if "%Section%"=="-" set Section=
set value=%~4
set currSection=
Set RC=0
for /f "usebackq delims=" %%a in ("%file%") do (
set ln=%%a
if "x!ln:~0,1!"=="x[" (
set currSection=!ln!
) else (
for /f "tokens=1,2 delims==" %%b in ("!ln!") do (
set currkey=%%b
set currval=%%c
if /i "x!Section!"=="x!currSection!" (
if /i "x!key!"=="x!currkey!" (
echo !currval!
if %_D% GEQ 2 echo %_tab%[%0 - RC:%RC%]
exit /b %RC%
) Else if "x!key!"=="x-" (
if /i "x!value!"=="x!currval!" (
echo !currkey!
if %_D% GEQ 2 echo %_tab%[%0 - RC:%RC%]
exit /b %RC%
)
)
)
)
)
)
if %_D% GEQ 2 echo %_tab%[%0 - RC:%RC%]
Exit /b %RC%
rem ------- Function end -------
No syntax highlighting for CMD? That's a shame.. ;-)
Hope this helps other as well.