How to loop through tokens in a string? - batch-file

Say I have a string such as foo:bar:baz, is it possible to loop through this string? It looked like you could tokenize lines of a file but the following will only echo 'foo' once.
for /f "tokens=1,2* delims=:" %%x in ("%%j") do echo %%x

set string=foo:bar:baz
for %%x in (%string::= %) do echo %%x
FOR value delimiters may be space, comma, semicolon and equal-sign. You may directly process a string if the elements are delimited with any of these characters. If not, just change the delimiter for one of these character (as I did above).
set string=foo bar,baz;one=two
for %%x in (%string%) do echo %%x

Aacini beat me to my first solution. It can be improved by adding quotes so that the string can contain spaces and special characters and still give the correct result.
set "str=foo bar:biz bang:this & that"
for %%S in ("%str::=" "%") do echo %%~S
The solution has limitations:
No * or ? can appear in the string
Problems can arise if the string already contains quotes ("), especially if there are special characters as well
The second solution has bizarre syntax, but the concept is fairly straight forward. FOR /F with "string" will break on linefeed characters - each line will be processesed as its own string. The trick is to replace the : delimiter with a linefeed character. Note that the blank line in the solution below is a critical part of the tricky replacement. Also, the following line must start with the ! which terminates the delayed variable expansion. There should not be any leading spaces.
The other thing to worry about is the pesky FOR /F "EOL" option. We don't want to skip any values that start with the EOL character which is ; by default. Since we have eliminated the string delimiter :, we can safely use that as the EOL.
Finally, we need to use delayed expansion, but ! will be corrupted during %%S expansion if we don't first disable delayed expansion within the loop.
set "str=foo bar:biz bang:this & that "^& the other thing!":;How does this work?"
setlocal enableDelayedExpansion
set ^"str=!str::=^
!"
for /f "eol=: delims=" %%S in ("!str!") do (
if "!!"=="" endlocal
echo %%S
)
I believe this technique can handle just about anything you throw at it.
jeb was the first to show me how to work with line feeds within batch, and especially how to use this technique. It may even be posted already somewhere else on SO.

it does as expected, it tokenizes it to %%x %%y %%z. So instead of just processing %%x, you might...
for /f "tokens=* delims=:" %%x in ("foo:bar:baz") do (
echo %%x
echo %%y
echo %%z
)
or if you don't know in advance how many items you got, you may...
#echo off
set string=foo:bar:baz
:again
for /f "tokens=1* delims=:" %%x in ("%string%") do (
echo %%x
set string=%%y
)
if not .%string%==. goto again
echo done

This will parse the string into variables accessable outside of the loop (;
setlocal enabledelayedexpansion
set your_string="whatever:you:want"
for /f "tokens=1,2,3 delims=:" %%a in ('echo !your_string!') do (
set first_sub_string=%%a
set second_sub_string=%%b
set third_sub_string=%%c
)
echo Congrats. !your_string! was parsed as !first_sub_string!, !second_sub_string!, !third_sub_string!

Related

How to use FOR to find string in file, strip it of spaces, set variable to it for token?

#ECHO off
setlocal EnableDelayedExpansion
set "VAR=da da da YES123123"
echo %VAR% > testing.txt
FOR /F %%a in ('findstr "YES" .\testing.txt') do (
set BLAH=%%a
set "BLAH2=%BLAH: =%"
set "FINAL=%BLAH2:~15%"
echo %FINAL%
)
endlocal
Whether WITH or WITHOUT "setlocal EnableDelayedExpansion" the batch file simply does not work.
But that's all I have so far. However I also want to strip the preceding characters from the FINDSTR string, but set a variable to the FINDSTR string AND IT'S SUCCEEDING 123123 characters. Unfortunately, it doesn't work. It only outputs "ECHO is off."
I have used FOR correctly in other ways, but I can't figure it out this time. Any help would be greatly appreciated.
to get the last word, use a plain for to split your string by default delimiters (space, tab, comma, =). No need to know, how many tokens there are. The following works, even if there are more lines with YES:
for /f "delims=" %%a in ('findstr "YES" .\testing.txt') do (
for %%b in (%%a) do set final=%%b
echo !final!
)
EDIT to find YES until end of line:
There is a token * for "all the rest", so you could do:
for /f "tokens=3,* delims= " %%a in ('findstr "YES" .\testing.txt') do echo %%b
but I recommend another method (replacing *YES with just YES). set can do limited wildcard replacement (*string works, but string* does not). Advantage: you don't need to know, which token YES... is:
for /f "delims=" %%a in ('findstr "YES" testing.txt') do (
set line=%%a
echo !line:* YES=YES!
)
Understand first meaning
Tokens mean take only the column 1 or 2 or ...... of stdout
skip mean skip line 1,2........... from the list of text of stdout
delims mean the way this columns seperated between each other with what (space or dot or slash ..........)
Ok so i figured it out. Had to use the "token=1,2,3,4, delim= " thing, which I hate using and it's confusing but have used it before for other purposes. I was just really confused on how to echo or set the variable to the token at the end of the command. Here is the corrected code.
FOR /F "tokens=1,2,3,4 delims= " %%a in ('findstr "YES" .\testing.txt') do echo %%d

Reading large file from CMD and cutting information

I'm writing batch script on Windows, with it's help I would like to sort out information from many files to smaller files.
I got ~3000 long lines in log files, from whom I need get few things, basically there are name and value (example ",INC_LIMI=050,ISO_LIMI=050,INC_MLIM=074,"), and everything is separated with "," symbol. My question how you can read long string line and just read values like:
String LineString[]
LineString = Line.split(,)
String s = "INC_MLIM"
For elem in LineString
if elem.exist(s)
NewLine.append(elem)
and latter on just save to new file.
EDIT:
There is service.log file which contains multiple lines with same variable names, but I don't need all of them so the thing I'm trying to do is
From line :
",INC_MLIM=074,ISO_MLIM=074,LOC_LI_P=050,LOC_LI_L=050,TRI_LI_P=074,TRI_LI_L=074,"
Transform to new line structure with less variables and separate with tabs instead of comma. New line should look something like this:
"INC_MLIM=074 ISO_MLIM=074 LOC_LI_L=050 TRI_LI_L=074"
You don't state which values you want. I'll arbitrarily assume you want INC_LIMI and INC_MLIM.
Like most any text file manipulation, this is a pain to do with pure batch. But it is possible.
I'm assuming your lines are all <8192 characters long. If you have lines that are longer than that, then a pure batch solution is not possible, and you should skip right down to the bottom of this answer for a JREPL solution
Batch does not have a convenient split function that allows splitting at a specific user defined character. The FOR command almost works, but it also splits at ;, =, <tab>, and <space>. So it is not a good choice.
With the correct arcane syntax, you can use variable expansion find/replace to substitute a newline (0x0A) for every comma. This will generate one name=value pair per line, which is very convenient for letting FINDSTR filter out the values that you want.
Here is a solution that relies on a temporary table. This iterates all *.log files, and for each one, it creates output in *.log.new.
#echo off
setlocal enableDelayedExpansion
(set LF=^
%= This creates a newline 0x0A character =%
)
for %%N in ("!LF!") do for %%F in (*.log) do (
(
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=%%A"
echo(!ln:,=%%~N!
)
)>"%%F.temp"
findstr /b "INC_LIMI= INC_MLIM=" "%%F.temp" >"%%F.new"
del "%%F.temp"
)
type *.log.new
exit /b
Note that the above can fail if your log files contain !. This could be solved by toggling delayed expansion on and off as needed.
Some people don't like to use temp files. In this case, getting rid of the temp file introduces even more arcane batch constructs. But it does eliminate the ! delayed expansion issue, and the code is shorter. This version can also be significantly slower if the source files are very large.
#echo off
setlocal disableDelayedExpansion
(set LF=^
%= This creates a newline 0x0A character =%
)
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=%%A"
cmd /v:on /c "for %%N in ("!LF!") do #echo(!ln:,=%%~N!"|findstr /b "INC_LIMI= INC_MLIM="
)
) >"%%F.new"
type *.log.new
exit /b
It is also possible to solve this without using FINDSTR. But this solution assumes the same name never appears more than once on any given line, and all found names have a value:
#echo off
setlocal disableDelayedExpansion
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=,%%A"
for %%N in (INC_LIMI INC_MLIM) do call :findName %%N
)
) >"%%F.new"
type *.log.new
exit /b
:findName Name
setlocal enableDelayedExpansion
set "test=!ln!"
:loop
set "test2=!test:*,%1=!"
if "!test2!" equ "!test!" return
if not defined test2 return
if "!test2:~0,1!" neq "=" set "test=,!test2:*,=!" & goto :loop
for /f "delims=," %%V in ("!test2:~1!") do (
endlocal
echo(%1=%%V
)
exit /b
Here is a variation that handles empty values, but can break if a value contains quotes or poison characters:
#echo off
setlocal disableDelayedExpansion
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=,%%A"
for %%N in (INC_LIMI INC_MLIM) do call :findName %%N
)
) >"%%F.new"
type *.log.new
exit /b
:findName Name
setlocal enableDelayedExpansion
set "test=!ln!"
:loop
set "test2=!test:*,%1=!"
if "!test2!" equ "!test!" return
if not defined test2 return
if "!test2:~0,1!" neq "=" set "test=,!test2:*,=!" & goto :loop
set "test2=%1!test2!
endlocal&echo(%test2:,=&rem %
exit /b
But I wouldn't use any of the above. In fact, I would never restrict myself to pure batch because text file manipulation is so darn inefficient and inscrutable.
Instead, I would use JREPL.BAT - a regular expression command line text processing utility. JREPL.BAT is pure script (hybrid JScript/batch) that runs natively on any Windows machine from XP onward - no 3rd party exe file required.
With JREPL, the solution is as simple as
#echo off
for %%F in (*.log) do call jrepl "(?:^|,)((?:INC_LIMI|INC_MLIM)=[^,]*)" "$txt=$1" /jmatchq /f "%%F" /o "%%F.new"
type *.log.new
Not only is the code nice and clean, it is much faster than any pure batch solution.

Replace characters in filename through .bat

I have a file named - for example: 01_XXXXXXXX_XXXX_XXX.txt.
I need to strip out the first three characters (replace 01_ by nothing) and replace the remaining _ by SPACEs.
I cannot use PowerShell, I do need to have a simple .batfile, to loop through all files in the directory where it is present, and do this task.
So I am using this:
#echo off
setlocal enabledelayedexpansion
for %%a in (*_*) do (
set file=%%a
ren "!file!" "!file:_= !"
)
#echo off
setlocal enabledelayedexpansion
set X=2
for %%f in (*) do if %%f neq %~nx0 (
set "filename=%%~nf"
set "filename=!filename:~%X%,-%X%!"
ren "%%f" "!filename!%%~xf"
)
popd
But it is eating two characters at the end before the extension and adding a SPACE at the beginning.
Any idea why?
You are splitting off the first two and the last two characters, as you implemented the sub-string expansion syntax wrongly. The leading SPACE derives from the first _ before the replacements.
The following shows a reliable way of doing it, using a single loop and sub-string replacement syntax only, the first time with the * immediately after the :, telling the command line interpreter to replace everything up to and including the first occurrence of the search string:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%A in ("*_*") do (
rem // Store current item:
set "FILE=%%~A"
rem // Toggle delayed expansion to avoid loss of or trouble with `!`:
setlocal EnableDelayedExpansion
rem // Remove everything up to and including the first `_`:
set "FNEW=!FILE:*_=!"
rem // Replace every remaining `_` by ` `:
ren "!FILE!" "!FNEW:_= !"
endlocal
)
endlocal
exit /B
If your pattern is consistent with 01_XXXXXXXX_XXXX_XXX.txt (i.e. an unrequired string ending with _ followed by three required strings separated by _), then a one liner like this may suffice:
From a batch file:
#For /F "Tokens=1-4 Delims=_" %%A In ('Where ?*_?*_?*_?*.txt') Do #If Not Exist "%%B %%C %%D" #Ren "%%A_%%B_%%C_%%D" "%%B %%C %%D"
From the command prompt:
For /F "Tokens=1-4 Delims=_" %A In ('Where ?*_?*_?*_?*.txt') Do #If Not Exist "%B %C %D" #Ren "%A_%B_%C_%D" "%B %C %D"

How to seprate space and more than one space delimiter in a batch For loop?

I have a problem to parse a text as a result of "net user" command
ABC ABCDE ABCDEFG
ABDCS HFJ ATi CObdnsen
to single users in a separate lines in batch script.
my code is this :
FOR /F "tokens=1,2,3 delims= " %%A in (test.txt) do echo %%A & echo %%B & echo %%C
the problem is the second line of my text that contains a username with space in the middle.
Note that i want this result :
ABC
ABCDE
ABCDEFG
ABDCS HFJ
ATi
CObdnsen
so what is the solution?
That looks to be fixed width columns, 25 characters per column. You could load each line into a variable and use substring operations to get the values.
#echo off
setlocal enableDelayedExpansion
for /f delims^=^ eol^= %%A in (test.txt) do (
set "ln=%%A"
echo col1=[!ln:~0,25!]
echo col2=[!ln:~25,25!]
echo col3=[!ln:~50!]
echo(
)
But that leaves you with the problem of removing trailing spaces from the end of each value. It is doable, but not easy with batch. My output from the above script encloses the values within square brackets so you can easily see the trailing space issue.
Instead of messing with the issue of removing trailing spaces, I would use my REPL.BAT utility to transform the data from fixed width to delimited format. REPL.BAT is a hybrid JScript/batch utility that performs a regular expression search/replace operation on stdin and writes the result to stdout. It is pure script that will run on any modern Windows machine from XP onward without needing any 3rd party executables. Full documentation is embedded within the script.
I would use REPL.BAT to insert a delimiter after the 25th and 50th characters. I would then use another REPL.BAT to strip out any spaces that precede the delimiter, and then a normal FOR /F can safely parse the values. I chose to use a pipe (|) as the delimiter.
#echo off
for /f "eol=| delims=| tokens=1-3" %%A in (
'type test.txt ^| repl "^(.{25})(.{25})" "$1|$2|" ^| repl " *\|" "|"'
) do (
echo col1=[%%A]
echo col2=[%%B]
echo col3=[%%C]
echo(
)
If you know that no value contains consecutive spaces, and all values are separated by at least 2 spaces, then you can get by with a single REPL that replaces 2 or more spaces with a delimiter
#echo off
for /f "eol=| delims=| tokens=1-3" %%A in (
'type test.txt ^| repl " {2,}" "|"'
) do (
echo col1=[%%A]
echo col2=[%%B]
echo col3=[%%C]
echo(
)
#echo off
setlocal enableextensions
for /f "tokens=*" %%a in ('net user^|find " "') do (
set "u=%%a"
setlocal enabledelayedexpansion
set "u=!u: =/!"
set "u=!u:/ =/!"
for /f "tokens=1 delims=/" %%b in ("!u!") do echo %%b
endlocal
)
endlocal
Not bullet proof. Will fail for user names with two spaces in it or (probably, not tested) with names of 24 characters or more (just to name two).
Thanks MC ND for your great idea... But your Answer should be edited like below for the best result :
#echo off
setlocal enableextensions
for /f "tokens=*" %%a in ('net user^|find " "') do (
set "u=%%a"
setlocal enabledelayedexpansion
set "u=!u: =/!"
set "u=!u:/ =/!"
for /f "tokens=1,2,3 delims=/" %%b in ("!u!") do echo %%b & echo %%c & echo %%d
endlocal
)
endlocal
Note that the delimiter is "5 spaces". because the most allowed user length is 20 character in windows. and the distance between the columns are 25 character.. so we have at least 5 spaces as a good delimiter. and we are not worry about spaces in the middle of a username.
and at last your should parse three tokens . Not only 1 token.
Thanks for your attention guys.

Read words separated by space in a batch script

I need to read the content of a text file word by word, in a batch script. The words are space separated. I tried this but it doesn't work, it only takes the first word:
for /f "delims= " %%i in (%OUTPUT_FILE%) do echo %%i
I also tried this but doesn't work also:
for /f "tokens=* delims= " %%i in (%OUTPUT_FILE%) do echo %%i
for /f "delims=" %%a in (file.txt) do for %%b in (%%a) do echo %%b
Delimiters: space, tab, 0xFF, , ; =.
Here is a pure batch solution that should be able to handle any content in your file. It reads each line with an outer FOR /F, and replaces each space with a newline character. It reads each resulting line (word) with an inner FOR /F.
#echo off
setlocal disableDelayedExpansion
:: Define LF to contain a newline character
set LF=^
:: Above 2 blank lines are critical - DO NOT REMOVE
for /f "eol= tokens=*" %%A in (%OUTPUT_FILE%) do (
set "ln=%%A"
setlocal enableDelayedExpansion
for %%L in ("!LF!") do for /f "eol= " %%W in ("!ln: =%%~L!") do echo(%%W
endlocal
)
Life is much simpler if you use a hybrid JScript/batch utility called REPL.BAT that performs regex search and replace on stdin and writes the result to stdout.
Assuming REPL.BAT is in your current folder, or better yet, somewhere within your PATH:
for /f "eol= " %%W in ('repl " " "\n" x ^<%OUTPUT_FILE%') do echo(%%W
Tokens control the elements split from delimiters that are available. So depending on what your overall goal is, it might be worth pursuing tokens to retrieve the desired variables. If you know how many elements will be on each line of your text file, such as a config file or key value pairs on each line, you could leverage 'tokens=' to get what you need.
for /f "tokens=1,2 delims= " %%a in (%OUTPUT_FILE) do echo %%a %%b
If the items per line is unknown, Endoro's word loop inside the line loop is definitely the way to go.

Resources