tyee
tyee

Reputation: 331

Batch - Replace line in text file using for loop including blank lines

I found an answer by Jeb that copies a file to another file including blank lines by adding a number: to the start of every line -

(
set taglinelinks=one two three
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ overview.md"`) do (
    set "var=%%a"
    if "%var:~0,2%" == "5:" (echo %taglinelinks%)
    SETLOCAL EnableDelayedExpansion
    set "var=!var:*:=!"
    echo(!var!
)
) > new.txt

I added the variable at the top for testing and added the "if" line. It works fine without the "if" line, but I can't see anything wrong with that line. I'm checking to see if the number of the line is 5 and if it is echo the variable to the file then continue on. I should have an else command in my if statement but right now I can't determine what to put there to prevent the rest of the code to skip line 5's original content.

Upvotes: 0

Views: 399

Answers (2)

aschipfl
aschipfl

Reputation: 34909

In this answer I stick to our original code and focus on the flaws. Basically I like this approach (with the errors corrected, of course), because this can even properly handle lines that begin with a colon :, and it does not have any problems with ! appearing in the text file since delayed expansion is toggled.

Anyway, at first I want to show the adapted and working code:

@echo off
setlocal DisableDelayedExpansion
set "taglinelinks=one two three"
> "new.txt" (
    for /F "delims=" %%a in ('findstr /N "^" "overview.md"') do (
        set "var=%%a"
        setlocal EnableDelayedExpansion
        if "!var:~,2!" == "5:" (
            echo(!taglinelinks!
        ) else (
            echo(!var:*:=!
        )
        endlocal
    )
)
endlocal

And this is what I corrected:

  • the if clause needs to be in the block with delayed expansion enabled, therefore I moved it one line downwards and I changed % to !;
  • I implemented the else clause so that it contains the remaining code portion in the for /F loop body
  • I added a missing endlocal, so you cannot run into setlocal nesting limitations, and the set "var=%%a" command line actually appears in a code section with delayed expansion disabled, which is necessary to not cause trouble with ! appearing in the file;

And here are some minor and cosmetic changes:

  • I added @echo off on top in order to avoid command echoes to be written into new.txt;
  • I moved the initial setlocal command up to the top to disable delayed expansion at first, so ! could even be safely used in the redirected file name and in the first set command line (since we might not know whether delayed expansion is disabled or enabled earlier);
  • I used the quoted set syntax also for the assignment of variable taglinelinks to make it safe; then I moved this line outside of the redirection block just to become more obvious;
  • although it does not disturb, the usebackq option is not necessary, so I removed it and used single-quotes '' (but is just a matter of taste here);
  • the quoted findstr command line appears a bit odd, particularly the escaped caret ^^, hence I changed quotation so that the search string as well as the file path are quoted, so no such escaping is necessary any more, and the file name may even contain spaces or other special characters;
  • the line to echo the value of taglinelinks is not in the block where delayed expansion is enabled; this allows the variable to contain even special characters;
  • the second assignment set "var=!var:*:=!" is not really necessary, so I removed it and echoed the truncated string immediately;
  • I adapted the code indentation, also for the if/else block, to improve readability;
  • I moved the redirection > "new.txt" to the front of the outer parenthesised block to make it more visible (but this is again just a matter of taste here), and I put double-quotes around the file name, so it might even contain spaces or other special characters;
  • I added another explicit endlocal command that belongs to the initial setlocal;

There is still one thing I do not like in the script, namely the inflexible way to identify the number of the current line, because it currently only works for numbers less than 10 without adapting the code a bit.

Here is a way to flexibly specify the number of the line to replace:

@echo off
setlocal DisableDelayedExpansion
set "taglinelinks=one two three"
set "linenumber=5"
> "new.txt" (
    for /F "delims=" %%a in ('findstr /N "^" "overview.md"') do (
        set "var=%%a"
        setlocal EnableDelayedExpansion
        set /A "num=var"
        if !num! equ !linenumber! (
            echo(!taglinelinks!
        ) else (
            echo(!var:*:=!
        )
        endlocal
    )
)
endlocal

This is what I did:

  • firstly I defined a variable (constant) linenumber with the line number 5; regard that linenumber must not hold leading zeros for the number not to be interpreted as octal integer later (by if);
  • in the loop I placed set /A "num=var", which makes use of implicit variable expansion of set /A, meaning that the line string is scanned from left to right up to the first non-numeric character1, then this string is converted to an integer and finally assigned to num; this implies that the file contains less than 231 lines;
  • the if clause is changed to if !num! equ !linenumber!, which performs a numeric comparison due to equ since both expressions are integers; so this no longer depends on the number of digits of the line number; here I could also have used %linenumber% since the variable is not changed in the code block, but I decided to use !linenumber! to cover the case of wrong initialisation of that variable (particularly when it is blank or contains special characters due to typos);

1) Actually leading SPACEs and TABs are ignored. Next a single + or - sign may occur. Then everything up to the next non-numeric figure is used to convert the string into a signed 32-bit integer. If the result cannot be represented as such it becomes coerced.

Upvotes: 2

Compo
Compo

Reputation: 38623

You could try it like this:

@Echo Off
Set "taglinelinks=one two three"
(   For /F "Tokens=1*Delims=:" %%A in ('Findstr /N "^" "overview.md"')Do (
        If %%A NEq 5 (Echo=%%B)Else Echo %taglinelinks%
    )
)>"new.txt"

Upvotes: 1

Related Questions