2

I've been trying to figure out how to replace an entire line in a text file that contains a certain string using a Batch Script. I've found this solution provided by another user on Stack Overflow, which does the job, however, it just stops iterating through the text file at some random point and in turn, the output file is left with a bunch of lines untransferred from the original file. I've looked character by character, and line by line of the script to figure out what each part exactly does, and can not seem to spot what is causing this bug.

The code provided, thanks to Ryan Bemrose on this question

copy nul output.txt
for /f "tokens=1* delims=:" %%a in ('findstr /n "^" file.txt') do call :do_line "%%b"
goto :eof

:do_line
set line=%1
if {%line:String =%}=={%line%} (
  echo.%~1 >> output.txt
  goto :eof
)
echo string >> output.txt

The lines it is stopping at always either contain < or > or both and lines with | will either cause it to stop, or sometimes it will delete the line and continue.

starball
  • 20,030
  • 7
  • 43
  • 238
nullptr
  • 41
  • 5
  • 2
    Although I would do some things differently, there is no obvious show-stopper in your code. Do you mind showing us one or two of the failing lines? You have no `echo off` - does the output look like you would expect? – Stephan Nov 25 '21 at 22:03
  • 2
    Change `set line=%1` to `set "line=%~1"` and the `if` condition to `if "%line:String =%"=="%line%"`; the `echo.%~1` (or better written as `echo(%~1`) is still problematic though, unless you switch to using [delayed variable expansion](https://ss64.com/nt/delayedexpansion.html) (which would also make other things more robust), given that you assign `%~1` to a normal environment variable before… – aschipfl Nov 25 '21 at 23:40
  • @aschipfl I gave those a try however now the line that is supposed to be replaced is being deleted, and the script stops at the first empty line (empty lines should be preserved). When you say use delayed variable expansion, does that mean simply adding `SETLOCAL EnableDelayedExpansion`? – nullptr Nov 26 '21 at 01:15
  • 1
    @Stephan Yes, sorry my script has `@echo off` in the beginning. Through more investigation, I've realized that lines only cause failure if they contain "<" or ">" or both (without quotations), or if they contain "|" in some cases the line with "|" is deleted and the script continues, but sometimes with "|" it will also just stop. But those two characters are definitely the ones causing the issues. I will update that in my question as well. – nullptr Nov 26 '21 at 01:15
  • No, it is not just adding `setlocal`, but that is why I referred to an external page that describes it in detail… – aschipfl Nov 26 '21 at 09:12

1 Answers1

4

To do this robustly, Delayed expansion is necessary to prevent "poison" characters such as < > & | etc being interpreted as command tokens.

Note however that delayed expansion should not be enabled until after the variable containing the line value is defined so as to preserve any ! characters that may be present.

The following will robustly handle all Ascii printable characters *1, and preserve empty lines present in the source file:

@Echo off

Set "InFile=%~dp0input.txt"
Set "OutFile=%~dp0output.txt"
Set "Search=String "
Set "Replace="

>"%OutFile%" (
 for /F "delims=" %%G in ('%SystemRoot%\System32\findstr.exe /N "^" "%InFile%"') do (
  Set "line=%%G"
  call :SearchReplace
 )
)
Type "%OutFile%" | More
goto :eof

:SearchReplace
Setlocal EnableDelayedExpansion
 Set "Line=!Line:*:=!"
 If not defined Line (
  (Echo()
  Endlocal & goto :eof
 )
 (Echo(!Line:%Search%=%Replace%!)
Endlocal & goto :eof

*1 Note - Due to how substring modification operates, You cannot replace Search strings that:

  • contain the = Operator
  • Begin with ~
T3RR0R
  • 2,747
  • 3
  • 10
  • 25
  • That worked brilliantly, and thank you for that explanation. The issue, however, is that I actually do not know the exact string that needs to be replaced, but rather some other strings in that same line. Think: Replacing a date in a sentence, where I know the sentence but not the specific date that's already there. Could you tell me how I would alter this code such that once it finds the search string, instead of only replacing the string itself, it replaces the entire line that the search string is contained in with the replace string? – nullptr Nov 26 '21 at 05:07
  • 1
    The task you describe is different from your current question. I suggest you ask a new question and povide a detailed explanation of what lines, in your real world scenario, should be considered valid matches and what subbstring pattern needs to be matched and replaced (A task powershell would be better suited to as it has the capacity to match regex patterns and replace them.) – T3RR0R Nov 26 '21 at 05:33
  • Thank you, I will give Powershell a go instead. – nullptr Nov 26 '21 at 16:27