I have a string "xx-yy-zz"
I need to get the part after the last minus character.
Usually, I do it that way
FOR /F "tokens=1-3 delims=-" %%i IN ("xx-yy-zz") DO SET USER_COUNTRY=%%k
echo Country: %USER_COUNTRY%
But now I don't have the string with a fixed length (there could be 2/3/4 delimiters)
How can I specify that I want the last one? So I have the code that would work for "xx-yy-zz" and also for "xx-yy-zz-aa"
Could someone help with this, please?
use a plain for (without /f):
set string=xxx-yyy-zzz
for %%a in (%string:-= %) do set last=%%a
echo %last%
(%string:-= % replaces every - with a space, so the for can split the string into tokens and assign every one of them to (the same) variable, resulting in the variable holding the last token.
The option string tokens=1-4 would let the for /F loop assign the last string portion aa to variable reference %%l if it is present; %%l would be empty otherwise. So you could do an if query to check for emptyness and use %%k instead in case, and so on, like this:
for /F "tokens=1-4 delims=-" %%i in ("xx-yy-zz-aa") do (
set "USER_COUNTRY=%%l"
if not defined USER_COUNTRY set "USER_COUNTRY=%%k"
if not defined USER_COUNTRY set "USER_COUNTRY=%%j"
)
echo/%USER_COUNTRY%
An alternative method would be this:
set "STRING=xx-yy-zz-aa"
set "DUMMY=%STRING:-=" & set "USER_COUNTRY=%"
echo/%USER_COUNTRY%
This approach might fail if the input string contains " characters.
One solution could be to convert the string, replacing dashes with backslashes so it "seems" a path that can be handled with a for command
#echo off
setlocal enableextensions disabledelayedexpansion
set "string=xxx-yyy-zzz"
for /f "delims=" %%a in ("::%string:-=\%") do set "last=%%~nxa"
echo %last%
An additional example:
#Echo Off
Set/P "InString=Enter your delimited string: "
Echo=
If "%InString%"=="" GoTo :EOF
Set/P "Delimiter=Enter the delimiter: "
Echo=
If "%Delimiter%"=="" GoTo :EOF
Set "Delimiter=%Delimiter:~,1%"
:Loop
If Defined InString (
For /F "EOL=%Delimiter% Tokens=1* Delims=%Delimiter%" %%A In ("%InString%"
) Do (Set "LastToken=%%A" & Set "InString=%%B") & GoTo Loop
) Else Set LastToken
Timeout -1
Exit/B
This version will still have issues with some characters
Related
So what I'm trying to do is create a find for multiple people where it in the text file it will say names and numbers like
Example of text file:
Beth
1234567891
Jay
2134456544
This is the best way I can explain what I'm trying to do:
#echo off
set "file=Test1.txt"
setlocal EnableDelayedExpansion
<"!file!" (
for /f %%i in ('type "!file!" ^| find /c /v ""') do set /a n=%%i && for /l %%j in (1 1 %%i) do (
set /p "line_%%j="
)
)
set /a Name=1
set /a Number=2
Echo Line_%Name%> %Name%.txt (Im trying to get this to say line_2 to say 1st line in the text file)
Echo Line_%Number%> %Name%.txt (Im trying to get this to say line_2 to say 2nd line in the text file)
:Start
set /a Name=%Name%+2 (These are meant to take off after 1 so lines 3,5,7,9 so on)
set /a Number=%Number%+2 (These are meant to take off after 2 so lines 4,6,8,10 so on)
Echo Line_%Name%
Echo Line_%Number%
GOTO :Start
so the outcome would be
In Beth.txt:
Beth
1234567891
So every name will be a file name and the first line in a file. I will change it later so I can do a addition in each text file.
Name: Beth
Number: 1234567891
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
SET "filename1=%sourcedir%\q65417881.txt"
rem make sure arrays are empty
For %%b IN (name number) DO FOR /F "delims==" %%a In ('set %%b[ 2^>Nul') DO SET "%%a="
rem Initialise counter and entry array
SET /a count=0
SET "number[0]=dummy"
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
IF DEFINED number[!count!] (SET /a count+=1&SET "name[!count!]=%%a") ELSE (SET "number[!count!]=%%a")
)
rem clear out dummy entry
SET "number[0]=dummy"
FOR /L %%c IN (1,1,%count%) DO (
rem replace spaces with dashes
SET "name[%%c]=!name[%%c]: =-!"
rem report to console rem report to console
ECHO Name: !name[%%c]! Number: !number[%%c]!
rem generate name.txt file
(
ECHO !name[%%c]!
ECHO !number[%%c]!
)>"%destdir%\!name[%%c]!.txt"
)
GOTO :EOF
You would need to change the values assigned to sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
I used a file named q65417881.txt containing your data for my testing.
The line data read from the file is assigned to %%a is assigned to and number[!count!] alternately. The data is retained in these arrays for use by further processing.
[Edited to include conversion of spaces within names to dashes]
If I understand correctly, you want to precede every second line with Number: + SPACE and every other line with Name: + SPACE. For this you do not need to store each line in a variable first, you can use a single for /F loop lo read the file line by line and process every line individually. There are two possibilities:
Temporarily precede every line with a line number plus : using findstr /N:
#echo off
rem // Loop through lines and precede each with line number plus `:`:
for /F "tokens=1* delims=:" %%K in ('findstr /N "^" "Test1.txt"') do (
rem // Calculate remainder of division by two:
set /A "MOD=%%K%%2" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !MOD! neq 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This will fail when a line begins with the a :.
Check whether numeric representation of current line string is greater than 0:
#echo off
rem // Loop through (non-empty) lines:
for /F "usebackq delims=" %%L in ("Test1.txt") do (
rem // Determine numeric representation of current line string:
set /A "NUM=%%L" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !NUM! equ 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This fails when a name begins with numerals and/or when a numeric line is 0.
And just for the sake of posting something different:
#SetLocal EnableExtensions DisableDelayedExpansion & (Set LF=^
% 0x0A %
) & For /F %%G In ('Copy /Z "%~f0" NUL') Do #Set "CR=%%G"
#For /F "Tokens=1,2* Delims=:" %%G In ('%__AppDir__%cmd.exe /D/V/C ^
"%__AppDir__%findstr.exe /NR "^[a-Z]*!CR!!LF![0123456789]" "Test1?.txt" 2>NUL"
') Do #(SetLocal EnableDelayedExpansion
(Set /P "=Name: %%I!CR!!LF!Number: " 0<NUL & Set "_="
For /F Delims^=^ EOL^= %%J In ('%__AppDir__%more.com +%%H "%%G"') Do #(
If Not Defined _ Set "_=_" & Echo %%J)) 1>"%%I.txt" & EndLocal)
This file should be run with the Test1.txt file in the current working directory. It is important that along side Test1.txt, there are no other .txt files with the same basename followed by one other character, (for example Test1a.txt or Test12.txt). Should you wish to change your filename, just remember that you must suffix its basename in the above code with a ? character, (e.g. MyTextFile.log ⇒ MyTextFile?.log).
I had the rare opportunity to verify that this script worked against the following example Test1.txt file:
Beth
1234567891
Jay
2134456544
Bob
2137856514
Jimmy
4574459540
Mary
3734756547
Gemma
6938456114
Albert
0134056504
Hi I managed to get the code below to create a list of numbers and place a comma at the end of each number created However, it has proven to be quite the challenge to get them on the same line separated by a ,[space]
#ECHO OFF
setlocal EnableDelayedExpansion
set _Output=%UserProfile%\Desktop\NumGen.txt
::only change these three lines
set "start=1" ::starts from this number
set "amount=10" ::amount of files created
set "length=5" ::length of fileNames
set "join_with=, " ::what to join each number with
set /a "last=%start%+%amount%"
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "folderName=!folderName:~-%length%!%join_with%"
>>"%_Output%" ECHO.!folderName!
)
pause
so my output at the moment is
00001,
00002,
00003,
00004,
00005,
00006,
00007,
00008,
00009,
00010,
00011,
However I would like it to be
00001, 00002, 00003, 00004, 00005, 00006, 00007, 00008, 00009, 00010, 00011
I have windows 10 64bit. Any help will be appreciated
echo is not able to do that. But there is a workaround, (ab)using the set command:
for /l %%i in (1,1,5) do (
<nul set /p "=%%i, "
)
echo(
After a long break I decided to give this another bash and presto. Success :-)
::CallScript
#ECHO OFF
CALL :ScriptA
CALL :ScriptB
CALL :ScriptC
pause
goto :eof
:ScriptA
#ECHO OFF
setlocal EnableDelayedExpansion
set _Output=%UserProfile%\Desktop\NumGen.txt
::only change these three lines
set "start=1"
set "amount=10"
set "length=5"
set "join_with=, "
set /a "last=%start%+%amount%"
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "folderName=!folderName:~-%length%!%join_with%"
>>"%_Output%" ECHO.!folderName!
)
goto :eof
:ScriptB
#ECHO OFF
setlocal enableextensions enabledelayedexpansion
set "var="
for /f "usebackq delims=" %%a in ("%UserProfile%\Desktop\NumGen.txt") do set "var=!var!%%a"
echo %var%> "%UserProfile%\Desktop\NumList.txt"
goto :eof
:ScriptC
#ECHO OFF
del "%UserProfile%\Desktop\NumGen.txt"
goto :eof
This code determines the starting number, the amount of numbers, the length of the numbers and joins them with , "
Then ScriptB concatenates each number together with the , " and saves to NumList.txt
Lastly the script deletes the NumGen.txt file.
Stephan has one solution with SET /P. The other option is to build the entire string within an environment variable, and then write after the loop ends. This is significantly faster, but it will fail if the final string length exceeds ~8191 bytes.
set "str="
for /l %%i in (%start%,1,%last%) do (
set "folderName=0000000000%%i"
set "str=!str!!folderName:~-%length%!%join_with%"
)
>>"%_Output%" ECHO.!str!
You could remove the unwanted trailing , if you want:
>>"%_Output%" ECHO.!str:~0,-2!
Hi I do need to extract the last part of a string after the last dot
Example:
1.2.37 ==> I need the 37
1.2.567 ==> I need the 567
as you can see the number of characters after the dot is not fixed so expressions like
base=%fullver:~0,-2%
Can't be used. How can I achieve this?
#echo off
setlocal enableextensions disabledelayedexpansion
set "fullver=1.2.456"
for %%a in ("%fullver:.=\%") do set "base=%%~na"
echo %base%
The trick is to replace the dots with backslashes, process the string as a path and retrieve the name of the last element in it.
Alternatively, if all the elements need to be retrieved, instead of a for, a for /f is used to tokenize the variable using the dots as separators
#echo off
setlocal enableextensions disabledelayedexpansion
set "fullver=1.2.456"
for /f "tokens=1-3 delims=." %%a in ("%fullver%") do (
set "major=%%a"
set "minor=%%b"
set "build=%%c"
)
echo [%major%] [%minor%] [%build%]
I found the following question which actually tokenizes the string.
How to split a string in a Windows batch file?
May be you can try using this to delimit it with "." and take the last value stored in the string variable. Not sure if there is a simple way, but this works.
Here is an edited Version to fit your Needs:
#echo off
setlocal ENABLEDELAYEDEXPANSION
REM Set a string with an arbitrary number of substrings separated by semi colons
set teststring=1.2.5.234
for /f "tokens=1 delims=." %%a IN ("!teststring!") DO set firststring=%%a
echo !firststring!
REM Do something with each substring
:stringLOOP
REM Stop when the string is empty
if "!teststring!" EQU "" goto END
for /f "delims=." %%a in ("!teststring!") do set substring=%%a
REM Now strip off the leading substring
:striploop
set stripchar=!teststring:~0,1!
set teststring=!teststring:~1!
if "!teststring!" EQU "" goto stringloop
if "!stripchar!" NEQ "." goto striploop
goto stringloop
:END
echo !substring!
endlocal
I prefer MC ND's answer if you are looking for only the last node, or if you know how many nodes there are.
Here is a method to capture all nodes if the total number of nodes is unknown:
#echo off
setlocal enableDelayedExpansion
set "fullver=1.2.456"
:: Parse each node and store in an "array"
set cnt=0
for %%A in (%fullver:.= %) do (
set /a cnt+=1
set "node.!cnt!=%%A"
)
:: Show the results
for /l %%N in (1 1 %cnt%) do echo node.%%N = !node.%%N!
Another solution! This one gets the first and last parts of the string:
#echo off
setlocal
set "testString=1.2.5.234"
set "first="
for %%a in ("%testString:.=" "%") do (
if not defined first set "first=%%~a"
set "last=%%~a"
)
echo First: %first%
echo Last: %last%
As a bonus, this method correctly process special Batch characters that may appear in the string, excepting wild-cards.
You can use the below command to achieve what you want.
base=%fullver:~~4,3%
4 implies 4th digit i.e., 5 and 3 implies 3 digits from 4.
The output will be
567
In my .csv file,
abc,10/24/2013,ABC
cede,5/1/2013,ABCk
cdeh,7/27/2014,ABCf
cdedsf,1/27/2014,gfABC
.
.
.(1xx more lines with similar text)
I would like to find the latest date in the middle field (e.g. 7/27/2014 in above case) and save to a variable named as "latest_date".
However, I do not know how to read the specific field from a .csv file and find the latest date with such format(M/D/Y).
Can anyone teach me?
This simpler method should run faster:
#echo off
setlocal EnableDelayedExpansion
set latest_num=0
for /F "tokens=2-4 delims=,/" %%a in (theFile.csv) do (
set /A "new_num=((%%c*100)+%%a)*100+%%b"
if !new_num! gtr !latest_num! (
set latest_num=!new_num!
set latest_date=%%a/%%b/%%c
)
)
echo Latest date: %latest_date%
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "tokens=1-3 delims=/ " %%a in (
' cmd /q /v:on /c "for /f "tokens^=2-4 delims^=^,/" %%a in (data.csv) do (set /a "x^=%%c*10000+%%a*100+%%b" >nul & echo(^!x:~0,4^!/^!x:~4,2^!/^!x:~-2^!)" ^| sort /r '
) do set "last_date=%%b/%%c/%%a" & goto done
:done
echo %last_date%
How does it work?
The input file is readed via a for /f loop (the inner one). Each record is tokenized, using the commas and slashes as delimiters. This leaves the tokens 2 to 4 as the elements of the date. This elements are normalized (month and days have one or two digits, years are at the end) with some arithmetics to get a yyyy/mm/dd date, and the resulting dates are echoed. This list of dates is sorted in inversed order (so the greatest date is in the first record).
As the process in defined as a pipe (each process inside a pipe run in a separate cmd instance), and as the left part of the pipe requires delayed expansion enabled, the for /f that reads the file is executed inside its own instance of cmd with the adecuated configuration: echo off (/q) and delayed expansion active (/v:on).
The sorted list, will be readed with another for /f loop (the outer one), that will tokenize the retrieved data, separating again the year, month and day, so the final variable have the required format (mm/dd/yyyy). As the greatest date is in the first record, once it is retrieved and the value assigned to the variable, a goto jump to a label is executed to skip the rest of the records.
To see it clear, this is the same code, more readable, but separated in steps and using a temporary file
#echo off
setlocal enableextensions enabledelayedexpansion
set "tempFile=%temp%\%~nx0.%random%.tmp"
( for /f "tokens=2-4 delims=,/" %%a in (data.csv) do (
set /a "x=%%c*10000+%%a*100+%%b"
echo(!x:~0,4!/!x:~4,2!/!x:~-2!
)
)> "%tempFile%"
for /f "tokens=1-3 delims=/ " %%a in (
' type "%tempFile%" ^| sort /r '
) do set "last_date=%%b/%%c/%%a" & goto done
:done
echo %last_date%
del /f /q "%tempFile%" >nul 2>nul
endlocal
The inner loop in original code is now the first loop. File is readed by the for, date elements extracted (see tokens and delims), date normalized (set /a arithmetics) and the list saved to a temporary file
The outter loop in original code is the second one here. The file is readed with a type command, the data piped to sort /r and the resulting lines are tokenized by the for command to reformat the date.
Edited to adapt to comments
Aacini is right, his code is faster, but given that i just started this way, .... Anyway, the changes in this code can be translated to his solution
This should handle differences in date fields (aditional spaces and aditional initial 0) and missing fields.
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "tokens=2 delims=:" %%a in (
'cmd /q /v:on /c "for /f "delims^=" %%z in (data.csv) do for /f "tokens^=1-3 delims^=^," %%w in (" %%~z ") do if not "%%~y" equ "" for /f "tokens^=1-3 delims^=/ " %%a in ("%%~x") do (set /a "x^=%%c*10000 + 100%%a %% 100*100 + 100%%b %% 100" >nul & echo(^!x^!:%%~x)" ^| sort /r '
) do set "latest_date=%%a" & goto done
:done
echo %latest_date%
The inner for loops :
for %%z will read lines from file
for %%w will tokenize the readed line with an aditional space at the start and end of the line to prevent problems with adjacent delimiters removal.
for %%a handles the date normalization and outputs the calculated value used for sort and the readed date.
The outer for %%a loop will split the retrieved record to separate the calculated value from the readed date.
In the CMD the variable are displayes in alphabetical order and from the smaller to the bigger value.
So we transform all your dates in variables like this : set #142707=7/27/2014
Then looping against a set # we take the last value who is the latest date.
#echo off
setlocal enabledelayedexpansion
for /f "tokens=2 delims=," %%a in (data.csv) do call:checkDate %%a
for /f "tokens=2 delims==" %%a in ('set #') do set $LatestDate=%%a
echo The Latest Date : [!$LatestDate!]
exit/b
:CheckDate
set $out=
set "$Date=%1"
set "$Date=%$Date:/= %
for %%b in (%$Date%) do (
set $val=0%%b
set $out=!$val:~-2!!$out!)
set #!$out!=%1
This is extension to another question (Loop through CSV file with batch - Space issue)
I have csv file content like this
name,sex,age,description,date
venu,m,16,test mesg,2012-05-01
test,f,22,"He is good guy
and
brilliant",2012-05-01
I am looping this file using this command.
For /F "usebackq tokens=1-3 delims=" %%x in (test.csv) Do (
But since there is line break in second row, I am getting 3 records even though there are two records in the file.
How to fix this? Thanks in advance.
The main problem seems to be to count the quotes in a line.
If the count of quotes is odd then you need to append the next line and count again the quotes.
Counting of characters in a string is a bit tricky, if you won't iterate through all charachters.
I used here the delayed reduction technic, each quote will be effectivly replaced by a +1 and all other characters are removed.
To begin and terminate the line in a proper way there is always one extra +1 at the beginning, which will be compensated by a -1 in front.
The main trick is to replace the complete text from one quote to the next with exactly one +1 by replacing each quote with !!#:#=.
This works as !#:#=...<some text>...! will always be expanded to +1, as the content of the variable # is +1 and so the search pattern # can't be found.
The other replacements are only necessary to avoid problems with exclamation marks and carets int the text.
:::::::::::::::::::::::::::::::::::::::::::
:CountQuotes <stringVar> <result>
setlocal EnableDelayedExpansion
set "line=!%~1!"
set "#=+1"
rem DelayedExpansion: double all quotes
set "line=!line:"=""!"
rem DelayedExpansion: remove all carets ^
set "line=!line:^=!"
rem PercentExpansion: Remove all !
set "line=%line:!=%"
rem PercentExpansion: Replace double quotes to !!#:#=
set "line=-1^!#:#=%line:""=^!^!#:#=%"
for /F "delims=" %%X in ("!line!") do (
set /a count=%%X!
)
(
endlocal
set %~2=%count%
exit /b
)
And the logic for appending lines and inserting linefeeds
#echo off
setlocal DisableDelayedExpansion
set "lastLine="
set LF=^
rem Two empty lines
for /F "delims=" %%A in (test.csv) do (
set "line=%%A"
setlocal EnableDelayedExpansion
set "line=!line:\=\x!"
if defined lastLine (
set "line=!lastLine!\n!line!"
)
call :CountQuotes line quoteCnt
set /a rest=quoteCnt %% 2
if !rest! == 0 (
for %%L in ("!LF!") DO set "line=!line:\n=%%~L!"
set "line=!line:\\=\!"
echo Complete Row: !Line!
echo(
set "lastLine="
) ELSE (
set "lastLine=!line!"
)
for /F "delims=" %%X in (""!lastLine!"") DO (
endlocal
set "lastLine=%%~X"
)
)
exit /b
:::::::::::::::::::::::::::::::::::::::::::
:CountQuotes <stringVar> <result>
setlocal EnableDelayedExpansion
set "line=!%~1!"
set "#=+1"
rem DelayedExpansion: double all quotes
set "line=!line:"=""!"
rem DelayedExpansion: remove all carets ^
set "line=!line:^=!"
rem PercentExpansion: Remove all !
set "line=%line:!=%"
rem PercentExpansion: Replace double quotes to !!#:#=
set "line=-1^!#:#=%line:""=^!^!#:#=%"
for /F "delims=" %%X in ("!line!") do (
set /a count=%%X!
)
(
endlocal
set %~2=%count%
exit /b
)
The Batch file below do what you want:
#echo Off
setlocal EnableDelayedExpansion
call :processFile < test.csv
goto :EOF
:processFile
set line=
set /P line=
if not defined line exit /b
set "line=!line:,,=,#,!"
for %%a in (name sex age description mydate) do set %%a=
for %%a in (!line!) do (
if not defined name (
set "name=%%a"
) else if not defined sex (
set "sex=%%a"
) else if not defined age (
set "age=%%a"
) else if not defined description (
set "description=%%a"
) else if not defined mydate (
set "mydate=%%a"
)
)
:checkDate
if defined mydate goto show
set /P line=
for /F "tokens=1* delims=," %%a in ("!line!") do (
set "description=!description! %%a"
set "mydate=%%b"
)
goto checkDate
:show
for %%a in (name sex age description mydate) do set /P "=%%a=!%%a!, " < NUL
echo/
goto processFile
I added the requirements you requested in your previous topic, that is, the sex may be empty (and is changed by # character as I explained in my answer to that topic), and the name may include commas. I tested the program with this data file:
name,sex,age,description,date
venu,m,16,"test mesg",2012-05-01
test,,22,"He is good guy
and
brilliant",2012-05-01
"venu,gopal",m,16,"Another
multi-line
description",2012-05-02
And get these results:
name=name, sex=sex, age=age, description=description, mydate=date,
name=venu, sex=m, age=16, description="test mesg", mydate=2012-05-01,
name=test, sex=#, age=22, description="He is good guy and brilliant", mydate=2012-05-01,
name="venu,gopal", sex=m, age=16, description="Another multi-line description", mydate=2012-05-02,
Note that any field that contain commas or spaces must be enclosed in quotes.