Link to home
Start Free TrialLog in
Avatar of taki1gostek
taki1gostekFlag for United States of America

asked on

Batch Script find first instance of string & delete that line

I need functions, which will modify my running script "in place".  The script is called go.bat and contains multiple instances of :Next.  I would like the function to search the script for the first instance of :Next and remove that line (only that line).  

Below is an example:
@ECHO OFF
TITLE TEST
Goto Next
:Next
ECHO this is test #1
*** Function to remove first instance of :Next in the batch file
shutdown -r
:Next
ECHO this is test #2
*** Same function to remove first instance of :Next in the batch file
shutdown -r
:Next
ECHO this is test #1

So... when the script is executed, after the first reboot, the script will look like this:
@ECHO OFF
TITLE TEST
Goto Next
' Notice this is where ":Next" used to be
ECHO this is test #1
*** Function to remove first instance of :Next in the batch file
shutdown -r
:Next
ECHO this is test #2
*** Same function to remove first instance of :Next in the batch file
shutdown -r
:Next
ECHO this is test #1

I tested something like this:
for /f "tokens=* delims= " %%a in (go.bat) do (
echo %%a | findstr ":Next" >> go.bat
)
But it seems to look for all instances of :Next and only copy the lines containing it to the end of the go.bat script.  Can a function be written to remove only the first :Next in the file, either leaving the line blank or deleting it entirely?
Avatar of taki1gostek
taki1gostek
Flag of United States of America image

ASKER

Perhaps I can instead call an external batch file which will perform this function?  Here's a program I found, which will search a file for a specific string and replace it.  It'd have to be modified to stop at the first instance of :Next, and to replace :Next with an empty line.

So, perhaps modify & call the batch file below find.bat and run a command from within go.bat between restarts that reads START /WAIT CALL find.bat ":Next" go.bat?

@echo off
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEDELAYEDEXPANSION

::BatchSubstitude - parses a File line by line and replaces a substring"
::syntax: BatchSubstitude.bat OldStr NewStr File
::          OldStr [in] - string to be replaced
::          NewStr [in] - string to replace with
::          File   [in] - file to be parsed
if "%*"=="" findstr "^::" "%~f0"&GOTO:EOF
for /f "tokens=1,* delims=]" %%A in ('"type %3|find /n /v """') do (
    set "line=%%B"
    if defined line (
        call set "line=echo.%%line:%~1=%~2%%"
        for /f "delims=" %%X in ('"echo."%%line%%""') do %%~X
    ) ELSE echo.
)
As you posted in VB Script zone, the below does what I think you want - look for the first line with an instance of ':NEXT' in it, and remove it. Remove only the first instance.
Call it via cscript using two arguments -
'l' (lower case L) - string to look for
'f' - file to look in
For example: cscript remove.vbs /l:":NEXT" /f:"C:\myscript.bat"
Let us know if this is any good.

Set Fso = CreateObject("Scripting.FileSystemObject") 
lookFor = WScript.Arguments.Named("l")
txtFile = WScript.Arguments.Named("f")
tempFile = Replace(txtFile,Right(txtFile,3),"$$$")
Set objTxt = Fso.OpenTextFile(txtFile,1)
fso.CreateTextFile(tempFile)
Set ObjTemp = Fso.OpenTextFile(tempFile,2)
While Not objTxt.AtEndOfStream
	strLine = objTxt.ReadLine
	If InStr(strLine,LookFor) = 0 Then ObjTemp.WriteLine(strLine) Else boolFound = True
	If boolFound Then objTemp.Write(objTxt.ReadAll)
Wend
objTxt.Close
ObjTemp.Close
fso.DeleteFile(txtFile)
fso.MoveFile tempFile, txtFile

Open in new window

Can actually remove one unecessary line...

Set Fso = CreateObject("Scripting.FileSystemObject") 
lookFor = WScript.Arguments.Named("l")
txtFile = WScript.Arguments.Named("f")
tempFile = Replace(txtFile,Right(txtFile,3),"$$$")
Set objTxt = Fso.OpenTextFile(txtFile,1)
fso.CreateTextFile(tempFile)
Set ObjTemp = Fso.OpenTextFile(tempFile,2)
While Not objTxt.AtEndOfStream
	strLine = objTxt.ReadLine
	If InStr(strLine,LookFor) = 0 Then ObjTemp.WriteLine(strLine) Else objTemp.Write(objTxt.ReadAll)
Wend
objTxt.Close
ObjTemp.Close
fso.DeleteFile(txtFile)
fso.MoveFile tempFile, txtFile

Open in new window

Sweeeeet!  I'll test this out asap and let you know.
Doesn't seem to be working... please take a look at this batch.... utilizing your VBS script... Anything weird stand out?
@ECHO OFF
REM file called go2.bat in c:\find\ directory
REM vbs script called find.vbs in c:\find\ directory
 
