Replace a line in a text file if it matches a pattern - windows

I'm trying to loop a specific file and see if any line contains a certain word or text and I want to replace the whole line. I am not sure how I am suppose to do it.
Right now this what I have:
#echo off
setlocal enabledelayedexpansion
FOR /F "usebackq delims=" %%G IN ("C:\folder\myfile.properties") DO (
Set Line="transaction.sic.lettreEnvironnementBackend"
IF %%G == %Line% (
replace that line with new text
)
)
pause
endlocal

#echo off
setlocal enabledelayedexpansion
Set "Line=transaction.sic.lettreEnvironnementBackend"
(
FOR /F "usebackq delims=" %%G IN ("C:\folder\myfile.properties") DO (
IF "%%G"=="%Line%" (
echo replacement line
) else (echo %%G)
)
)>replacement_filename
pause
endlocal
Note that the replacement filename should not be the source filename. Once tested, move the replacement file to the original filename if required.
Note also that the instruction will exactly match the contents of line - there's no allowance for any other characters on the line.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.

Also easy in PowerShell.
Get-Content .\OutputFile.txt |
% { if ($_ -eq 'Warning message') { 'NEW WARNING' } else { $_ } }

Related

Batch to remove range of lines with start and stop delimiters from text file

I know batch isn't the best vehicle for this but my requirements dictate that I keep it.
I have text that looks like the following (it also has blank lines):
Line AAA text
Line BBB text
! ***### START
Body text here
! ***### END
Line XXX
Line YYY
!Comment Line etc
I want to remove the ! ***### START and END lines and everything in between and then save over the original file.
I found and modified the code below but it strips out my blank lines and the ! characters.
#echo off
setlocal enabledelayedexpansion
set "sourceFile=c:\temp\startfile.txt"
set "tempFile=c:\temp\tempfile.txt"
set "StartPhrase=! ***### START"
set "EndPhrase=! ***### END"
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" == "%StartPhrase%" (
echo Found "%StartPhrase%" on line !lineNum!
set /a StartrefLine=!lineNum!
)
if "%%i" == "%EndPhrase%" (
echo Found "%EndPhrase%" on line !lineNum!
set /a EndrefLine=!lineNum!
)
)
REM make backup
copy "%sourceFile%" "%sourceFile%-%DATE:/=-% %TIME::=-%.txt"
echo. 2>%tempFile%
REM Rewrite file
set /a lineNum=0
set /a lowEnd=%StartrefLine%
echo "Set low end to %lowEnd%"
set /a highEnd=%EndrefLine%
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%"
A possible and quite simple way is to let the particular line markers toggle a flag that indicates whether or not the currently iterated line is to be output:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=C:\TEMP\startfile.txt"
set "_TMPF=%TEMP%\%~n0_%RANDOM%.tmp"
set "_START=! ***### START"
set "_END=! ***### END"
rem // Initialise flag:
set "FLAG=#"
rem // Write to temporary file:
> "%_TMPF%" (
rem /* Loop through lines of input file; `findstr` precedes each line with
rem line number plus `:`, so they do not appear empty to `for /F`: */
for /F "delims=" %%L in ('findstr /N "^" "%_FILE%"') do (
rem // Store current line string (with line number prefix) to variable:
set "LINE=%%L"
rem // Toggle delayed expansion to avoid loss of `!`:
setlocal EnableDelayedExpansion
rem // Remove line number prefix to retrieve original line string:
set "LINE=!LINE:*:=!"
rem // Check contents of current line:
if "!LINE!"=="!_START!" (
endlocal & set "FLAG="
) else if "!LINE!"=="!_END!" (
endlocal & set "FLAG=#"
) else (
rem // Check state of flag for outputting:
if defined FLAG echo(!LINE!
endlocal
)
)
) && (
rem // Move temporary file onto target file:
move /Y "%_TMPF%" "%_FILE%"
)
endlocal
exit /B
This task could be done with worst script interpreter available for this task with following code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFile=C:\Temp\startfile.txt"
if not exist "%SourceFile%" goto :EOF
set "TempFile=%SourceFile%.tmp"
set "PrintLines=1"
(for /F "delims=" %%I in ('%SystemRoot%\System32\findstr.exe /N /R "^" "%SourceFile%"') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
if defined PrintLines (
if not "!Line: ***### START=!" == "!Line!" (
endlocal
set "PrintLines="
) else (
echo(!Line:*:=!
endlocal
)
) else (
if not "!Line: ***### END=!" == "!Line!" (
endlocal
set "PrintLines=1"
) else endlocal
)
))>"%TempFile%"
move /Y "%TempFile%" "%SourceFile%"
if exist "%TempFile%" del "%TempFile%"
endlocal
for /F results in ignoring all empty lines. For that reason command findstr is used to output all lines with line number and colon at beginning in a separate command process started with %ComSpec% /c in background. So there is no empty line anymore in captured output of findstr.
Each captured line starting with a digit in range 1 to 9 is assigned completely to the loop variable I because of using option delims= to define an empty list of string delimiters to disable the default line splitting behavior on normal spaces and horizontal tabs.
The line with line number and colon at beginning is assigned to the environment variable Line while delayed environment variable expansion is disabled. This is important as otherwise the Windows command processor cmd.exe processing the batch file would parse the command line set "Line=%%I" after having replaced %%I by current line with enabled delayed expansion and would interpret each ! as beginning/end of a delayed expanded environment variable.
Then the delayed expansion is enabled for further processing the line depending on printing lines currently enabled as by default until a line is found containing the string  ***### START. Next the environment variable PrintLines is undefined until a line is found containing the string ***### END on which PrintLines is defined again for the following lines.
Please read second half of this answer for details about the commands SETLOCAL and ENDLOCAL.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
del /?
echo /?
endlocal /?
findstr /?
for /?
if /?
move /?
set /?
setlocal /?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q65218525.txt"
SET "outfile=%destdir%\outfile.txt"
SET "blockstart=! ***### START"
SET "blockend=! ***### END"
SET "repro=Y"
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r ".*" "%filename1%"') DO (
IF /i "%%b"=="%blockstart%" SET "repro="
IF /i "%%b"=="%blockend%" (SET "repro=Y") ELSE (IF DEFINED repro IF "%%b"=="" (ECHO.) ELSE (ECHO %%b))
)
)>"%outfile%"
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q65218525.txt containing your data for my testing.
Produces the file defined as %outfile%
Since your code saves the original file under a new name and creates a new file with the old name but your requirement seems to be to simply overwrite the old file, I'll leave that part as an exercise.
First, define the strings involved, and a flag (which I've called repro) which is initialised to reproduce the data.
Then use findstr with the /N option to prefix each line, including blank lines, with number: which is then parsed by the for/r to put the number in %%a and the text in %%b.
Check %%b for the target strings and frob the repro flag as appropriate to select whether to reproduce the line read or not.
Another way to do this would be using PowerShell. Invoke it from the command line or a .bat file script using:
powershell -NoLogo -NoProfile -File '.\Slice-File.ps1'
=== Slice-File.ps1
$SourceFile = 'C:\src\t\Slice-File.txt'
$TempFile = New-TemporaryFile
$WriteIt = $true
Get-Content -Path '.\Slice-File.txt' |
ForEach-Object {
if ($_ -eq '! ***### START') { $WriteIt = $false }
if ($WriteIt) { $_ }
if ($_ -eq '! ***### END') { $WriteIt = $true }
} |
Out-File -FilePath $TempFile -Encoding ascii
Remove-Item -Path $SourceFile
Move-Item -Path $TempFile -Destination $SourceFile

Copy the line after the findstr result

I have a file with contents like this:
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>8.10</string>
<key>CFBundleURLTypes</key>
To extract the line with CFBundleShortVersionString, I can use:
(for /f "delims=," %a in ('type "Info.pList" ^|findstr "CFBundleShortVersionString"') do #echo %a) > "CFBundleShortVersionString.txt"
gives output
<key>CFBundleShortVersionString</key>
But how do I extract the line below that please? i.e.
<string>8.10</string>
I am using the command prompt in Windows 10.
Thanks,
Chris.
If you only need those two lines let's just use this trick
You can paste this into the CLI:
SET "_Found="
#for /f "delims=," %a in ('
Type "Info.pList"
') do #(
IF NOT DEFINED _Found (
ECHO=%a|find "CFBundleShortVersionString" && (
SET "_Found=1"
)
) ELSE (
ECHO=%a
SET "_Found="
)
)>> "CFBundleShortVersionString.txt"
This will output every match and the first line after each match
You could first determine the number of the line containing the search string, then read the file and skip that number of lines, like shown in the following batch-file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Initialise variables:
set "LineNumber=" & set "LineString="
rem // Let `findstr` precede the matching line(s) with line number plus a colon:
for /F "delims=:" %%L in ('findstr /N "CFBundleShortVersionString" "Info.pList"') do (
rem // Fetch the line number (remove `if defined LineNumber` if you want the last match):
if not defined LineNumber set /A "LineNumber=%%L"
)
rem // Check whether the search string has been found:
if defined LineNumber (
rem // Read the file but skip lines up to the one containing the search string:
for /F "usebackq skip=%LineNumber% delims=" %%S in ("Info.pList") do (
rem // This captures the next (non-empty) line following the one containing the search string:
if not defined LineString set "LineString=%%S"
)
)
rem // Return result:
echo This is the extracted line: "%LineString%"
endlocal
exit /B
Here is one way to use a regex to get the next line. Select-String is not sed or awk, but it can do some things.
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"(Get-Content -Path C:\src\t\Get-NextLine.txt -Raw |" ^
"Select-String -Pattern 'CFBundleShortVersionString.*\n(.*)\n').Matches.Groups[1].Value"') DO (
SET "NEXT_LINE=%%~A"
)
ECHO NEXT_LINE is "%NEXT_LINE%"
If the goal is to get 8.10 from the line, a little more work on the -Pattern could do that.
NB: If ECHO %NEXT_LINE% is used, the redirection characters <> will be interpreted. That is why the last output line uses QUOTATION MARK characters.

