Parsing string in batch file - windows

I have the following string:
MyProject/Architecture=32bit,BuildType=Debug,OS=winpc
I would like to be able to grab the values 32bit, Debug, and winpc and store them in variables named Architecture, BuildType, and OS to reference later in the batch script. I'm normally a Unix guy so this is new territory for me. Any help would be greatly appreciated!

This should do it:
FOR /F "tokens=1-6 delims==," %%I IN ("MyProject/Architecture=32bit,BuildType=Debug,OS=winpc") DO (
ECHO I %%I, J %%J, K %%K, L %%L, M %%M, N %%N
)
REM output is: I MyProject/Architecture, J 32bit, K BuildType, L Debug, M OS, N winpc
The batch FOR loop is a pretty interesting piece of machinery. Type FOR /? in a console for a description of some of the crazy stuff it can do.

Here is an interesting solution that doesn't care how many or what order the name=value pairs are specified. The trick is to replace each comma with a linefeed character so that FOR /F will iterate each name=value pair. This should work as long as there is only one / in the string.
#echo off
setlocal enableDelayedExpansion
set "str=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc"
::Eliminate the leading project info
set "str=%str:*/=%"
::Define a variable containing a LineFeed character
set LF=^
::The above 2 empty lines are critical - do not remove
::Parse and set the values
for %%A in ("!LF!") do (
for /f "eol== tokens=1* delims==" %%B in ("!str:,=%%~A!") do set "%%B=%%C"
)
::Display the values
echo Architecture=%Architecture%
echo BuildType=%BuildType%
echo OS=%OS%
With a bit more code it can selectively parse out only name=value pairs that we are interested in. It also initializes the variables to undefined in case the variable is missing from the string.
#echo off
setlocal enableDelayedExpansion
set "str=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc"
::Eliminate the leading project info
set "str=%str:*/=%"
::Define a variable containing a LineFeed character
set LF=^
::The above 2 empty lines are critical - do not remove
::Define the variables we are interested in
set "vars= Architecture BuildType OS "
::Clear any existing values
for %%A in (%vars%) do set "%%A="
::Parse and conditionally set the values
for %%A in ("!LF!") do (
for /f "eol== tokens=1* delims==" %%B in ("!str:,=%%~A!") do (
if !vars: %%B ! neq !vars! set "%%B=%%C"
)
)
::Display the values
for %%A in (%vars%) do echo %%A=!%%A!

Try the following:
#ECHO OFF
SET Var=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc
FOR /F "tokens=1,2,3 delims=," %%A IN ("%Var%") DO (
FOR /F "tokens=1,2 delims==" %%D IN ("%%A") DO (
SET Architecture=%%E
)
FOR /F "tokens=1,2 delims==" %%D IN ("%%B") DO (
SET BuildType=%%E
)
FOR /F "tokens=1,2 delims==" %%D IN ("%%C") DO (
SET OS=%%E
)
)
ECHO %Architecture%
ECHO %BuildType%
ECHO %OS%
PAUSE

Related

how to get a unique string from a text file using Batch script

