L.Morsing
L.Morsing

Reputation: 11

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?

Upvotes: 0

Views: 1411

Answers (3)

Magoo
Magoo

Reputation: 80033

@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. ]

Upvotes: 1

Aacini
Aacini

Reputation: 67216

@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

Upvotes: 0

user736893
user736893

Reputation:

Okay, challenge accepted. This should do what you want in 100% 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.

Upvotes: 0

Related Questions