Batch removing X number of lines from file from Nth line - batch-file

I am trying to remove a tag from a set of html files and I can't seem to find the right commands to do the job.
I am able to find the line number of the tag; the tag is a total of 10 lines that need to be deleted. Once I find this line, how do I got and delete said line & next 10 lines?
Here's what I have (a first for loop to collect files, a second for loop that collects all line numbers.) thank you.
::start by creating a list of html files
set i=0
for /r %%G in (*.html) do (
set /A i+=1
set array[!i!]=%%G
)
set n=%i%
::second nested loop collects all lines for bottom secion to be deleted.
set j = 0
for /L %%i in (1,1,%n%) do (
for /f "delims=" %%a in ('findstr /n /c:"u-backlink u-clearfix u-grey 80" !array[%%i]!') do (
set var=%%a
set /A j+=1
set array[!j!]=!var:~0,3!
)
)
set m=%j%

#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory & destination directory are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
for /r "%sourcedir%" %%G in (*.html) do (
SET "linecount="
FOR /f "delims=:" %%e IN ('findstr /n /c:"u-backlink u-clearfix u-grey 80" "%%G"') DO SET /a linecount=%%e
IF DEFINED linecount (
FOR /f "usebackqdelims=" %%b IN ("%%G") DO (
SET /a linecount-=1
IF !linecount! gtr 0 ECHO %%b
IF !linecount! lss -10 ECHO %%b
)
)>"%destdir%\tempfile"& MOVE /y "%destdir%\tempfile" "%%G" >nul
)
GOTO :EOF
Start a recursive directory list of the target files from sourcedir.
Initialise linecount to nothing and use findstr to locate the target string. %%e will be set to the number of the line found, set this into linecount.
If the string was found, linecount will now contain a value, so read each line and count down. If linecount is between 0 and -10, we don't echo the line to the output file.
I originally put all the generated files in the one directory. Yours to change at will.
Revised to generate a temporary file (quite where would be irrelevant) and then move that file over the original.
Always verify against a test directory before applying to real data.

Related

Batch File - When copying files sometimes it shows The system cannot find the path specified for all the file or for majority of file

i am making a file selector which would randomly copy files from one folder to another code works quite fine but sometimes it shows The system cannot find the path specified for all or majority of files i don't know what went wrong can please someone help
my code
#echo off
setlocal enabledelayedexpansion
set num=0
cls
set /p input= enter the number of files you want:
set /p address= enter the address of your files:
md SelectedFiles
pushd "%address%" || goto :EOF
set /a num=%num%+1
for /f "tokens=1,* delims=[]" %%i in ('dir /b /s /a-d ^| findstr /RV "[.]jpg [.]png" ^| find /v /n ""') do (
set "file%%i=%%~j"
set "cnt=%%i"
)
for /l %%c in (1,1,%input%) do (
set /a rand=!random! %% !cnt!
for %%r in (!rand!) do copy "!file%%r!" "%address%\SelectedFiles" | clip
)
echo your files have been copied
pause
popd
Try, as a replacement for your for /l loop
for /l %%c in (1,1,%input%) do (
set /a rand=1 + !random! %% !cnt!
for %%r in (!rand!) do (
copy "!file%%r!" "%address%\SelectedFiles" | clip
for %%s in (!cnt!) do set "file%%r=!file%%s!"
set /a cnt-=1
)
)
Your filenames are currently being assigned to file1..file!cnt!.
You are then generating rand as 0..cnt-1, so there is a probability that you will choose file0 which does not exist and no possibility of choosing file!cnt!
There is also a possibility of re-choosing a file.
You should make sure that input is not greater than cnt.
My suggested code simply makes the range 1..cnt, then when a file has been processed, moves the very last filename (file!cnt!) over the chosen name and reduces cnt since there is one fewer filename in the list.
NOTE: Since the |clip is piping from a copy statement for one file only, it should only ever generate 1 file(s) copied. on the clipboard

Delete a certain line and lines before and after the matched line

