Split user input to array elements - batch-file

I need to write a batch script which pleases a user about filenames.
For example, a user launches the .bat file, selects option "delete files", the script waits for filenames and next the user writes it in one line like file1.txt file2.txt file3.txt file4.txt.
How can I grab the filenames like array elements to use it later?

setlocal EnableDelayedExpansion
:deleteFiles
set /P "filenames=Enter file names to delete: "
rem Grab filenames in an array
set n=0
for %%a in (%filenames%) do (
set /A n+=1
set "filename[!n!]=%%~a"
)
rem For example, to process the filenames:
for /L %%i in (1,1,%n%) do (
echo %%i- !filename[%%i]!
)
The list of filenames must be separated by spaces (or commas, or semicolons); if a name include spaces, it must be enclosed in quotes.
For further information about arrays, see this post.

version without delayed expansion:
#ECHO OFF &SETLOCAL
set "filepattern=*.txt"
for /f "tokens=1*delims=:" %%a in ('dir /b /a-d "%filepattern%"^|findstr /n $') do (
set "filename[%%a]=%%~b"
)
for /f "tokens=2delims==" %%a in ('set "filename"') do (
echo "%%~a"
)

Related

Iterate through all text files in a specific path and check if 2nd line has value

Test which among the text files in a specific path contains value on its 2nd line and save the filenames to another text file using windows batch file.
This is what Ive done so far.
#echo off
setlocal ENABLEDELAYEDEXPANSION
SET COUNT=0
FOR %%A IN (C:\Users\mark.jim.a.mercado\Desktop\*.txt) DO (
ECHO %%A
)
pause
It will list all the text files in the directory I used but when I tried to save the names of it to another text file (using the code below) only the name of the last text file will be saved.
#echo off
setlocal ENABLEDELAYEDEXPANSION
SET COUNT=0
FOR %%A IN (C:\Users\mark.jim.a.mercado\Desktop\*.txt) DO (
ECHO %%A #echo off
setlocal ENABLEDELAYEDEXPANSION
SET COUNT=0
FOR %%A IN (C:\Users\mark.jim.a.mercado\Desktop\*.txt) DO (
ECHO %%A >C:\Users\mark.jim.a.mercado\Desktop\names.txt
)
pause
I believe the easiest way to retrieve the second line of every text file is input redirection <:
> ".\logfile.log" (
for %%F in (".\*.txt") do (
set "FILE=%%~F"
setlocal EnableDelayedExpansion
< "!FILE!" (
for /L %%I in (1,1,2) do (
set "LINE=" & set /P LINE=""
)
echo 2nd line of "!FILE!": "!LINE!"
)
endlocal
)
)
Since you do not tell us what you want to do with the second lines or how you want to filter them, it is quite impossible to provide more help...
You don't say what value to look for. IMO the easiest is to use findstr to number the lines and another piped findstr to filter for :2: aka second line (if existent)
Suppose there are three file: one.txt two.txt, three.txt with analogous number of lines and content:
> type *.txt
one.txt
one
two.txt
one
two
three.txt
one
two
three
This batch:
#Echo off&SetLocal EnableExtensions EnableDelayedExpansion
For /f "tokens=1,2* delims=:" %%A in (
'findstr /N "^" *.txt ^|findstr ":2:"'
) Do Echo File:%%A Line:%%B Value:%%C
will outout:
File:two.txt Line:2 Value:two
File:three.txt Line:2 Value:two

Extract substring from filename and count

Example below - 5 files will be located in the same folder.
Sales-fid1000-f100.dat
Revenue-fid1000-f100.dat
Sales-fid2000-f200.dat
Revenue-fid2000-f200.dat
Income-fid2000-f200.dat
I need to read the filename and get the number after "fid", in this case 1000 and 2000 and count the number of files associated with each "fid".
So for fid1000, there are 2 files and for fid2000, there are 3 files.
I need to write the output into a .txt file as below with first field being the fid number and second field being the count.
1000|2
2000|3
How can I generate output text file with fid and count using a Windows batch file?
#echo off
setlocal EnableDelayedExpansion
rem Process all file names
for /F "tokens=2 delims=-" %%a in ('dir /B /A-D *.dat') do (
rem Get FID from second dash-delimited token; format: "xxx-fid####-xxx.dat"
set "fid=%%a"
rem Accumulate it to the corresponding element of "count" array
set /A "count[!fid:~3!]+=1"
)
rem Create the output
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do echo %%a^|%%b) > output.txt
For further details on array management in Batch files, see: Arrays, linked lists and other data structures in cmd.exe (batch) script
#ECHO OFF
SETLOCAL
:: remove variables starting $
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "outfile=%destdir%\outfile.txt"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*-fid*" '
) DO (
SET "filename=%%a"
CALL :process
)
(
FOR /F "tokens=1,2delims=$=" %%a In ('set $ 2^>Nul') DO ECHO(%%a^|%%b
)>"%outfile%"
GOTO :EOF
:process
SET "filename=%filename:*-fid=%"
FOR /f "delims=-" %%q IN ("%filename%") DO SET /a $%%q+=1
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
Produces the file defined as %outfile%
After clearing all the $ variables (for safety's sake), perform a directory listing without directorynames and in basic form of files in the source directory matching *-fid*.
For each name found, assign the name to filename and execute the :process routine, which first removes the characters up to and including -fid from filename then uses the delims=- option to assign the part originally between -fid and the following - to %%q.
setthe variable $%%q up by 1 (if $?? is undefined, assign 1)
Finally, when all the names have been processed, list the variables named $... using set which produces a report of the style
$1000=2
$2000=3
Using $ and = as delimiters puts token 1 (eg 2000) into %%a and token 2 (eg 3) into %%b. Write these to the output using echo, remembering to escape the pipe (|) with a caret (^) to suppress the interpretation as a redirector.
The parentheses around the for...$... ensures the output is directed to the destination file specified.
Extract the numbers into a temporary file, then count the occurrences of each number in that file.
#echo off
setlocal EnableDelayedExpansion
>temp.txt type nul
set "unique_num="
for /f "tokens=2 delims=-" %%a in ('dir /b *.dat') do (
set "fid=%%a"
set "num=!fid:~3!"
>>temp.txt echo !num!
echo " !unique_num! " | find " !num! " >nul
if !errorlevel! neq 0 set "unique_num=!unique_num! !num!"
)
for %%n in (%unique_num%) do (
for /f "delims=: tokens=2" %%c in ('find /c "%%n" temp.txt') do (
set "count=%%c"
echo %%n^|!count: =!
)
)
del /f /q temp.txt
Pipe the result into sort if you need the output sorted.

Batchfile: read last lines from logfiles and copy them to a new file

This is my first posting so if the format is not as it supposed to be please excuse me for this. (Suggestions for
improvement are welcome.)
I am trying to create a batchfile that will read last lines from logfiles and copy them to a new file.
Until now I have found here a way to read the last line.
Code would be something like:
for /f %%i in ('find /v /c "" ^< someFile.txt') do set /a lines=%%i
set /a startLine=%lines% - 1
more /e +%startLine% someFile.txt > lastLines.txt
The above code works for one file at a time. What I need is to read the last line from all files in a known list and add this line to a new .csv file.
I have been using the following code for getting the 4th entry in the logfiles but it returns every line of every logfile:
for /f %%x in (%list%) do for /f "delims=.txt, tokens=4" %%i in (%%x.txt) do echo %%x, %%i >> output.csv
What I would need is a sort of combination of both but I don't know how to combine them and make the complete last line be copied to the .csv file.
===
#Magoo:
Thanx for your reaction.
In every logfile can be 1 to >100 lines with comma separated information. Something like:
"LOGON,6-1-2015,12:43:39,USERNAME,HOSTNAME,,,,192.168.209.242,00:21:5A:2E:64:5E"
The last code with the 4th entry was used to get a list of all accounts that had logged in to the computers. This code gave me a very large list of all logon/logoff events on all computerlogs I checked in %list%.
In %list$ I had all the names of logfiles I wanted to be checked. This returned all lines.
For a new batchfile I need only the last logon/logoff entry and I want the whole last line.
So I have a .txt file with the hostnames of all computers I need to examine.
This .txt file will be read line by line via the variable %list%.
From every logfile I need only the last line copied to an output file.
===
I just tried the solution offered by JosefZ. Unfortunately this does not work for me yet. No lastlines are copied to the resultfile. In the code I removed the extra entry for possible lastlines for there are no empty lines in the logs, I also added an entry for the hostname I want to be available in the result. JosefZ had the filename there:
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set "host=%%~x"
for /F "tokens=*" %%G in ('type "%%~x"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
set "filename=.\logs\%filename:&=^&%.txt"
echo %host%,%lastline%>>output.csv
goto :eof
The resultfile shows only the hostnames. I'll puzzle some more with this but all tips are welcome!
===
Got it!!!
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set filename= :: *empty previous filename*
set lastline= :: *empty previous lastline*
set "host=%%~x"
set "filename=.\logs\%host%.txt" :: *creating the filename from path+hostname+extention*
for /F "tokens=*" %%G in ('type "%filename%"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
echo %host%,%lastline%>>output.csv
goto :eof
Your approach with line numbering could fail if a file has more trailing empty lines. Fortunately for /F loop ignores (does not iterate) empty lines; let's put to use this feature: in the script used next practices:
disabledelayedexpansion to allow ! in file names
set "list=_listing.txt" where the _listing.txt contains list of file names (full path and extension .txt including), one file name on one line: got by dir /b /s *.txt>_listing.txt
type nul>files\output.csv to empty the output file (optional)
set "lastline=!!!file empty!!!" to initialize variable %lastline%; could be set "lastline=" as well
call :lline to process variables %filename% and %lastline%
set "filename=%filename:&=^&%" to allow & in file names
The script is as follows:
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>files\output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set "filename=%%~x"
set "lastline=!!!file empty!!!"
rem the whole line
for /F "tokens=*" %%G in ('type "%%~x"') do set "lastline=%%G"
rem the fourth token only
rem for /F "tokens=4" %%G in ('type "%%~x"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
set "filename=%filename:&=^&%"
echo %filename% %lastline%
rem >>files\output.csv
goto :eof
Sample _listing.txt file:
d:\bat\files\1exclam!ation.txt
d:\bat\files\2exc!lam!ation.txt
d:\bat\files\11per%cent.txt
d:\bat\files\12per%cent%.txt
d:\bat\files\17per%Gcent.txt
d:\bat\files\18per%%Gcent.txt
d:\bat\files\21ampers&nd.txt
d:\bat\files\22ampers&&nd.txt
Output:
d:\bat>lastlines
d:\bat\files\1exclam!ation.txt 0 15.01.2015 1:52:28.48 -15072 20465
d:\bat\files\2exc!lam!ation.txt 6 15.01.2015 1:52:28.50 3250 16741
d:\bat\files\11per%cent.txt -8 15.01.2015 1:52:28.50 -3692 27910
d:\bat\files\12per%cent%.txt !!!file empty!!!
d:\bat\files\17per%Gcent.txt 0 15.01.2015 1:52:28.56 14508 12374
d:\bat\files\18per%%Gcent.txt 1 15.01.2015 1:52:28.56 30540 26959
d:\bat\files\21ampers&nd.txt 15.01.2015 1:22:50.18
d:\bat\files\22ampers&&nd.txt 15.01.2015 1:22:50.18
Honestly, all that ballast is for (possibly) trailing empty lines in files and for (possibly) ! and & in file names only; all could be done with
for /f %%x in (%list%) do for /f "skip=%startLine% tokens=4" %%i in (%%x) do echo %%x, %%i >> output.csv
You should use a simple FOR to iterate a list of values, not FOR /F.
Something like the following should work:
#echo off
setlocal enableDelayedExpansion
>>output.csv (
for %%F in (
"file1.log"
"file2.log"
"file3.log"
etc.
) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
more +!skip! %%F
)
)
The quotes around the file names are there in case you get a name with spaces.
You could use your LIST variable if it looks something like
set LIST="file1.log" "file2.log" "file3.log" etc.
#echo off
setlocal enableDelayedExpansion
set LIST="file1.log" "file2.log" "file3.log" etc.
>>output.csv (
for %%F in (%LIST%) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
more +!skip! %%F
)
)
If any of your file names contain the ! character, then you must toggle delayed expansion ON and OFF within your loop. Otherwise the delayed expansion will corrupt the names when %%F is expanded.
#echo off
setlocal disableDelayedExpansion
set LIST="file1.log" "file2.log" "file3.log" etc.
>>output.csv (
for %%F in (%LIST%) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
setlocal enableDelayedExpansion
more +!skip! %%F
endlocal
)
)

How to replace Strings in Windows Batch file

I would like to replace the following String in a file:
android:versionName="anyStringHere" >
*anyStringHere represents any possible string
With:
android:versionName="1.04.008" >
How would I do this in a clean, reusable way, and preserve the new lines, tabs, and indentation in the file?
Not even close to the fastest option, and not 100% bulletproof, but this is pure batch and will handle spacing and indentation while do the replacement.
#echo off
setlocal enableextensions disabledelayedexpansion
rem File to process
set "file=data.txt"
rem How to find lines
set "match=public static String CONST = \"abc\";"
rem String to replace and replacement
set "findStr=abc"
set "replaceStr=def"
rem temporary file to work with lines
set "tempFile=%temp%\repl.tmp"
rem All the output goes into the temporary file
(
rem Process input file extracting non matching lines
for /f tokens^=^1^*^ delims^=^:^ eol^= %%a in ('findstr /n /v /c:"%match%" ^< "%file%"') do (
set /a "n=1000000+%%a"
setlocal enabledelayedexpansion
< nul set /p "n=!n!"
endlocal
echo :%%b
)
rem Process input file extrancting matching lines and changing strings
for /f tokens^=^1^*^ delims^=^:^ eol^= %%a in ('findstr /n /c:"%match%" ^< "%file%"') do (
set /a "n=1000000+%%a"
set "data=%%b"
setlocal enabledelayedexpansion
set "data=!data:%findStr%=%replaceStr%!"
echo !n!:!data!
endlocal
)
)> "%tempFile%"
rem Sort the output file to get the final file
(for /f tokens^=^1^*^ delims^=^:^ eol^= %%a in ('sort "%tempFile%"') do (
if "%%b"=="" (
echo.
) else (
echo %%b
)
)) > "%file%.repl"
This is the simplest way to do this that I could come up with. It takes a String and searches for it in a file, then replaces the entire line that contains the string. It won't only replace parts of a line, which can be done with a bit more effort.
#echo off
:: file containing string to replace
set file=test.txt
:: string to replace in file
set searchString=line 4
:: string to write to file
set repString=line 4 edited
setLocal enableDelayedExpansion
set count=0
if not exist %file% echo cannot find file - %file% & goto :EOF
:: Search for string - and get it's line number
for /F "delims=:" %%a in ('findstr /N /I /C:"%searchString%" "%file%"') do set searchLine=%%a
if not defined searchLine echo cannot find string - %searchString% - in file - %file% & goto :EOF
:: Read file into variables - by line number
for /F "delims=~!" %%b in ('type %file%') do (
set /a count=!count!+1
set line!count!=%%b
)
:: Edit the one line
set line%searchLine%=%repString%
:: Empty file and write new contents
del %file%
for /L %%c in (1,1,!count!) do echo !line%%c!>>%file%
pause
You can change the echo on the last for loop to output to a different file, maybe %file%.new or something, and then remove the del command.
This is a robust solution that retains all formatting. It uses a helper batch file called repl.bat - download from: https://www.dropbox.com/s/qidqwztmetbvklt/repl.bat
Place repl.bat in the same folder as the batch file or in a folder that is on the path.
type "file.txt" | repl "(public static String CONST = \q).*(\q.*)" "$1def$2" x >"newfile.txt"
I found that using sed was the cleanest solution
http://gnuwin32.sourceforge.net/packages/sed.htm
sed "s/android:versionName=\".*\" >/android:versionName=\"%NEW_VERSION%\" >/g" %ORIG_FILE_NAME% > %TEMP_FILE_NAME%
#move /Y %TEMP_FILE_NAME% %ORIG_FILE_NAME% >nul

Batch filter duplicate lines and write to a new file (semi-finished)

I have successfully made a script that filters out duplicate lines in a file and saves the results to a variable semi-colon separated (sort of an "array"). I could not find any real good solution to it.
#echo off
setlocal enabledelayedexpansion
rem test.txt contains:
rem 2007-01-01
rem 2007-01-01
rem 2007-01-01
rem 2008-12-12
rem 2007-01-01
rem 2009-06-06
rem ... and so on
set file=test.txt
for /f "Tokens=* Delims=" %%i in ('type %file%') do (
set read=%%i
set read-array=!read-array!;!read!
)
rem removes first trailing ";"
set read-array=!read-array:*;=!
echo !read-array!
for /f "Tokens=* Delims=" %%i in ('type %file%') do (
set dupe=0
rem searches array for the current read line (%%i) and if it does exist, it deletes ALL occurences of it
echo !read-array! | find /i "%%i" >nul && set dupe=1
if ["!dupe!"] EQU ["1"] (
set read-array=!read-array:%%i;=!
set read-array=!read-array:;%%i=!
)
rem searches array for the current read line (%%i) and if it does not exist, it adds it once
echo !read-array! | find /i "%%i" >nul || set read-array=!read-array!;%%i
)
rem results: no duplicates
echo !read-array!
Contents of !read-array! is 2008-12-12;2007-01-01;2009-06-06
I now want to take out each item in the array and write them to a new file, with line breaks after each item. Example:
2008-12-12
2007-01-01
2009-06-06
So this is what I've come up with so far.
The problem I'm having is that the second for-loop doesn't accept the !loop! variable as a token definition when being nested. It does however accept %loop% if it's not nested.
The reason I'm doing it this way is that the !read-array! may have a unknown number of items, therefore I count them as well.
Any ideas?
rem count items in array
set c=0
for %%i in (!read-array!) do set /a c+=1
echo %c% items in array
for /l %%j in (1,1,%c%) do (
set loop=%%j
for /f "Tokens=!loop! Delims=;" %%i in ("!read-array!") do (
echo %%i
rem echo %%i>>%file%
)
)
exit /b
At end of your first section, when contents of !read-array! is 2008-12-12;2007-01-01;2009-06-06, you may directly separate the elements of your "list" with a simple for because the standard separators in Batch files may be, besides spaces, comma, semicolon or equal signs:
for %%i in (%read-array%) do echo %%i
However, may I suggest you a simpler method?
Why not define a "real" array with the subscript value of the lines? This way, several repeated lines will store its value in the same array element. At end, just display the values of the resulting elements:
#echo off
set file=test.txt
for /F "Delims=" %%i in (%file%) do (
set read-array[%%i]=%%i
)
rem del %file%
for /F "Tokens=2 Delims==" %%i in ('set read-array[') do (
echo %%i
rem echo %%i>>%file%
)
EDIT
Alternative solution
There is another method that assemble a list of values separated by semicolon as you proposed. In this case each value is first removed from previous list content and immediately inserted again, so at end of the cycle each value is present just once.
#echo off
setlocal EnableDelayedExpansion
set file=test.txt
for /F "Delims=" %%i in (%file%) do (
set read-array=!read-array:;%%i=!;%%i
)
rem del %file%
for %%i in (%read-array%) do (
echo %%i
rem echo %%i>> %file%
)

Resources