I have a text file that contains the following information:
-host A -P 1
-host A -P 2
-host B -P 1
-host B -P 2
-host B -P 3
-host C -P 1
-host C -P 2
-host A -P 3
Now from a Batch script, I want to extract these hostnames uniquely. For example, after fetching my array should look like this: [A B C]
I was able to get these hostnames with this below command:
setlocal ENABLEDELAYEDEXPANSION
set /A f=0
if exist "%TEXTFILE%" for /F usebackq^ delims^=^ eol^= %%I in (%TEXTFILE%) do for %%J in (%%I) do (
if /I "%%~J" == "-host" (
set /A f=1
) else (
if !f!==1 (
echo %%J
set /A f=0
)
)
)
I am getting the following output:
A
A
B
B
B
C
C
A
But I only want unique names here.
How should we get the unique names from the text file?
#echo off
setlocal
for /f "tokens=2" %%a in (test.txt) do set "_%%a=."
for /f "delims=_=" %%a in ('set _') do echo %%a
Output:
A
B
C
The first for sets a variable for each name (we don't care for the content).
The second for lists those variables. For that to happen, all variables have to start with the same character, and no other variables may start with that letter. The set command will even sort them alphabetically for you.
#echo off
setlocal enabledelayedexpansion
set "list=["
for /f "tokens=2" %%a in (test.txt) do set "_%%a=."
for /f "delims=_=" %%a in ('set _') do set "list=!list! %%a"
set "list=%list:[ =[%]"
echo %list%
Output:
[A B C]
What follows is a rather complex looking batch file, written only for use on Windows 10. The code uses the new, but undocumented, /unique option of the sort command only in that OS version. I should mention here that the sort command is case insensitive so, a and A, for instance, are not unique.
It attempts to get all trimmed content between the line leading -host word and any following -P* word. I did it this way to ensure that strings containing spaces would still be captured. Whitespace is disallowed in hostnames, so you should not need this extra functionality, however, for generic use, it may be useful to you. In addition, should you wish to modify this for other uses later, it does not currently capture substrings beginning with a hyphen, which are also disallowed in hostnames, (you would need to modify the findstr sub-match [^-] on line 8 to allow for such strings). Finally, should your case insensitive line leading word not be -host, you can change that on line 8, and if your second hyphen leading word doesn't begin with the case insensitive character pair -P, you could modify that on line 11.
The intended output should be a variable %array%, which using the example you've posted, should look like this:
"A","B","C"
If you'd prefer not to have the commas, change ^, on line 37 to . Also, if, you dont need the enclosing doublequotes, change "%%I" on line 35 to %%I, and "!$:~1!" on line 37 to !$:~1!.
It should also create individual variables, for each array indexed item, in the format %array[#]%, where # is the index number, zero based, (you could adjust that by changing Set "$=-1" on line 17 to Set "$=0" and array[0] on line 28 to array[1]). Using your posted example the current code should produce the following:
%array[0]% - expanding to string value A
%array[1]% - expanding to string value B
%array[2]% - expanding to string value C
Here is the batch file example, please remember to adjust the value of %TEXTFILE% to the full, (recommended), or relative, path of your source file on line 3:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "TEXTFILE=myfile.txt"
Set "$T=%TEMP%\$.lst"
If Not Exist "%TEXTFILE%" GoTo :EOF
For /F "Delims==" %%G In ('"(Set array) 2>NUL"') Do Set "%%G="
( For /F Delims^=^ EOL^= %%G In ('Type "%TEXTFILE%" ^|
%SystemRoot%\System32\findstr.exe /RIC:"^-host[ ][ ]*[^-]"') Do (
Set "$=%%G"
SetLocal EnableDelayedExpansion
For /F "Delims=&" %%H In ("!$: -P=&:!") Do (
EndLocal
For /F "Tokens=1,*" %%I In ("%%H") Do Echo %%J
)
)
) 1>"%$T%"
Set "$=-1"
For /F Delims^=^ EOL^= %%G In (
'%SystemRoot%\System32\sort.exe /Unique "%$T%" ^& Del "%$T%"'
) Do (
Set /A $ += 1
SetLocal EnableDelayedExpansion
For %%H In (!$!) Do (
EndLocal
Set "array[%%H]=%%G"
)
)
If Not Defined array[0] GoTo :EOF
For /F "Tokens=1,* Delims=]" %%G In ('"(Set array[) 2>NUL"') Do (
Set "$=%%H"
SetLocal EnableDelayedExpansion
If Not Defined array (
For /F Delims^=^ EOL^= %%I In ("!$:~1!") Do (
EndLocal
Set "array="%%I""
)
) Else For /F UseBackQ^ Delims^=^ EOL^= %%I In ('!array!^,"!$:~1!"') Do (
EndLocal
Set "array=%%I"
)
)
For %%G In (TEXTFILE $T $) Do Set "%%G="
Set array & Pause
The last line is included just to ensure that you can see the resultant variables with their values. After testing, you can replace that with your own code, as needed.

Windows Batch File: Loop on rows and split string

I have a text file with the following format:
name1:surname1
name2:surname2
name3:surname3
and so on.
I need to write a for loop in window batch script and assign to 2 variables
name=name1
surname=surname1
and so on. Something like (this is wrong)
for /F "tokens=*" %%row in (myfile.txt) do (
for /F "tokens=1* delims=:" %%u in ("%row%") do (
....
)
)
Any suggest?
You don't really need two nested loops for that.
What you probably will need, however, is delayed variable expansion.
#echo off
setlocal enabledelayedexpansion
for /f "tokens=1,2 delims=:" %%u in (myfile.txt) do (
set "name=%%u"
set "surname=%%v"
echo !surname!, !name!
)
outputs this for me:
surname1, name1
surname2, name2
surname3, name3
Delayed variable expansion is what allows you to assign the single-letter loop variables (u and v in this case) to real variables and use them in the rest of the loop, by accessing them with ! instead of %.
for /F "tokens=*" %%r in (myfile.txt) do (
for /F "tokens=1* delims=:" %%u in ("%%r") do (
....
)
)
or
for /F "tokens=1* delims=:" %%u in (myfile.txt) do (
....
)
Metavariables live r and u are limited to a single (case-sensitive) alphabetical character.
#echo off
setlocal EnableDelayedExpansion
for /F "tokens=1,2 delims=:" %%u in (myfile.txt) do (
set name=%%u
set surname=%%v
echo name=!name!
echo surname=!surname!
)
output:
name=name1
surname=surname1
name=name2
surname=surname2
name=name3
surname=surname3

Bypass native variables sorting in a batch FOR loop

I need to read an arbitrary number of variable "names" from one file, and later assign them "values" supplied by another file, with both sources not available at once. I tried using the code below, but the problem is, SET command natively sorts variables alphabetically, thus preventing correct value assignments. Is there an alternative approach to set variables in this case, or a way to block native Cmd vars sorting by SET? I don't want setting numbered variable arrays if possible, as they complicate the code by adding extra layer of variables:
#echo off
setlocal EnableDelayedExpansion
for /f "tokens=1" %%i in (%args1_file%) do (
set "%%i=0" & set "%%i=_%%i")
for /f "tokens=1 delims==" %%i in ('set _') do (
for /f "tokens=1" %%j in (%args2_file%) do (
set "%%i=%%j"
if not !%%i! equ 0 (echo %%i = %%j
) else (set /p "%%j=Enter %%i > " 2>nul)
call :validate
)
:: more code using vars %%i
exit /b
:validate
Assuming you mean the sorted order returned by set, there's no way around it.
It's not documented anywhere I can see, but the environment variable block maintained by GetEnvironmentStrings() and friends is maintained in sorted order, at least in every NT OS I've seen, and probably before then. When you add a new string to it's list, it's added in sorted position, so the order of addition is lost by the system.
I think you can set the variables based off names in one file with values in another file by:
#echo off
setlocal enabledelayedexpansion
set _i=0
for /f "tokens=1" %%i in (names.txt) do (
set _val_!_i!=%%i
set /a _i=!_i!+1
)
set _i=0
for /f "tokens=1" %%i in (vals.txt) do (
set _temp=_val_!_i!
call set __%%!_temp!%%=%%i
set /a _i=!_i!+1
)
echo one == !__one!
echo two == !__two!
echo three == !__three!
I found the approach that doesn't use a numbered array to read var names and values from separate files, and won't cause vars sorting to ensure correct value assignments:
#echo off
setlocal EnableDelayedExpansion
for /f "tokens=1" %%i in (%args1_file%) do (
set "%%i=0" & set "vars=!vars! _%%i")
for %%i in (!vars!) do (
for /f "tokens=1" %%j in (%args2_file%) do (
if not %%j.==. set %%i=%%j)
if not !%%i! equ 0 (echo %%i = %%j
) else (set /p "%%j=Enter %%i > " 2>nul)
call :validate
)
:: more code using vars %%i
exit /b

Groupby and count on lines of a text file in Windows CMD

I have a long files with identifiers, e.g.
A
A
B
C
A
C
I would like to do a group by, count and sort operation to get a file with:
A 3
C 2
B 1
How can I achieve it in a CMD script?
Global edit - All code has been modified to allow - in identifiers. Identifiers must not contain !
Assuming the identifiers do not contain = or $ or !, and the identifiers are NOT case sensitive, the following lists the counts sorted by identifier.
#echo off
setlocal enableDelayedExpansion
:: Clear any existing $ variables
for /f "delims==" %%V in ('set $ 2^>nul') do set "%%V="
:: Get a count of each identifier
for /f "usebackq delims=" %%A in ("test.txt") do (
set /a "cnt=!$%%A!+1"
set "$%%A=!cnt!"
)
:: Write the results to a new file
>output.txt (
for /f "tokens=1,2 delims=$=" %%A in ('set $') do echo %%A %%B
)
:: Show the result
type output.txt
The prefix can be adapted as needed. But this technique cannot be used if the identifiers are case sensitive.
EDIT
Here is a version that sorts the result by count descending
#echo off
setlocal enableDelayedExpansion
:: Clear any existing $ variables
for /f "delims==" %%V in ('set $ 2^>nul') do set "%%V="
:: Get a count of each identifier
for /f "usebackq delims=" %%A in ("test.txt") do (
set /a "cnt=!$%%A!+1"
set "$%%A=!cnt!"
)
:: Write a temp file with zero padded counts prefixed to the left.
>temp.txt (
for /f "tokens=1,2 delims=$=" %%A in ('set $') do (
set "cnt=000000000000%%B"
echo !cnt:~-12!=%%A=%%B
)
)
:: Sort and write the results to a new file
>output.txt (
for /f "tokens=2,3 delims=$=" %%A in ('sort /r temp.txt') do echo %%A %%B
)
del "temp.txt"
:: Show the result
type output.txt
EDIT 2
And here is another option sorted by count descending that assumes REPL.BAT is somewhere within your PATH
#echo off
setlocal enableDelayedExpansion
:: Clear any existing $ variables
for /f "delims==" %%V in ('set $ 2^>nul') do set "%%V="
:: Get a count of each identifier
for /f "usebackq delims=" %%A in ("test.txt") do (
set /a "cnt=!$%%A!+1"
set "$%%A=!cnt!"
)
:: Sort result by count descending and write to output file
set $|repl "\$(.*)=(.*)" "000000000000$2=$1 $2"|repl ".*(.{12}=.*)" $1|sort /r|repl ".{13}(.*)" $1 >output.txt
:: Show the result
type output.txt

Shell script hash table

I'm trying to translate a .bat file into a .sh script. Several parameters are passed to the script, one of these being a hash table. The code looks like...
date /T
time /T
FOR /F "tokens=1-11" %%A IN (%4) DO (
set args1=%%A %%B %%C %%D %%E %%F %%G %%H %%I %%J %%K
)
FOR /F "tokens=12" %%A IN ("%4") DO (
set args2=%%A
)
FOR /F "tokens=12*" %%A IN (%4) DO (
set dummy=%%A
set args3=%%B
)
I'm not sure what is going on here, or how to handle it?
Any suggestions? Or good reference pages online I can take at look at?
Here is a good reference page: http://technet.microsoft.com/en-us/library/bb490909.aspx
Breakdown
The first loop is treating the input as a filenameset.
This is storing the first 11 whitespace delimited items in the variable args1.
The second loop is treating the input as a literal string.
This is storing just the 12 whitespace delimited item in the variable args2.
The last loop is treating the input as a filenameset.
This is storing all the remaining whitespace delimited items after the 12th item in the variable args3.
Example
I would recommend adding the echo command after each loop so you can see what the parsed values look like.
FOR /F "tokens=1-11" %%A IN (%4) DO (
set args1=%%A %%B %%C %%D %%E %%F %%G %%H %%I %%J %%K
)
echo %args1%
FOR /F "tokens=12" %%A IN ("%4") DO (
set args2=%%A
)
echo %args2%
FOR /F "tokens=12,*" %%A IN (%4) DO (
set args3=%%B
)
echo %args3%

Resources