I want to search and replace case sensitive string
like if I have rise Rise RISE in a text file I only want to replace string \"rise\" the code below is replace a
This is a subject that has interested me for a long time. My personal criteria is that the solution is a script that utilizes only native Windows commands, and that it be compatible with all Windows versions from XP onward.
I have developed two solutions: 1) A pure batch solution that I believe is about as efficient as is possible for batch, and 2) a hybrid JScript/batch solution that is extremely powerful and also very fast.
I have pretty much abandoned the pure batch solution in favor of the JScript/batch hybrid because the hybrid is more powerful with full regex support, and it is MUCH faster.
1) Pure batch solution: MODFILE.BAT
I first published this at DOSTIPS: The "ultimate" file search and replace batch utility
The batch function can be used as a stand-alone utility, or incorporated within a larger batch script.
Assuming the function is a stand-alone utility in a file named MODFILE.BAT that is either in your current folder, or else somewhere within your PATH, then your script becomes:
@echo off
setlocal enableDelayedExpansion
set file="c:\Users\rawal\Desktop\a\file.txt"
set "OldStr=rise"
set "NewStr="
set /p "NewStr=Enter some text: "
call ModFile "%file%" OldStr NewStr
Here is the ModFile function itself. Full documentation is embedded within the script. I've taken great pains to optimize the code, and eliminate limitations that plague most batch solutions. But there are a few remaining limitations that are listed within the documentation.
@echo off
:modFile File SearchVar [ReplaceVar] [/I]
::
:: Perform a search and replace operation on each line within File.
::
:: SearchVar = A variable containing the search string.
::
:: ReplaceVar = A variable containing the replacement string.
:: If ReplaceVar is missing or is not defined then the
:: search string is replaced with an empty string.
::
:: The /I option specifies a case insensitive search.
::
:: A backup of the original File is made with an extension of .bak
:: prior to making any changes.
::
:: The number of replacements made is returned as errorlevel.
::
:: If an error occurs then no changes are made and
:: the errorlevel is set to -1.
::
:: Limitations
:: - File must use Windows style line terminators <CR><LF>.
:: - Trailing control characters will be stripped from each line.
:: - The maximum input line length is 1021 characters.
::
setlocal enableDelayedExpansion
::error checking
if "%~2"=="" (
>&2 echo ERROR: Insufficient arguments
exit /b -1
)
if not exist "%~1" (
>&2 echo ERROR: Input file "%~1" does not exist
exit /b -1
)
2>nul pushd "%~1" && (
popd
>&2 echo ERROR: Input file "%~1" does not exist
exit /b -1
)
if not defined %~2 (
>&2 echo ERROR: searchVar %2 not defined
exit /b -1
)
if /i "%~3"=="/I" (
>&2 echo ERROR: /I option can only be specified as 4th argument
exit /b -1
)
if "%~4" neq "" if /i "%~4" neq "/I" (
>&2 echo ERROR: Invalid option %4
exit /b -1
)
::get search and replace strings
set "_search=!%~2!"
set "_replace=!%~3!"
::build list of lines that must be changed, simply exit if none
set "replaceCnt=0"
set changes="%temp%\modFileChanges%random%.tmp"
<"%~1" find /n %~4 "!_search:"=""!^" >%changes% || goto :cleanup
::compute length of _search
set "str=A!_search!"
set searchLen=0
for /l %%A in (12,-1,0) do (
set /a "searchLen|=1<<%%A"
for %%B in (!searchLen!) do if "!str:~%%B,1!"=="" set /a "searchLen&=~1<<%%A"
)
::count number of lines + 1
for /f %%N in ('find /v /c "" ^<"%~1"') do set /a lnCnt=%%N+1
::backup source file
if exist "%~1.bak" del "%~1.bak"
ren "%~1" "%~nx1.bak"
::initialize
set "skip=2"
<"%~1.bak" (
%=for each line that needs changing=%
for %%l in (!searchLen!) do for /f "usebackq delims=[]" %%L in (%changes%) do (
%=read and write preceding lines that don't need changing=%
for /l %%N in (!skip! 1 %%L) do (
set "ln="
set /p "ln="
if defined ln if "!ln:~1021!" neq "" goto :lineLengthError
echo(!ln!
)
%=read the line that needs changing=%
set /p "ln="
if defined ln if "!ln:~1021!" neq "" goto :lineLengthError
%=compute length of line=%
set "str=A!ln!"
set lnLen=0
for /l %%A in (12,-1,0) do (
set /a "lnLen|=1<<%%A"
for %%B in (!lnLen!) do if "!str:~%%B,1!"=="" set /a "lnLen&=~1<<%%A"
)
%=perform search and replace on line=%
set "modLn="
set /a "end=lnLen-searchLen, beg=0"
for /l %%o in (0 1 !end!) do (
if %%o geq !beg! if %~4 "!ln:~%%o,%%l!"=="!_search!" (
set /a "len=%%o-beg"
for /f "tokens=1,2" %%a in ("!beg! !len!") do set "modLn=!modLn!!ln:~%%a,%%b!!_replace!"
set /a "beg=%%o+searchLen, replaceCnt+=1"
)
)
for %%a in (!beg!) do set "modLn=!modLn!!ln:~%%a!"
%=write the modified line=%
echo(!modLn!
%=prepare for next iteration=%
set /a skip=%%L+2
)
%=read and write remaining lines that don't need changing=%
for /l %%N in (!skip! 1 !lnCnt!) do (
set "ln="
set /p "ln="
if defined ln if "!ln:~1021!" neq "" goto :lineLengthError
echo(!ln!
)
) >"%~1"
:cleanup
del %changes%
exit /b %replaceCnt%
:lineLengthError
del %changes%
del "%~1"
ren "%~nx1.bak" "%~1"
>&2 echo ERROR: Maximum input line length exceeded. Changes aborted.
exit /b -1
2) Hybrid JScript/batch solution: REPL.BAT
I first published this at DOSTIPS: regex search and replace for batch - Easily edit files!
I really love this utility. Most batch scripting I do as a hobby, but I use this utility regularly in my day job. It is extremely powerful and fast, yet requires very little code. It supports regular expression search and replace, but also has an /L
literal option. The search is case sensitive by default.
Assuming REPL.BAT is either in your current folder, or else somewhere within your PATH, then your code becomes:
@echo off
setlocal enableDelayedExpansion
set "file=c:\Users\rawal\Desktop\a\file.txt"
set "OldStr=rise"
set "NewStr="
set /p "NewStr=Enter some text: "
type "%file%" | repl OldStr NewStr VL >"%file%.new"
move /y "%file%.new" "%file%" >nul
I use the L
option to force a literal search instead of the default regex search, and the V
option to read the search and replace values directly from environment variables instead of passing string literals.
Here is the actual REPL.BAT utility. Full documentation is embedded within the script.
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::************ Documentation ***********
:::
:::REPL Search Replace [Options [SourceVar]]
:::REPL /?
:::
::: Performs a global search and replace operation on each line of input from
::: stdin and prints the result to stdout.
:::
::: Each parameter may be optionally enclosed by double quotes. The double
::: quotes are not considered part of the argument. The quotes are required
::: if the parameter contains a batch token delimiter like space, tab, comma,
::: semicolon. The quotes should also be used if the argument contains a
::: batch special character like &, |, etc. so that the special character
::: does not need to be escaped with ^.
:::
::: If called with a single argument of /? then prints help documentation
::: to stdout.
:::
::: Search - By default this is a case sensitive JScript (ECMA) regular
::: expression expressed as a string.
:::
::: JScript regex syntax documentation is available at
::: http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
::: Replace - By default this is the string to be used as a replacement for
::: each found search expression. Full support is provided for
::: substituion patterns available to the JScript replace method.
::: A $ literal can be escaped as $$. An empty replacement string
::: must be represented as "".
:::
::: Replace substitution pattern syntax is documented at
::: http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
::: Options - An optional string of characters used to alter the behavior
::: of REPL. The option characters are case insensitive, and may
::: appear in any order.
:::
::: I - Makes the search case-insensitive.
:::
::: L - The Search is treated as a string literal instead of a
::: regular expression. Also, all $ found in Replace are
::: treated as $ literals.
:::
::: B - The Search must match the beginning of a line.
::: Mostly used with literal searches.
:::
::: E - The Search must match the end of a line.
::: Mostly used with literal searches.
:::
::: V - Search and Replace represent the name of environment
::: variables that contain the respective values. An undefined
::: variable is treated as an empty string.
:::
::: M - Multi-line mode. The entire contents of stdin is read and
::: processed in one pass instead of line by line. ^ anchors
::: the beginning of a line and $ anchors the end of a line.
:::
::: X - Enables extended substitution pattern syntax with support
::: for the following escape sequences:
:::
::: \\ - Backslash
::: \b - Backspace
::: \f - Formfeed
::: \n - Newline
::: \r - Carriage Return
::: \t - Horizontal Tab
::: \v - Vertical Tab
::: \xnn - Ascii (Latin 1) character expressed as 2 hex digits
::: \unnnn - Unicode character expressed as 4 hex digits
:::
::: Escape sequences are supported even when the L option is used.
:::
::: S - The source is read from an environment variable instead of
::: from stdin. The name of the source environment variable is
::: specified in the next argument after the option string.
:::
::************ Batch portion ***********
@echo off
if .%2 equ . (
if "%~1" equ "/?" (
findstr "^:::" "%~f0" | cscript //E:JScript //nologo "%~f0" "^:::" ""
exit /b 0
) else (
call :err "Insufficient arguments"
exit /b 1
)
)
echo(%~3|findstr /i "[^SMILEBVX]" >nul && (
call :err "Invalid option(s)"
exit /b 1
)
cscript //E:JScript //nologo "%~f0" %*
exit /b 0
:err
>&2 echo ERROR: %~1. Use REPL /? to get help.
exit /b
************* JScript portion **********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var args=WScript.Arguments;
var search=args.Item(0);
var replace=args.Item(1);
var options="g";
if (args.length>2) {
options+=args.Item(2).toLowerCase();
}
var multi=(options.indexOf("m")>=0);
var srcVar=(options.indexOf("s")>=0);
if (srcVar) {
options=options.replace(/s/g,"");
}
if (options.indexOf("v")>=0) {
options=options.replace(/v/g,"");
search=env(search);
replace=env(replace);
}
if (options.indexOf("l")>=0) {
options=options.replace(/l/g,"");
search=search.replace(/([.^$*+?()[{\\|])/g,"\\$1");
replace=replace.replace(/\$/g,"$$$$");
}
if (options.indexOf("b")>=0) {
options=options.replace(/b/g,"");
search="^"+search
}
if (options.indexOf("e")>=0) {
options=options.replace(/e/g,"");
search=search+"$"
}
if (options.indexOf("x")>=0) {
options=options.replace(/x/g,"");
replace=replace.replace(/\\\\/g,"\\B");
replace=replace.replace(/\\b/g,"\b");
replace=replace.replace(/\\f/g,"\f");
replace=replace.replace(/\\n/g,"\n");
replace=replace.replace(/\\r/g,"\r");
replace=replace.replace(/\\t/g,"\t");
replace=replace.replace(/\\v/g,"\v");
replace=replace.replace(/\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}/g,
function($0,$1,$2){
return String.fromCharCode(parseInt("0x"+$0.substring(2)));
}
);
replace=replace.replace(/\\B/g,"\\");
}
var search=new RegExp(search,options);
if (srcVar) {
WScript.Stdout.Write(env(args.Item(3)).replace(search,replace));
} else {
while (!WScript.StdIn.AtEndOfStream) {
if (multi) {
WScript.Stdout.Write(WScript.StdIn.ReadAll().replace(search,replace));
} else {
WScript.Stdout.WriteLine(WScript.StdIn.ReadLine().replace(search,replace));
}
}
}
@ECHO OFF
SETLOCAL
SET "old=rise"
SET "new=deflate"
DEL newfile.txt /F /Q
FOR /f "delims=" %%i IN ('type somefile.txt^|findstr /n "$" ') DO (
ECHO %%i
SET line=%%i
CALL :replace
)
FC somefile.txt newfile.txt
GOTO :eof
:REPLACE
:: first replace all characters up to the colon by nothing
SET line=%line:*:=%
SET "withreplacements="
:loop
IF NOT DEFINED line >>newfile.txt ECHO(%withreplacements%&GOTO :EOF
ECHO %line%|FINDSTR /b /l /c:"%old%" >NUL
IF ERRORLEVEL 1 SET withreplacements=%withreplacements%%line:~0,1%&SET line=%line:~1%&GOTO loop
SET withreplacements=%withreplacements%%new%
SET remove=%old%
:loploop
IF DEFINED remove SET remove=%remove:~1%&SET line=%line:~1%&GOTO loploop
GOTO loop
Here's a relatively simple method. It has a marked sensitivity to certain characters, "^&|<>
being the problems - perhaps some others too - but space,;%!)(
seem fine.
It reads every line by numbering usinf FINDSTR
which places linenumber :
at the beginning of each line
That prefix is removed and the withreplacements
line built character-by-character
replaceme
stringand repeat until the original line
becomes empty
Yes - it's S-L-O-W. But it works. Kinda.
Improvement suggestions welcome.
We all know that Batch files have multiple restrictions, so the creation of general purpose solutions is difficult. Because of this, I always try to fullfill the particular requirements of a certain given problem first. If this is possible, then the limitations of Batch to provide a more general solution for other similar problems that are NOT currently being requested by someone don't matters, right?
The Batch file below do a case-sensitive replacement of one string by another one and it is very fast, but it fail in lines that contain the original string written MORE THAN ONCE in different case combinations, including the target one. I think this method is enough for most users that have this requirement.
@echo off
setlocal EnableDelayedExpansion
set /P "file=Enter file name: "
set /P "OldStr=Enter original text: "
set /P "NewStr=Enter new text: "
rem Get list of numbers of matching lines to replace
set n=0
for /F "delims=:" %%a in ('findstr /N /C:"%OldStr%" "%file%"') do (
set /A n+=1
set replace[!n!]=%%a
)
if %n% equ 0 (
echo Original text not found in file
goto :EOF
)
set /A n+=1
set replace[%n%]=0
rem Process all lines in the file
setlocal DisableDelayedExpansion
set i=1
(for /F "tokens=1* delims=:" %%a in ('findstr /N "^" "%file%"') do (
set line=
set "line=%%b"
setlocal EnableDelayedExpansion
rem If this line have the original string...
for %%i in (!i!) do if %%a equ !replace[%%i]! (
rem ... replace it and advance to next matching line number
echo !line:%OldStr%=%NewStr%!
endlocal & set /A i=%%i+1
) else (
echo(!line!
endlocal
)
)) > "%file%_new.txt
rem If you want to replace the original file, remove REM from next line:
REM move /Y "%file%_new.txt" "%file%"
For example, this input file:
This line is not changed: Rise.
No problem with special characters: & | < > ! " ^
This line is changed: rise
This line is not changed: RISE
This line is incorrectly changed: Rise & rise
with a replacement of "rise" by "New Text", produce:
This line is not changed: Rise.
No problem with special characters: & | < > ! " ^
This line is changed: New Text
This line is not changed: RISE
This line is incorrectly changed: New Text & New Text