In a large .ahk file, i need to locate the text 'no label' in a line, delete 2 lines before that, delete 23 lines after that, and delete the line with 'no label' itself (26 lines total). There can be multiple cases of this in the file, and in all cases it needs to remove these 26 lines.
I have no knowledge of SED, AWK and so on, and I need this to run on a windows machine. Is it doable with a .bat or some other windows application that I'll be able to run?
#ECHO OFF
SETLOCAL
SET "$="
SET "tempfile=%temp%\some-tempfile-name.txt"
ECHO.>"%tempfile%"
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r ".*" q34397055.txt') DO (
ECHO %%b|FIND "no label" >NUL
IF NOT ERRORLEVEL 1 CALL :saveline# %%a
)
IF NOT DEFINED $ COPY /b q34397055.txt u:\newfile.txt 2>NUL >nul&GOTO done
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r ".*" q34397055.txt^|findstr /v /b /g:"%tempfile%"') DO (
ECHO(%%b
)
)>u:\newfile.txt
:done
DEL "%tempfile%"
GOTO :EOF
:saveline#
:: calculate START line number to delete
SET /a $=%1 - 2
:: number of lines to delete
SETLOCAL enabledelayedexpansion
FOR /l %%d IN (1,1,26) DO (
>>"%tempfile%" ECHO(!$!:
SET /a $+=1
)
GOTO :EOF
I used a file named q34397055.txt containing some test data for my testing.
Produces u:\newfile.txt
Essentially, read the entire file, numbering each line in the format line#:line content
Use the tokens facility to extract the line number part, and if the line-content part contains the target string (it's not clear whether OP wants a line containing "no label" or whether a line exactly matching "no label" is required) then call :saveline# passing the line number.
In :saveline#, calculate the starting line of the block-to-be-deleted and then write the line numbers to be deleted to a file in the format (eg) 6:..32:.
Then perform the same numbering trick, but this time filter the output for lines that do not contain (/v) at the beginning of the line (/b) any string in the tempfile of line-numbers-to-be-deleted.
Output any line-content parts that pass through the filter.
[Edit : to fix empty-output-if-no-target-found problem
Insert set "$=" to ensure variable $is deleted at the start.
Insert if not defined $... line to detect whether $ has been established (ie. :saveline# has been called at least once). Simply mechanically copy the source file to the destination if :saveline# has not been called, and then skip to the label done to delete the tempfile.
Insert the label done
Suggestion : establish variables to contain the source and destination filenames so that only one change need be made to change the filenames, not two or three.
]
Okay, challenge accepted. This should do what you want in 100% batch-file form. I threw in a few comments but if you have questions feel free to ask.
First it scans the file for any instance of the searchPhrase (currently "no label"). If found it saves that line number as refLine*. It then sets the upper bounds as refLine + 23 and the lower bounds as refLine - 2, as per your criteria. If the current line number falls outside those bounds it will write the line to a new, temporary, file. Once complete, it backs up the original file then deletes it and renames the temp file.
#echo off
setlocal enabledelayedexpansion
set "sourceFile=c:\temp\batchtest\myfile.txt"
set "tempFile=c:\temp\batchtest\tempfile.txt"
set "searchPhrase=no label"
set /a lineNum=0
REM check file for search phrase, store line as refLine
FOR /F "delims=" %%i IN (%sourceFile%) DO (
set /a lineNum+=1
echo !lineNum! = "%%i"
if "%%i" == "%searchPhrase%" (
echo Found "%searchPhrase%" on line !lineNum!
set /a refLine=!lineNum!
)
)
REM make backup
copy "%sourceFile%" "%sourceFile%-%DATE:/=-% %TIME::=-%.bak"
echo. 2>%tempFile%
REM Rewrite file
set /a lineNum=0
set /a lowEnd=%refLine%-2
echo "Set low end to %lowEnd%"
set /a highEnd=%refLine%+23
echo "Set high end to %highEnd%"
FOR /F "delims=" %%i IN (%sourceFile%) DO (
set /a lineNum+=1
if !lineNum! GTR %lowEnd% (
if !lineNum! LSS %highEnd% (
echo "Skipping line #!lineNum!"
)
)
if !lineNum! LSS %lowEnd% (
echo "Writing Line !lineNum! %%i to temp file..."
echo %%i >> %tempFile%
)
if !lineNum! GTR %highEnd% (
echo "Writing Line !lineNum! %%i to temp file..."
echo %%i >> %tempFile%
)
)
REM get target filename only
for %%F in ("%sourceFile%") do set fname=%%~nxF
REM del original file and rename tempfile
echo "Deleting original file..."
echo Y | del "%sourceFile%"
echo "Renaming %tempFile% to %fname%"
ren "%tempFile%" "%fname%"
*Note that it will currently only find one instance of "no label". If you think there are multiple instances, just run the bat file again. If a person wanted to, they could find multiple instances and store the line numbers to a 3rd, temporary, text file then use that to determine more complicated bounds for filtering. Alternatively, you could put a loop around the entire thing and exit the loop when it doesn't find an instance of the searchPhrase.
#echo off
setlocal EnableDelayedExpansion
rem Get number of lines of sections to preserve in "copy.tmp" file
set last=0
(for /F "delims=:" %%a in ('findstr /N /C:"no label" input.ahk') do (
set /A copy=%%a-3-last, last=copy+26
echo !copy!
)) > copy.tmp
rem Read from input file
< input.ahk (
rem Process all "copy-lines, skip-26" sections
for /F %%n in (copy.tmp) do (
for /L %%i in (1,1,%%n) do (
set "line="
set /P "line="
echo(!line!
)
for /L %%i in (1,1,26) do set /P "line="
)
rem Copy the rest of lines after last section
findstr "^"
) > output.ahk
del copy.tmp
move /Y output.ahk input.ahk

Fastest way to check for the highest filename in sequence with Batch file?

I'm attempting to use a batch file to keep all files in a particular folder numbered sequentially (0,1,2,...). To do this I first require knowing what the highest number in this sequence is so I can append the non-sequentially named files to the end of the list. For example, I may have the following files:
0.png
1.jpg
2.jpg
3.png
file_51.jpg
pictureabc.png
This would return a result of 3 as that is the last filename in sequence. I would then be able to find the remaining files (not sure the best way to do this either) and number them starting at 4.
The only solution I have come up with so far is using an infinite goto loop that stops when no filename including that number exists.
set count=0
:loop
if exist %count%.* set /a count+=1 & goto loop
This solution is highly inefficient however and takes almost a minute for 100 thousand images. I'm looking for a solution that will be able to achieve this in under 10 seconds if possible.
#echo off
setlocal enableextensions disabledelayedexpansion
setlocal enabledelayedexpansion
set "nextFile=0" & for /f "delims=" %%a in ('
dir /a-d /b ^| findstr /b /r /x
/c:"[1-9][0-9]*\.[^.]*"
/c:"[1-9][0-9]*"
/c:"0"
') do if %%~na geq !nextFile! set /a "nextFile=%%~na+1"
endlocal & set "nextFile=%nextFile%"
echo Next file will be: %nextFile%
This just uses a dir command to retrieve the list of all files, filtered with findstr to only get those with numeric name. The rest is just compare the file name against the previous max value.
Edited - This should work fast enough
#if (#This === #IsBatch)
#echo off
rem Start of batch zone ----------------------------------------------
setlocal enableextensions disabledelayedexpansion
for /f %%a in ('
dir /a-d /b /o-d
^| findstr /r /x /c:"[0-9][0-9]*\.[^.]*" /c:"[0-9][0-9]*"
^| cscript //nologo //e:jscript "%~f0"
') do set "nextFile=%%a"
echo next file will be %nextFile%
exit /b
#end
// Start of JScript zone ---------------------------------------------
var inputStream = WScript.StdIn;
var max = 0, value ;
while(!WScript.StdIn.AtEndOfStream) {
value = parseInt( inputStream.ReadLine().split('.')[0], 10 );
if ( value >= max ) max = value + 1;
}
WScript.StdOut.WriteLine( max );
This is an hybrid batch/jscript file. Saved as .cmd file, it uses a dir command to retrieve the list of files, a findstr to leave only the files with numbered names and then the native JScript engine to extract the numbers and search for the greatest value.
I think this is the fastest way:
#echo off
setlocal EnableDelayedExpansion
set lastNum=0
for /L %%a in (1,1,9) do (
for %%b in (%%a*.*) do (
if %%~Nb gtr !lastNum! (
set lastNum=%%~Nb
)
)
)
echo Last numbered file: %lastNum%
EDIT: New method added
Previous method depends on the number of existent files, so it is not as fast as I expected...
The new method below takes almost the same time no matter the number of files:
#echo off
setlocal EnableDelayedExpansion
set "names="
for /L %%i in (1,1,9) do set "names=!names! %%i*.*"
dir /B /A-D /O-D %names% 2> NUL | ( set /P "lastNum=" & set lastNum ) > lastNum.txt
for /F "tokens=2 delims==." %%a in (lastNum.txt) do set "lastNum=%%a"
del lastNum.txt
echo Last numbered file: %lastNum%
Previous method may fail if more than one of the last files have the same created time, but this point may be fixed if needed.
sorry - but the last method by Aacini works really bad. Actually it does not find the highest number, but the file created (not modified) last. It would be great to know how to fix this!
Stopping at hole can be fixed with iterations (yeah 2 lines of code for each subsequent hole).
here is how to iterate...
set count=1234 ::starting number
:loop
if exist %count%.img set /a count+=1 & goto loop
set /a count+=1
if exist %count%.img set /a count+=1 & goto loop
set /a count+=1
if exist %count%.img set /a count+=1 & goto loop
set /a count=%count%-2
need to reduce the count at the end again. This could be made much longer of course but then it will slow down if you iterate 1000 times...

Edit specific lines of a text file by looping through an input file of items

I need to run a process on many items and the process takes a configuration file as input. The configuration file needs to be changed for each item at three different lines (1,2,17) with different extensions. I can read the configuration file into batch script as a text file but it should be saved with *.cfg configuration so that it can be used. I have put together the following pseudo code with pointers where I would need help for making it functioning batch script.
set inputline=1
set outputline=2
set xmlline=17
set infile=config.txt
set items=list.txt
for /f "delims=" %%a in (%items%) do (
echo.%%a
set curr=1
for /f "delims=" %%b in (%infile%) do (
if !curr!==%inputline% [replace this line in infile with "a".ext1]
if !curr!==%outputline% [replace this line in infile with "a".ext2]
if !curr!==%xmlline% [replace this line in infile with "a".ext3]
)
set /b "curr = curr + 1"
[save "infile".cfg]
)
"call" proccess.exe -config.cfg
)
And the configuration file:
sample.264
sample.yuv
test_rec.yuv
1
1
0
2
500000
104000
73000
leakybucketparam.cfg
1
2
2
0
1
sample.xml
3
Batch has no built in way of replacing a single line in a text file - however you can read the entire contents of the file, change the lines, then rewrite the file.
set file=config.txt
setLocal enableDelayedExpansion
set count=0
for /F "delims=~!" %%b in ('type %file%') do (
set /a count=!count!+1
set line!count!=%%b
)
set line1="a".ext1
set line2="a".ext2
set line17="a".ext3
del %file%
for /L %%c in (1,1,!count!) do echo !line%%c!>>%file%
pause
I slightly modified what I did here. I took out the check to see if the file exists, as you should be able to assume that it will.
Thanks unclemeat, it helped me to nail-down the following working code for me:
set file=config.cfg
set vdos=test.txt
setlocal enabledelayedexpansion
for /f "delims=" %%a in (%vdos%) do (
echo %%a
set count=0
for /F "delims=~!" %%b in ('type %file%') do (
set /a count=!count!+1
set line!count!=%%b
)
set line1=%%a%.264
set line2=%%a%.yuv
set line17=%%a%.xml
del %file%
for /L %%c in (1,1,!count!) do echo !line%%c!>>%file%
ldecod config.cfg
)

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