How Can I Replace Any Line by Its Line Number?

EDIT: After great help from #aschipfl, the code is %110 as functional as I wanted it to be! I did some extra research and made it easy to use with prompts for that extra %10 :P
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Create a prompt to set the variables
set /p _FILETYPE="What file type: "
set /p _LINENUM="Which line: "
set /p _NEWLINE="Make line say: "
rem // Start the loop, and set the files
for %%f in (*%_FILETYPE%) do (
set "_FILE=%%f"
echo "_FILE=%%f"
rem // To execute seperate code before the end of the loop, starting at ":subroutine".
call :subroutine "%%f"
)
:subroutine
rem // Write to a temporary file:
> "%_FILE%.new" (
rem /* Loop through each line of the original file,
rem preceded by the line number and a colon `:`:*/
for /F "delims=" %%A in ('findstr /N "^" "%_FILE%"') do (
rem // Store the current line with prefix to a variable:
set "LN=%%A"
rem /* Store the line number into another variable;
rem everything up to the first non-numeric char. is regarded,
rem which is the aforementioned colon `:` in this situation: */
set /A "NUM=LN"
rem // Toggle delayed expansion to avoid trouble with `!`:
setlocal EnableDelayedExpansion
rem /* Compare current line number with predefined one and replace text
rem in case of equality, or return original text otherwise: */
if !NUM! equ %_LINENUM% (
echo(!_NEWLINE!
) else (
rem // Remove line number prefix:
echo(!LN:*:=!
)
endlocal
)
)
rem // Move the edited file onto the original one:
move /Y "%_FILE%.new" "%_FILE%"
endlocal
exit /B
ORIGINAL QUESTION:
Doesn't matter whats in any of the lines already. I just want to be able to pick any line from a .txt and replace it with whatever I choose.
So for example: Maybe I have a bunch of .txt's, and I want to replace line 5 in all of them with "vanilla". And later choose to replace line 10 of all .txt's with "Green". And so on...
I've seen lots of people asking the same main question. But I keep finding situational answers.
"How do I replace specific lines?" "you search for whats already in the line, and replace it with your new text" -I cant have that. I need it to be dynamic, because whats in each "line 5" is different, or there's lots of other lines with the same text.
I had tried the only one answer I could find, but all it ended up doing is replace literally all lines with "!ln:*:=!", instead of echoing.
#echo off
setlocal disableDelayedExpansion
set "file=yourFile.txt"
set "newLine5=NewLine5Here"
>"%file%.new" (
for /f "delims=" %%A in ('findstr /n "^" "%file%"') do for /f "delims=:" %%N in ("%%A") do (
set "ln=%%A"
setlocal enabableDelayedExpansion
if "!ln:~0,6!" equ "5:FMOD" (echo(!newLine5!) else echo(!ln:*:=!
endlocal
)
)
move /y "%file%.new" "%file%" >nul
The following (commented) code should work for you:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=yourFile.txt"
set "_NEWLINE=NewLine5Here"
set /A "_LINENUM=5" & rem // (target line number)
rem // Write to a temporary file:
> "%_FILE%.new" (
rem /* Loop through each line of the original file,
rem preceded by the line number and a colon `:`:*/
for /F "delims=" %%A in ('findstr /N "^" "%_FILE%"') do (
rem // Store the current line with prefix to a variable:
set "LN=%%A"
rem /* Store the line number into another variable;
rem everything up to the first non-numeric char. is regarded,
rem which is the aforementioned colon `:` in this situation: */
set /A "NUM=LN"
rem // Toggle delayed expansion to avoid trouble with `!`:
setlocal EnableDelayedExpansion
rem /* Compare current line number with predefined one and replace text
rem in case of equality, or return original text otherwise: */
if !NUM! equ %_LINENUM% (
echo(!_NEWLINE!
) else (
rem // Remove line number prefix:
echo(!LN:*:=!
)
endlocal
)
)
rem // Move the edited file onto the original one:
move /Y "%_FILE%.new" "%_FILE%"
endlocal
exit /B
Besides the typo in EnableDelayedExpansion in your code, you do not even need a second for /F loop to get the line number, and you do not need to extract a certain number of characters from the prefixed line text.
Note that this approach fails for line numbers higher than 231 - 1 = 2 147 483 647.
...is replace literally all lines with "!ln:*:=!", instead of echoing.
But that's correct, because the FINDSTR /N prefixes each line with a line number before.
The !ln:*:=! only removes the line number again.
And the findstr trick is used to avoid skipping of empty lines or lines beginning with ; (the EOL character).
The !line:*:=! replaces everthing up to the first double colon (and incuding it) with nothing.
This is better than using FOR "delims=:" because delims=: would also strip double colons at the front of a line.
The toggling of delayed expansion is necessary to avoid accidential stripping of ! and ^ in the line set "ln=%%A"
To fix your code:
setlocal DisableDelayedExpansion
for /f "delims=" %%A in ('findstr /n "^" "%file%"') do (
set "ln=%%A"
setlocal EnableDelayedExpansion
if "!ln:~0,6!" equ "5:FMOD" (
set "out=!newLine5!"
) else (
set "out=!ln:*:=!"
)
echo(!out!
endlocal
)

Loop recursively into subfolders and look for a substring into files

I would like to create a script that loops reccursively through subfolders of D:\MyFolder\ for example, to find multiple files named MyFile.txt
then look into each file for the keyword FROM and retrieve the string between the FROM and the next semicolon ;.
Sample of MyFile.txt:
LOAD
Thing1,
Thing2,
Thing3,
FROM
Somewhere ;
The desired result is: Somewhere.
(The position of the semicolon ; can be in another line).
I did some tries but I did not succeed in writing a correct script:
#echo off
SET PATH="D:\MyFolder\"
FOR /R %PATH% %%f IN (MyFile.txt) DO (
FOR /F "delims=FROM eol=;" %%A in (%%f) do (
set str=%%A
ECHO %str%
)
)
If it can't be done in batch, please let me know in which language I can do it easily. I would like to have an executable script in the end.
There are some issues in your code:
The delims option of for /F defines characters but not words to be used as delimiter for parsing text files. To find a word, use findstr instead (you could use its /N option to derive the position/line number of the search string).
The eol option of for /F defines a character to ignore a line in case it occurs at the beginning (or it is preceded by delimiters only).
for /R does actually not search for files in case there are no wild-cards (?, *) in the set (that is the part in between parentheses). The dir /S command does, so you can work around this by wrapping a for /F loop around dir /S.
The PATH variable is used by the system to find executables, like findstr, so you must not overwrite it; use a different variable name instead.
Here is the way I would probably do it (supposing any text following the keyword FROM needs to be returned also):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\MyFolder" & rem // (root directory of the tree to find files)
set "_FILE=MyFile.txt" & rem // (name of the files to find in the tree)
set "_WORD=FROM" & rem // (keyword to be searched within the files)
set "_CHAR=;" & rem // (character to be searched within the files)
rem // Walk through the directory tree and find matching files:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Retrieve the line number of each occurrence of the keyword:
for /F "delims=:" %%N in ('findstr /N /I /R "\<%_WORD%\>" "%%~F"') do (
rem // Process each occurrence of the keyword in a sub-routine:
call :PROCESS "%%~F" %%N
)
)
endlocal
exit /B
:PROCESS
rem // Ensure the line number to be numeric and build `skip` option string:
set /A "SKIP=%~2-1"
if %SKIP% GTR 0 (set "SKIP=skip^=%SKIP%") else set "SKIP="
rem // Read file starting from line containing the found keyword:
set "FRST=#"
for /F usebackq^ %SKIP%^ delims^=^ eol^= %%L in ("%~1") do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Split off everything up to the keyword from the first iterated line:
if defined FRST set "LINE=!LINE:*%_WORD%=!"
rem /* Split read line at the first occurrence of the split character;
rem the line string is augmented by preceding and appending a space,
rem so it is possible to detect whether a split char. is there: */
for /F "tokens=1,* delims=%_CHAR% eol=%_CHAR%" %%S in (" !LINE! ") do (
endlocal
set "TEXT=%%S"
set "RMND=%%T"
set "ITEM=%~1"
setlocal EnableDelayedExpansion
rem // Check whether a split character is included in the line string:
if not defined RMND (
rem // No split char. found, so get string without surrounding spaces:
set "TEXT=!TEXT:~1,-1!"
) else (
rem // Split char. found, so get string without leading space:
set "TEXT=!TEXT:~1!"
)
rem // Trimm leading white-spaces:
for /F "tokens=*" %%E in ("!TEXT!") do (
endlocal
set "TEXT=%%E"
setlocal EnableDelayedExpansion
)
rem // Return string in case it is not empty:
if defined TEXT echo(!ITEM!;!TEXT!
rem // Leave sub-routine in case split char. has been found:
if defined RMND exit /B
)
endlocal
set "FRST="
)
exit /B

CMD script to find and replace a part of found line in file

I need to find all lines in myfile.txt containing word 'MyWord', and then replace a part of this string next way:
Original line:
...,31-01-2012,00,some_words_and_symbols_and_digits,MyWord,...
After replace:
...,31-01-2012,01,some_words_and_symbols_and_digits,MyWord,...
Please, help me to write this cmd script!
OK.. I have next code:
#echo off
set code=MyWord
set req=new request
FOR /F "usebackq delims=, tokens=1,2,3,4,5,6,7,8,9*" %%a in (MyFile.txt) do (
IF %%h==%code% (
SET tempstr=%%a,%%b,%%c,%%d,60,%%f,%%g,%%h,%%i
) ELSE (
SET tempstr=%%a,%%b,%%c,%%d,%%e,%%f,%%g,%%h,%%i
)
IF %%a==%req% (
SET echo %%a >> new.strings
) ELSE (
echo %tempstr% >> new.strings
)
)
#echo on
And I have in my file something like:
new request
...,31-01-2012,01,some_words_and_symbols_and_digits,MyWord,...
new request
...,30-11-2011,01,some_words_and_symbols_and_digits,OtherWords,...
But then I have error:
ELSE was unexpected at this time.
And If I'm trying simple next in the end
IF %%a==%req% SET tempstr=%%a
echo %tempstr% >> new.strings
Then I have only one last row instead of other else
You can use find command to filter the lines containing given text. As I see, the file is CSV. So you can use for /f to parse the lines found. Then you can echo all parsed files replacing the field you want.
This will replace all values in the 3rd column with "01"
#echo off
for /f "usebackq delims=, tokens=1,2,3,4,<put as many as you need>" %%A in (`find "MyWord" myfile.txt`) do echo %%A,%%B,01,%%D,<as many %%letters as tokens>
If you want to replace the value only on some lines, you can use if command inside for /f loop.
==== EDIT
The problem is with the value of req variable. It contains a space, so after substitution your second if statement has the following form:
IF %%a==new request (
so if %%a is equal to new it will execute request ( echo ...... ) and then ELSE is unexpected indeed. Enclose both %%a and %req% in quotation marks and the problem will disappear.
But I see also other problems. First, you have redundant set in your second if statement.
Second, you need to use delayed expansion of variables, or your echo %tempstr% won't work.
Your code after needed changes:
#Echo off
setlocal enabledelayedexpansion
set code=MyWord
set req=new request
FOR /F "usebackq delims=, tokens=1,2,3,4,5,6,7,8,9*" %%a in (MyFile.txt) do (
IF %%h==%code% (
SET tempstr=%%a,%%b,%%c,%%d,60,%%f,%%g,%%h,%%i
) ELSE (
SET tempstr=%%a,%%b,%%c,%%d,%%e,%%f,%%g,%%h,%%i
)
IF "%%a"=="%req%" (
echo %%a >> new.strings
) ELSE (
echo !tempstr! >> new.strings
)
)
endlocal

Resources