Could I use findstr to search a string between specified bytes/positions only..
For example I have a text file and each line has maximum 1000 bytes
I wanted to search lines with a string in between byte number 50 to 100 only?
Your problem would be relatively easy to solve using regular expressions (regex). But unfortunately, FINDSTR support for regex is extremely limited. It does not have the features needed to solve your problem.
You could use grep for Windows instead of FINDSTR, but that requires a download.
Assuming you want to find my string somewhere between positions 50 and 100 on any line within "file.txt":
grep "^.\{49,91\}my string" file.txt
Another option is to switch to another scripting language with full support for regex. JScript, VBScript, and PowerShell can all be used to easily solve this problem.
A pure native batch solution requires a non-trivial script, and is much slower. Here is one possible solution:
#echo off
setlocal disableDelayedExpansion
for /f delims^=^ eol^= %%L in (file.txt) do (
set "ln=%%A"
setlocal enableDelayedExpansion
set "ln=!ln:~49,51!"
if "!ln:my string=!" neq "!ln!" echo !ln!
endlocal
)
Related
I'm looking for a way to find and replace multiple words in a text file using a Windows batch script.
I know replacing a word can be done so with this bat script:
#echo off &setlocal
set "search=%1"
set "replace=%2"
set "textfile=Input.txt"
set "newfile=Output.txt"
(for /f "delims=" %%i in (%textfile%) do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:%search%=%replace%!"
echo(!line!
endlocal
))>"%newfile%"
del %textfile%
rename %newfile% %textfile%
Now, I just have to take it one step further and feed in the search and replace strings from another file similar to this format:
search_string1, replace_string1
search_string2, replace_string2
search_string3, replace_string3
.
.
.
I would think that I would somehow process the file line by line, and parse them into two variables (search, replace) and then feed that into the script above. Any thoughts? I'm new to Windows batch scripts and have never really made one before so mind my newbie questions.
This type of text replacements are slow and prone to fail when performed via a Batch file. I wrote FindRepl.bat program that is a Batch-JScript hybrid script that not only run much faster and with no errors, but it also allows to perform the multiple replacements you are looking for in just one processing pass of the data file. JScript is a programming language that is included in all Windows versions from XP on. Using FindRepl.bat program you may solve your problem this way:
#echo off
setlocal EnableDelayedExpansion
set "search="
set "replace="
for /F "tokens=1,2 delims=," %%a in (replacements.txt) do (
set "search=!search!|%%a"
set "replace=!replace!|%%b"
)
set "search=!search:~1!"
set "replace=!replace:~1!"
< Input.txt FindRepl =search /A =replace > Output.txt
Note that all text placed after the comma in the replacements file is the replacement string, including spaces.
You may download FindRepl.bat program from this site. Place it in the same folder of previous program or, better yet, in a folder included in %PATH%, so you may use it directly.
The goal is to utilize the task scheduler to block facebook from a designated time to increase productivity and reduce distractions. I deleted facebook for seven months. I don't want to use third party software. Please help me. Also if you have an easier method or better code please show.
Here is what I have.
I have this batch file = blockfacebook.bat executed a particular time which works:
echo 0.0.0.0 www.facebook.com >> c:\windows\system32\drivers\etc\hosts
Although the next batch file = unblockfacebook.bat has the result of completely emptying the hosts file:
type c:\windows\system32\drivers\etc\hosts | findstr /v facebook > c:\windows\system32\drivers\etc\hosts
Could anyone tell me what I am doing wrong with the unblockfacebook.bat
Thank you,
AEGIS
#echo off
setlocal enableextensions disabledelayedexpansion
set "file=c:\windows\system32\drivers\etc\hosts"
for /f "tokens=* delims=0123456789" %%a in (
'findstr /n /i /v /c:"facebook" "%file%" ^& type nul ^> "%file%"'
) do (
set "line=%%a"
setlocal enabledelayedexpansion
>>"%file%" echo(!line:~1!
endlocal
)
endlocal
This code uses findstr to filter the input lines and number the output (numbering the lines ensures the empty lines in input file are also readed, processed and written to output), and removes the content of the input file. As for /f has cached the output of findstr in memory, and findstr has ended processing the data, there is no problem to do it.
Then for starts to process the input (the output of findstr). It is a list of numbered lines. To remove the numbers, they are used as delimiters in the for command. As the lines start with delimiters, they are removed until a non delimiter character is found, the colon that separates the numbers from the line content (colon has not been used as delimiter to avoid problems with lines starting with a colon per example some ipv6 address)
Now, with the numbers removed from start all that needs to be done is to remove the first character and append the lines to the input/output file (that was emptied).
type c:\windows\system32\drivers\etc\hosts | findstr /v facebook > c:\users\aegis\desktop\grr.txt
type c:\users\aegis\desktop\grr.txt > c:\windows\system32\drivers\etc\hosts
That's the solution I figured it out when I read this. Although more effective or alternative methods are always appreciated.
I need to replace a single line in a file.
Generally, this code works fine:
(The actual specifics on what this block is doing is not necessary for this question).
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" %DATA%') do (
if %%a equ %TargetLine% (
echo !insert!>>%filepath%cc.tmp
) else (
if [%%b]==[] (echo.>>%filepath%cc.tmp) else (echo %%b>>%filepath%cc.tmp)
)
)
Unfortunately, each line is assigned to %%a, which like any other variable can only store a maximum length of 8,192 characters (thanks dbenham for that tidbid, comes in use now).
So what options do I have when the line is greater than 8,192 characters (23,708 in this case)?
Before you ask: No it cannot be separated to a new line, it is an 10k JSON array encoded in Base64 which is then written into a ByteArray.
I assume that the way to go is using regular expressions, is this the correct assumption, or is there another workaround?
Thanks.
You could solve this with pure batch!
:readLongLine
< longline.tmp (
for /L %%n in (1 1 20) do set /p part[%%n]=
)
After this your line is splitted into the variables part[1] .. part[20]
Writing this to a new file you could use
:writeLongLine
<nul (
for /L %%n in (1 1 19) do set /p ".=!part[%%n]!"
(echo !part[20]!)
) > longLine2.tmp
You could use some other scripting language like VBScript, JScript, or PowerShell.
If you want to remain in the batch world, you can use a handy hybrid JScript/batch utility called REPL.BAT that performs regex search and replace on stdin and writes result to stdout. It is quite efficient, and works on any Windows machine from XP onward. It is pure script, so no exe download required. You can get REPL.BAT here. Full documentation is embedded within the script.
Simply use sed, awk or Perl for the job.
How would I achieve this:
for i in *.e; do mv $i ${i%-b*.e}.e; done
in a Windows batch file? (It renames files containing "-b" to the part before "-b". Note that this is not necessarily the end of the string! e.g. "file-b-4.e" will become "file.e")
If you really want to do this in batch, this should work
#echo off
setlocal disableDelayedExpansion
for %%F in (*.e) do (
set "var=%%~F"
setlocal enableDelayedExpansion
set "var=!var:-b=.e:!"
for /f "eol=: delims=:" %%A in ("!var!") do (
endlocal
echo ren "%%F" "%%A"
)
)
Edit
The comment by panda-34 alluded to the fact that the original posted code failed if the file name begins with -b. The code above was fixed by incorporating the extension into the replacement string. (thanks panda-34 for alerting me to the problem)
panda-34 also provided an alternate solution that uses command injection with search and replace. The injected command is the REM statement.
The panda-34 solution works as long as the file name does not contain & or ^ characters, but fails if it does.
Below is a modified version of the command injection technique that should work with all valid Windows file names. There are 2 critical mods, 1) make sure the special chars in the file name are always quoted, and 2) do not pass the value as a CALL argument, otherwise ^ will be doubled to ^^.
#echo off
setlocal disableDelayedExpansion
for %%i in (*-b*.e) do (
set old="%%~ni"
call :ren_b
)
exit /b
:ren_b
set v=%old:-b=.e"&rem "%
echo ren "%old:~1,-1%.e" %v%
exit /b
Final Edit (I hope):
As baruch indicates in his comment, the solutions above remove starting with the 1st occurance, whereas the original bash command removes starting with the last occurance.
Below is a version that should be an exact equivalent of the original bash command.
#echo off
setlocal disableDelayedExpansion
set "search=-b"
for %%A in (*%search%*.e) do (
set "old=%%A"
setlocal enableDelayedExpansion
set "new=\_!old:%search%=\_!"
for %%B in ("!new!") do (
endlocal
set "new=%%~pB"
setlocal enableDelayedExpansion
set "new=!new:~2,-1!.e"
echo ren "!old!" "!new:\_=%search%!"
endlocal
)
)
Simple, really
for %%i in (*-b*.e) do call :ren_b %%~ni
goto :eof
:ren_b
set v=%*
set v="%v:-b=.e" ^& rem %
ren "%*.e" %v%
Here's a variant to keep the name till the last -b occurence
setlocal enabledelayedexpansion
for %%i in (*-b*.e) do (
set v=%%~ni
set v=!v:-b=\!
for %%j in ("\!v!") do (
set v=%%~pj
set v=!v:~1,-1!
set v=!v:\=-b!
ren "%%i" "!v!.e"
)
)
It will fail for names containing ! and starting with -b.
P.S, Didn't see, dbenham already provided the equivalent solution, probably with more provisions for terminal cases of file names.
Forget it, some convenient things cannot be done in NT scripting. What you are asking here is not possible to my knowledge. And I've written and maintained complex NT scripts bigger than 50 KiB, using all kinds of tricks. The book "Windows NT Shell Scripting" points out many of these, for the same and more see Rob van der Woude's scripting pages.
I reckon you could do part of this, but certainly not in a one-liner due to how variable expansion works in NT scripting. For example you could extract the part of the string that you expect to be -b and check whether it is -b, then extract the other parts and rename from the original name to the one that is comprised of only the extracted parts.
But you'll likely need ten to fifteen lines to achieve that. In that light, consider using a different scripting language for the purpose. Especially if this is a modern Windows version.
I realize this is not the desired answer (i.e. that this is possible and a sample), but cmd.exe is very limited compared to Bash, albeit by far not as limited as some opponents of traditional batch scripting are pointing out.
I'm trying to create a batch that creates a fileC.txt containing all lines in fileA.txt except for those that contains the strings in the lines in fileB.txt:
Pseudo:
foreach(line L in fileA.txt)
excluded = false
foreach(string str in fileB.txt)
if L contains str
exclude = true
if !excluded
add L to fileC.txt
if L !contains
For example
fileA.txt: (all)
this\here\is\a\line.wav
and\this\is\another.wav
i\am\a\chocolate.wav
peanut\butter\jelly\time.wav
fileB.txt: (those to be excluded)
another.wav
time.wav
fileC.txt: (wanted result)
this\here\is\a\line.wav
i\am\a\chocolate.wav
I've been fiddling around with FINDSTR but I just can't seem to puzzle it together.. any help or pointers greatly appreciated!
Cheers!
/ Fredde
The answer should be this simple:
findstr /lvg:"fileB.txt" "fileA.txt" >fileC.txt
And with your example, the above does give the correct results.
But there is a nasty FINDSTR bug that makes it unreliable when using multiple case sensitive literal search strings. See Why doesn't this FINDSTR example with multiple literal search strings find a match?, as well as the answer that goes with it. For a "complete" list of undocumented FINDSTR features and bugs, see What are the undocumented features and limitations of the Windows FINDSTR command?.
So the simple code above can fail depending on the content of the files. If you can get away with using a case insensitive search, then the solution is simple.
findstr /livg:"fileB.txt" "fileA.txt" >fileC.txt
Edit: Both versions above will fail if fileB.txt contains \\ or \". In order to work properly, those strings must be escaped as \\\ and \\"
But if you must use a case sensitive search, then there is no simple solution. Your best bet for a pure batch solution might be to use the /R regular expression option. But then you will have to create a modified version of fileB.txt where all regex meta-characters are escaped so that the strings give the correct literal search. That is a mini project in and of itself.
Perhaps your best option for a case sensitive solution is to get a 3rd party tool like grep or sed for Windows.
Edit: Here is a reasonably performing pure batch solution that is nearly bullet proof
I looked into doing something like the proposed logic in your question. But using batch to read all lines in a file is relatively slow. This solution only reads the exclude file line by line. It uses FINDSTR to read the lines in "fileA.txt" repeatedly, once per search string. This is a much faster algorithm for a batch file.
The traditional method to read a file is to use a FOR /F loop, but there is another technique using SET /P that is faster, and it is safe to use with delayed expansion. The only limitations to this method are:
It strips trailing control characters from the line
It is limited to 1021 bytes per line
Each line must be terminated by <CR><LF> as is the Windows standard. It will not work with unix style lines terminated by <LF>
The search strings must have each \ and " escaped as \\ and \" when they are used with the /C option.
#echo off
setlocal enableDelayedExpansion
copy fileA.txt fileC.txt >nul
for /f %%N in ('find /c /v "" ^<fileB.txt') do set len=%%N
<fileB.txt (
for /l %%N in (1 1 !len!) do (
set "ln="
set /p "ln="
if defined ln (
set "ln=!ln:\=\\!"
set ln=!ln:"=\"!
move /y fileC.txt temp.txt >nul
findstr /lv /c:"!ln!" temp.txt >fileC.txt
)
)
)
del temp.txt
type fileC.txt