I have a version number 17.06.01.01 and I would like to know how many entries there are split by a period.
My last piece of code was;
setlocal ENABLEDELAYEDEXPANSION
for /F "tokens=1-10 delims=." %%a in ("17.09.01.04.03") do (
set /a "iCount+=1"
echo %%a, !iCount!
)
endlocal
I've tried a host of 'For' commands but seem to be getting further away each time.
replace every dot with a space (as a delimiter). Then count number of tokens:
#echo off
set "string=17.09.01.04.03"
set count=0
for %%a in (%string:.= %) do set /a count+=1
echo %count%
(May give false results, if there are other spaces, commas or tabs in the string, but should work nice for your example of version strings containing only numbers and dots)
#treintje:
echo off
set "string=1>7.0 9.0&1.04.0!3"
set count=0
:again
set "oldstring=%string%"
set "string=%string:*.=%"
set /a count+=1
if not "%string%" == "%oldstring%" goto :again
echo %count%
For the sample string you provided, Stephan's approach is perfectly sufficient.
However, if the string contains token separators (white-spaces, ,, ;, =), quotation marks ("), wild-cards (*, ?) or other special characters (^, &, (, ), >, <, |) it might probably fail. You could of course replace most of such characters by others in advance, and use delayed expansion rather than immediate one, but this still does not resolve all issues (for instance, you cannot replace all = or * characters due to the sub-string replacement syntax).
The following approach however can deal with every arbitrary combination of all such characters. Basically it replaces every period (.) with a new-line (line-feed) character and lets find /C /V "" count the total number of lines.
#echo off
set "string=17.09.01.04.03"
setlocal EnableDelayedExpansion
if defined string (set ^"strtmp=!string:.=^
%= empty string =%
!^") else set "strtmp="
for /F %%C in ('cmd /V /C echo(^^!strtmp^^!^| find /C /V ""') do set /A "count=%%C-1"
echo "!string!" contains %count% periods.
endlocal
The periods become replaced by line-feeds in advance, using delayed expansion in order not to fail if any special characters occur. The for /F loop executes the command line cmd /V /C echo(^^!string^^!| find /C /V "" and captures its output in a variable, reduced by one as find /C /V "" actually returns the number of lines, which are separated by one less line-feeds (hence periods), originally. The double-escaped exclamation marks ^^! are needed in order to ensure that the variable strtmp is actually expanded within the explicitly invoked inner-most cmd instance, because otherwise, the contained multi-line string is not correctly transported into the pipe (|).
A different approach comparing strLen before and after replacing the dots with nothing.
#Echo off&SetLocal EnableExtensions EnableDelayedExpansion
set "string=17.09.01.04.03"
set "str2=%string:.=%"
call :strlen string ret
call :strlen str2 ret2
Set /A "Dots=ret-ret2"
echo Number of dots in %string% is %Dots%
goto :Eof
:strLen string len
:$source http://www.dostips.com/?t=Function.strLen
(SETLOCAL ENABLEDELAYEDEXPANSION
set "str=A!%~1!"
set "len=0"
for /L %%A in (12,-1,0) do (set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A")
)
ENDLOCAL&IF "%~2" NEQ "" SET /a %~2=%len%
EXIT /b
Test with strings from comments:
Pass: set "string=Tim says:"There is a cat and a dog""
Fail: set "string=1>7.0% 9.0&1.04.%0!3"
Pass: set "string=Tim says:"There is a cat. And a dog""
Related
I need to escape "!" (and other special chars) in a for-loop where delayed variable expansion is enabled
I've tried manually escaping the loop-varible with string substitution ^^! in the loop-variable, %%a but with no luck at all. Is it already too late after the for has read them? If so, how the heck can I even accomplish this?
Below is a short function. The only relevant part here is the for-loop and the echo statement. That is printing out whole lines from a file every X'th line, and those lines are file-paths. They (sometimes) contain characters like "!" and other troublesome special characters. I just want echo here to pass it without interpreting it at all - but instead it ends up deleting my "!" chars.
For my use they need to be exactly correct or they are useless as they must correlate to actual files later on in what I use them for.
setlocal EnableDelayedExpansion
:SplitList
if [%3] == [] goto :SplitListUsage
set inputfile=%~1
set outputfile=%~2
set splitnumber=%~3
set skipLines=0
set skipLines=%~4
if %skipLines% GTR 0 (
set skip=skip=%skipLines%
) else (
set skip=
)
#echo off > %outputfile%
set lineNumber=0
for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
set /a modulo="!lineNumber! %% !splitnumber!"
if "!modulo!" equ "0" (
echo %%a >> !outputfile!
)
set /a lineNumber+=1
)
exit /B 0
Quick solution:
if !modulo! equ 0 (
setlocal DisableDelayedExpansion
(echo %%a)>> "%outputfile%"
endlocal
)
Better solution:
Based on your code sample, there is no need to have delayed expansion enabled for the entire code,in fact you should keep it disabled to not mess with the file names or input strings which may contain !, and enabled it when necessary:
setlocal DisableDelayedExpansion
REM Rest of the code...
for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
set /a "modulo=lineNumber %% splitnumber"
setlocal EnableDelayedExpansion
for %%m in (!modulo!) do (
endlocal
REM %%m is now have the value of modulo
if %%m equ 0 (
(echo %%a)>> "%outputfile%"
)
)
set /a lineNumber+=1
)
Side notes:
There are some other issues with your code which as you might have noticed, some of them are corrected in the above solutions. But to not distract you from main issue you had, I covered them here separately.
There is also room for improving the performance of the code when writing to the outputfile.
Here is the re-written code which covers the rest:
#echo off
setlocal DisableDelayedExpansion
:SplitList
if "%~3"=="" goto :SplitListUsage
set "inputfile=%~1"
set "outputfile=%~2"
set "splitnumber=%~3"
set "skipLines=0"
set /a "skipLines=%~4 + 0" 2>nul
if %skipLines% GTR 0 (
set "skip=skip=%skipLines%"
) else (
set "skip="
)
set "lineNumber=0"
(
for /f "usebackq tokens=* %skip% delims= " %%a in ("%inputfile%") do (
set /a "modulo=lineNumber %% splitnumber"
setlocal EnableDelayedExpansion
for %%m in (!modulo!) do (
endlocal
REM %%m is now have the value of modulo
if %%m equ 0 echo(%%a
)
set /a lineNumber+=1
)
)>"%outputfile%"
You didn't protect the variable assignments with double qoutes " e.g. set inputfile=%~1. If the now naked batch parameter %~1 contains spaces or special characters like & your batch files fails, either fatally with a syntax error or at execution time with incorrect data. The recommended syntax is to use set "var=value" which does not assign the quotes to the variable value but provides protection against special characters. It also protects the assignment against the accidental trailing spaces.
The %inputfile% may contain special characters or spaces, so it should be protected by double quoting it when using in the FOR /F's IN clause. When double quoting the file name in FOR /F the usebackq parameter must also be used.
With SET /A there is no need to expand the variable values: set /a modulo="!lineNumber! %% !splitnumber!". The variable names can be used directly, and it will work correctly with or without delayed expansion.
Using (echo %%a) >> "%outputfile%" inside a FOR loop introduces a severe performance penalty specially with a large number of iterations, because at each iteration the output file will be opened, written to and then closed. To improve the performance The whole FOR loop can redirected once. Any new data will be appended to the already opened file.
The odd looking echo( is to protect against the empty variable values, or when the variable value is /?. Using echo %%a may print the `ECHO is on/off' message if the variable is empty or may print the echo usage help.
In the main solutions, The reason I've used (echo %%a)>> "%outputfile%" instead of echo %%a >> "%outputfile%" is to prevent outputting the extra space between %%a and >>. Now you know the reason for using echo(, it is easy to understand the safer alternative: (echo(%%a)>> "%outputfile%"
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"
I have a test.csv file which has data something like this.
"a","usa","24-Nov-2011","100.98","Extra1","Extra2"
"B","zim","23-Nov-2011","123","Extra22"
"C","can","23-Nov-2011","123"
I want to fetch the maximum number of columns in this file (i,e 6 in this case) and then store this in a variable.
Like
Variable=6
I'm aware that this can be acheived in scripting as I have some basic knowledge about scripting. But I have zero programming knowledge in .BAT.
Please help me regarding this
#echo off
setlocal EnableDelayedExpansion
set variable=0
for /F "delims=" %%a in (test.csv) do (
set count=0
for %%b in (%%a) do set /A count=+1
if !count! gtr !variable! set variable=!count!
)
echo Variable=%variable%
This solution use the fact that strings enclosed in quotes in a FOR set are treated as individual items, so count they is very easy. For this reason, this method fail if there are wild-card characters ("*" or "?") in the lines.
If comma is your delimiter, maybe you could try to count the number of commas in every line and remember the biggest one (and increase it by one). (I assume, that commas are not in the strings)
count instances of a character in file per line : https://stackoverflow.com/a/7988661/2282321
edit:
I came up with following solution, please try it (assuming input file name with data to be 'a.txt'):
#echo off
set biggest_len=0
for /f "tokens=*" %%a in (a.txt) do call :processline %%a
echo %biggest_len%
goto :eof
:processline
set len=0
for %%b in (%*) do set /A len+=1
if %len% gtr %biggest_len% set /A biggest_len=len
goto :eof
:eof
sources I used to create solution:
Batch Script - Count instances of a character in a file
If greater than batch files
batch script - read line by line
#ECHO OFF
SETLOCAL
SET /a maxcols=0
SET /a mincols=999999
FOR /f "delims=" %%a IN (q28540735.txt) DO SET /a total=0&CALL :count %%a
ECHO maximum columns=%maxcols%
ECHO minimum columns=%mincols%
GOTO :EOF
:count
SET "$1=%~1"
IF DEFINED $1 SET /a total+=1&shift&GOTO count
IF %total% gtr %maxcols% SET /a maxcols=total
IF %total% lss %mincols% SET /a mincols=total
GOTO :EOF
I used a file named q28540735.txt containing your data for my testing.
Each line is presented to the subroutine count as comma-separated parameters, so if you set the total found to zero before each call, then it's a simple matter of counting the parameters. If the parameter presented is enclosed in quotes, then any commas and spaces within the quotes are processed as being part of the token, not separators in their own right.
Hello I'm trying to complete a batchfile im working on.
I have a library of motivational quotes textfiles, and i want my batchfile to pick one random quote textfile and display it in the command window. with the command type i guess.
my path to the folder with the quotes is:
C:\Users\niv\Documents\test\scripts\quotes
inside there is :
Quote 1.txt
Quote 2.txt
Quote 3.txt.
i did the command: `type "C:\Users\niv\Documents\test\scripts\quotes\quote1.txt"
that only displayed the selected quote1.txt ofcourse.
If possible i want to get the selected Quote-text file to be a variable so i can use it for different tasks in my batchfile like:
after receiving the selected quote do this command with a program that makes it possible for my computer to read anything after "". like this
%speech% "%quotes%"
the %speech% lets my computer speak whats inside the quotation marks.
i hope you can understand what im trying to accomplish here if not just ask :)
I am assuming each quote is contained in a single line in each file. I recommend placing all the quotes in a single file named quotes.txt, with one quote per line. This should make it easier to maintain the quotes. Then all you need is code that will count the number of lines in the file, and then randomly pick a line between 1 and count.
The FIND /C command can count the number of lines in a file.
The %RANDOM% dynamic variable gives a pseudo random number between 0 and 32767, and the modulo SET /A operator can be used to convert the value into a number between 0 and count-1.
The FOR /F command reads lines from a file, and you can use the SKIP option to skip a random number of lines.
quotes.txt
quote 1
quote 2
quote 3
quote 4
randomQuote.bat
#echo off
setlocal
:: Count the number of quotes and define a random number to skip
for /f %%N in ('find /c /v "" ^<quotes.txt') do set /a skip=%random% %% %%N
if %skip% gtr 0 (set skip=skip=%skip%) else set "skip="
:: Read a random quote into a variable
for /f "%skip% delims=" %%A in (quotes.txt) do (
set "quote=%%A"
goto :break
)
:break
:: Echo the quote (or do whatever you need)
echo %quote%
You can read a text file into a variable like this:
setlocal EnableDelayedExpansion
set filename=Quote 1.txt
for /f "tokens=*" %%a in ('type "%filename%"') do (
set quote=!quote!%%a^
)
echo !quote!
endlocal
The trailing ^ in set quote=!quote!%%a^ followed by an empty line ends each appended line with a newline. If you don't need the newlines, you could replace
for /f "tokens=*" %%a in ('type "%filename%"') do (
set quote=!quote!%%a^
)
with
for /f "tokens=*" %%a in ('type "%filename%"') do (
set quote=!quote! %%a
)
to get the whole quote in a single line.
In any case you need delayed expansion for this (!quote! instead of %quote%).
My answer focuses on counting the number of text files in the path you provided, then choosing a random quote number and typing the quote. both of the previous answers can be used as reference for inserting the quote into a variable (which my code does not provide):
#Echo off
set path=C:\Users\niv\Documents\test\scripts\quotes
cd /D %path%
setLocal EnableDelayedExpansion
set count=0
for %%a in (*.txt) do (
set /a count=!count!+1
)
echo Total number of quote files: %count%
set /a num=(%count% * %random%) / 32768 + 1
echo Quote #: %num%
type "%path%\quote %num%.txt"
endlocal
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!