TITLE TESTING
GOTO NEXT
:NEXT
ECHO 1
START /wait cscript c:\find\find.vbs /l:":NEXT" /f:"c:\find\go2.bat"
GOTO END
:NEXT
ECHO 2
START /wait cscript c:\find\find.vbs /l:":NEXT" /f:"c:\find\go2.bat"
GOTO END
:NEXT
ECHO 3
START /wait cscript c:\find\find.vbs /l:":NEXT" /f:"c:\find\go2.bat"
GOTO END
PAUSE
:END
ECHO Last Line
PAUSE

Open in new window

I also tried without START /wait, just by running script c:\find\find.vbs... etc...  

It seems to remove the first two instances of :NEXT along with the start /wait cscript c:\find... lines ...
Hmm... I'm getting issues as well but slightly different - it's removing two lines rather than one. And I'm getting an error:
'D' is not an operable batch file... strange.
The vbs does work fine if you run it on a seperate text file. The problem lies in it trying to delete a line out the script being run. Is the point of this so each time the script is run, a different action is performed? Wat actions do you want to perform?
It might be easier to have a seperate text file holding a count of the times the script have been run (could also write a value to the registry). Each time it's run, it consults this value and increments it by 1. The script knows what to run based on the number in the file or reg value.
I could help with VB Script for this, but my DOS scripting isn't so hot....
The go.bat script will be created dynamically by a menu script that will be executed first.  The user will select which software to install by toggling each program they want...  let's say #1, 3, 4 and 7.  After the selections are made, the menu script copies contents of 1.bat, 3.bat, 4.bat and 7.bat into a new go.bat file, and the runs the go.bat file.  

For example, 1.bat contains the code to do an unattended install of office, while 3.bat does the same for vnc and 4.bat installs autodesk, which requires a restart before it can be automatically activated and 7, which continues the batch to install let's say acrobat reader.  

I would prefer not to have to create more batch files, because it'll get confusing, but just have windows "pick up where it left off" in the go.bat file.  

What I am thinking is that for go.bat to continue on the next line, it should use the GOTO command, and go to the :NEXT line.  So all of my 1, 2, 3, 5, 6, 7, etc... will have the same code identifying that it is :NEXT and a script to remove that :NEXT.  

The menu script will assemble the go.bat script to look somewhat like this:
REM 1.bat
:NEXT
start /wait runprogram.msc
script c:\find\find.vbs /l:":NEXT" /f:"c:\find\go2.bat" (which will remove the first :NEXT it finds in go.bat)

REM 2.bat (requires restart)
:NEXT
start /wait runprogram.msc
script c:\find\find.vbs /l:":NEXT" /f:"c:\find\go2.bat" (which will remove the first :NEXT it finds in go.bat)
shutdown -r (to reboot the machine .... the machine will already know to run the go.bat batch file again after windows starts, and Go.bat will tell it to GOTO NEXT, meaning the following line)
:NEXT
start /wait somotherprogram.msc
CALL endscript.bat
:END
Edit to the above...  The first lines of the go.bat file will be:

@ECHO OFF
GOTO NEXT
REM 1.bat
:NEXT
start /wait runprogram.msc ... ... .... .. .

So when go.bat is ran after a restart, it'll skip over what's it's already done and go directly to the next :NEXT.  

Awesome try, bluntTony -- any Windows Batchers out there?
OK - So, since the vbscript above only works when the batch file is closed, how about closing & reopening it?    

Frame of thought:

@ECHO OFF
GOTO NEXT
:NEXT
start /wait runprogram.msc /noreboot
REM PASS A RESTART REQUIRED? VARIABLE TO AN EXTERNAL SCRIPT
REM CALL External Script
REM The external script will gracefully end the batch file that called it
REM then run the vbscript above to remove the first instance of :NEXT
REM then based on the restart required? variable that was passed earlier, either run shutdown -r
REM or CALL the go.bat script, which will GOTO the next :NEXT

then GO.bat starts again
and looks like this
@ECHO OFF
GOTO NEXT
REM this is where ":NEXT" line used to be
start /wait runprogram.msc /noreboot
REM PASS A RESTART REQUIRED? VARIABLE TO AN EXTERNAL SCRIPT (or memory?)
REM CALL External Script
REM The external script will gracefully end the batch file that called it
REM then run the vbscript above to remove the first instance of :NEXT
REM then based on the restart required? variable that was passed earlier, either run shutdown -r
REM or CALL the go.bat script, which will GOTO the next :NEXT
:NEXT
start /wait runsecondprogram.msc /noreboot
REM PASS A RESTART REQUIRED? VARIABLE TO AN EXTERNAL SCRIPT (or memory?)
REM CALL External Script
REM The external script will gracefully end the batch file that called it
REM then run the vbscript above to remove the first instance of :NEXT
REM then based on the restart required? variable that was passed earlier, either run shutdown -r
REM or CALL the go.bat script, which will GOTO the next :NEXT

etc... etc...   am I asking for something totally impossible?
I AM SO SO SO CLOSE PEOPLE... I HOPE SOMEONE WILL GO OVER MY SCRIPT AND TELL ME WHY IT"S THROWING AN ERROR ON THE LAST RUN...  IT SEEMS TO WANT TO LOOP AS OPPOSED TO JUST EXIT THE SCRIPT...  

The erorr message I get after running go.bat batch script 5 times is:
The batch file cannot be found .
'O' is not recognized as an inernal or external command,
operable program or batch file.
... and then it asks to Press any key to continue (meaning it's running the Pause command) and after that, randomly pause etc...

Test Environment:
Files in C:\testing:
go.bat - main batch script that makes it all happen
find.bat - batch to call on blunttony's vbs script to search for the first instance of :NEXXT
find2.bat - the last batch that calls on blunttony's vbs script  for the last known instance of :NEXXT.  This script also renames find.bat to find.old - so that the go.bat script doesn't look for the NEXXT label
find.vbs - bluntTony's (thanks Tony!) script that looks for the first instance of :NEXXT and removes the whole line

I will paste all of the code I am using... Perhaps something obvious sticks out, that I can correct, so that the go.bat file simply closes without looping?  

Once you have these in place, make a copy of go.bat in the same drectory (because it'll remove :NEXXT labels) and run it from c:\testing.  You'll see what I mean.  Before running the second test, rename your copy to go.bat, make a copy of it, then rename find.old to find.bat.  

Anyone out there who can help me figure this out??? I burned the whole weekend working on this ... lol

****** GO.BAT CONTAINS:
@ECHO OFF
TITLE TESTING
CLS 
ECHO MAIN PROGRAM
IF EXIST C:\testing\find.bat GOTO NEXXT
GOTO END
:NEXXT
CLS
ECHO Installing Program #1
PAUSE
CALL find.bat && GOTO :EOF
:NEXXT
CLS
ECHO Installing Program #2
PAUSE
CALL find.bat && GOTO :EOF
:NEXXT
CLS
ECHO Installing Program #3
PAUSE
CALL find.bat && GOTO :EOF
:NEXXT
CLS
ECHO Installing Program #4
PAUSE
CALL find2.bat && GOTO :EOF
:END
CLS
ECHO Good Job!
ECHO.
ECHO There are no more NEXXT LABLES in the go.bat file!
ping -n 5 localhost >nul
:EOF
 
*********FIND.BAT CONTAINS:
START /wait cscript c:\testing\find.vbs /l:":NEXXT" /f:"c:\testing\go.bat"
CALL C:\testing\go.bat
 
**********FIND2.BAT CONTAINS:
START /wait cscript c:\testing\find.vbs /l:":NEXXT" /f:"c:\testing\go.bat"
REN C:\testing\find.bat find.old
CALL C:\testing\go.bat && GOTO :EOF
 
*********FIND.VBS CONTAINS:
Set Fso = CreateObject("Scripting.FileSystemObject") 
lookFor = WScript.Arguments.Named("l")
txtFile = WScript.Arguments.Named("f")
tempFile = Replace(txtFile,Right(txtFile,3),"$$$")
Set objTxt = Fso.OpenTextFile(txtFile,1)
fso.CreateTextFile(tempFile)
Set ObjTemp = Fso.OpenTextFile(tempFile,2)
While Not objTxt.AtEndOfStream
	strLine = objTxt.ReadLine
	If InStr(strLine,LookFor) = 0 Then ObjTemp.WriteLine(strLine) Else objTemp.Write(objTxt.ReadAll)
Wend
objTxt.Close
ObjTemp.Close
fso.DeleteFile(txtFile)
fso.MoveFile tempFile, txtFile

Open in new window

I think this is related to the VBS script... there needs to be some cleanup routine performed every time that the VBS runs... maybe some sort of a cscript cache / memory / variable cleanup?  maybe the shell needs to be exited differently every time for find.bat, find2.bat or both?  
ASKER CERTIFIED SOLUTION
Avatar of bluntTony
bluntTony
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you want to clean the VB script up, add:
Set Fso = Nothing
Set objTxt = Nothing
Set objTemp = Nothing
To the very bottom. (I really should have added this anyway)
So you mean I don't have to close the go.bat file and the line gets removed while go.bat is running?
:START
ECHO bluntTony is the Man!
ECHO Thanks!
GOTO START
Yep, the problem was with my code - I looked at it the next day and realised the error of my ways!
Glad you got it sorted :)