I'm trying to write a windows batch file that would read in a text file with certain text in it and increment some values in that file.
The text file would contain text like :
public static const COUNTER:int = 0
the batch file would then search for "COUNTER:int = 0" and increment the 0 value.
Unfortunately my knowledge of windows batch files is non-existent, so any advice or help on the matter is appreciated!
Thanks!
I'd advise against using batch files for parsing files. It just doesn't play nicely with such things.
If you are absolutely sure that your file can never contain the following characters: &, |, >, <, " then you can use a batch file. But catering for those characters is hard and and—in some cases—downright impossible.
In such cases you would be better off either using VBScript to process the file or using various UNIX tools to perform that task. This might be possible using awk.
Note that Windows 7 includes Windows PowerShell where such a task is really trivial. And it can be installed separately on Windows XP and higher.
However, I think I'd go with a VBScript solution here.
If your requirements match above constraints, you can do it with a batch file. The one below should work.
First of all, we need delayed expansion, so this has to be one of the very first lines in the batch:
setlocal enableextensions enabledelayedexpansion
You can iterate over the lines in a file using for /f:
for /f "delims=" %%x in (my_file) do call :process "%%x"
goto :eof
:process
...
goto :eof
This will call the subroutine process for each line of the file, handing over the line as argument. The delims= part specifies that we don't want tokenizing on that line. We now look at the contents of that routine.
Fist we need to know whether the line even contains the string we're looking for (Note that the loop variable, containing the line is only %%x inside of the loop, in the subroutine it becomes %1):
echo %1 | findstr "COUNTER:int" >nul 2>&1
if not errorlevel 1 (
...
) else (
echo %~1>>new_file
)
Inside, where now the ... are, we can handle that line in case it contains the search string. We first need to dissect it. The easiest way would be to split it up at the = character and then we increment the number and output everything again.
for /f "tokens=1,2 delims==" %%a in (%1) do (
set /a number=%%b+1
echo %%a= !number!>>new_file
)
So, putting it all together, it looks like this:
#echo off
setlocal enableextensions enabledelayedexpansion
del new_file
for /f "delims=" %%x in (my_file) do call :process "%%x"
goto :eof
:process
echo %1 | findstr "COUNTER:int">nul 2>&1
if not errorlevel 1 (
for /f "tokens=1,2 delims==" %%a in (%1) do (
set /a number=%%b+1
echo %%a= !number!>>new_file
)
) else (
echo %~1>>new_file
)
goto :eof
Code can be found in my SVN.
If you've got sed, you can do this:
sed "s/COUNTER:int *= *0/COUNTER = 1/" file.cs >newfile.cs
This assumes that you're only looking for a particular constant name and wanting to change '0' to '1'.
Related
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.
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 ~
In Windows batch, I have a for loop like so:
for /l %%a in (0,1,337) do (
for /F "tokens=*" %%b IN ("tile%%a.jpg") DO set size=%%~zb
if !size! GTR 0 (
echo Size is greater than 0
) ELSE (
)
)
I know this code doesn't make much sense right now, but I'm going to develop it further. I just want to know how to subtract 1 from %%a in the ELSE statement. Basically I want to be able to "redo" a loop number when the IF isn't true, if that makes sense. Thanks.
You can't modify the value of a loop variable. You can only modify the value of an environment variable.
But why using for /L %%a in (0,1,337) do at all?
Better would be for example:
#echo off
for %%A in (tile*.jpg) do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo File size of %%A is greater than 0.
)
)
This loop processes simply all tile*.jpg in current directory.
But this loop can't be used if files with 0 bytes are deleted in current directory. Processing the list of tile*.jpg files in current directory and change the files list in the same loop is no good idea because simply not working. The solution is using command DIR to get first the list of all files matching the file name pattern and next process the output of DIR line by line using FOR.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%A in ('dir /A-D /B /OS tile*.jpg 2^>nul') do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo First file with more than 0 bytes is: %%A
goto ExitLoop
)
)
:ExitLoop
endlocal
The command DIR is executed to output the list of files matching the pattern tile*.jpg with ignoring directories which by chance would be matched also by this wildcard pattern because of option /A-D in bare format (only file name) because option /B in order sorted by file size because of option /OS from smallest to largest file.
2^>nul redirects the error message output by command DIR to handle STDERR on not finding any file matching the wildcard pattern to device NUL to suppress this error message. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and interpreted as redirection operator on execution of DIR command line by FOR.
The loop is immediately exited once a file with more than 0 bytes is found as all further files have surely also more than 0 bytes.
One more loop can be used after label ExitLoop which should be renamed to something more suitable in this case for example to renumber the remaining files using command REN when first loop deletes files with 0 bytes.
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.
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
setlocal /?
See also the Microsoft article Using command redirection operators for an explanation of 2>nul.
You cannot modify the loop variable %%a. Only the loop itself can modify it.
If you want to calculate a new value you can do:
set /A NEW_VALUE=%%a-1
echo %NEW_VALUE% (prove that NewValue is now 1 smaller than %%a)
You cannot modify a for variable reference like %%a, but you can store its value into a standard environment variable (like index) and modify this. For this to work you need to enable and use delayed expansion, because the variable is modified and read within the same block of code, namely the loop body, so read it like !index!; using normal expansion like %index% returned the value present before the loop has even started:
#echo off
setlocal EnableDelayedExpansion
for /L %%a in (0,1,337) do (
set /A "index=%%a-1"
echo %%a - 1 = !index!
)
endlocal
A nice alternative that avoids need of delayed expansion is to use an embedded for /F loop that gets the output of the subtraction and iterates once only per iteration of the surrounding for /L loop, like this:
#echo off
for /L %%a in (0,1,337) do (
for /F %%b in ('set /A "%%a-1"') do (
echo %%a - 1 = %%b
)
)
This works because the for /F loop executes the set /A command in cmd context, in which it returns the resulting value -- in contrast to the aforementioned approach, where set /A is executed in batch-file context, in which it does not output anything.
I'm trying to insert a line into a file using the following code (from Write batch variable into specific line in a text file)
#echo off
setlocal enableextensions enabledelayedexpansion
set inputfile=variables.txt
set tempfile=%random%-%random%.tmp
copy /y nul %tempfile%
set line=0
for /f "delims=" %%l in (%inputfile%) do (
set /a line+=1
if !line!==4 (
echo WORDS YOU REPLACE IT WITH>>%tempfile%
) else (
echo %%l>>%tempfile%
)
)
del %inputfile%
ren %tempfile% %inputfile%
endlocal
My problem is the file has comment lines (which start with semicolons) which need to be kept
; directory during network startup. This statement must indicate a local disc
; drive on your PC and not a network disc drive.
LOCALDRIVE=C:\TEMP;
; PANELISATION PART/NET NAMING CONVENTION
; When jobs are panelised, parts/nets are renamed for each panel step by
When I run the batch file, it ignores the semicolon lines, So I only get:
LOCALDRIVE=C:\TEMP;
What do I need to do to keep the semicolon lines?
The EOL option determines what lines are to be ignored. The default value is a semicolon. If you know a character that can never appear in the first position of a line, then you can simply set EOL to that character. For example, if you know a line can't start with |, then you could use
for /f "eol=| delims=" %%l in (%inputfile%) do ...
There is an awkward syntax that disables EOL completely, and also disables DELIMS:
for /f delims^=^ eol^= %%l in (%inputfil%) do ...
Note that FOR /F always discards empty lines, so either of the above would result in:
; directory during network startup. This statement must indicate a local disc
; drive on your PC and not a network disc drive.
LOCALDRIVE=C:\TEMP;
; PANELISATION PART/NET NAMING CONVENTION
; When jobs are panelised, parts/nets are renamed for each panel step by
A trick is used if you want to preserve empty lines. Use FIND or FINDSTR to insert the line number before each line, and then use expansion find/replace to remove the line number. Now you know the line never begins with ;, so you can ignore the EOL option.
for /f "delims=" %%L in ('findstr /n "^" "%inputfile%"') do (
set "ln=%%L"
set "ln=!ln:*:=!"
REM You now have the original line, do whatever needs to be done here
)
But all of the above have a potential problem in that you have delayed expansion enabled when you expand the FOR variable, which means that any content containing ! will be corrupted. To solve this you must toggle delayed expansion on and off within the loop:
setlocal disableDelayedExpansion
...
for /f "delims=" %%L in (findstr /n "^" "%inputfile%") do (
set "ln=%%L"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
REM You now have the original line with ! preserved, do whatever needs done here
endlocal
)
Also, when ECHOing an empty line, it will print out ECHO is off unless you do something like
echo(!ln!
It takes time to open and position the write cursor to the end every time you use >> within the loop. It is faster to enclose the entire operation in one set of parentheses and redirect once. Also, you can replace the DEL and REN with a single MOVE command.
Here is a final robust script:
#echo off
setlocal disableDelayedExpansion
set "inputfile=variables.txt"
set line=0
>"%inputfile%.new" (
for /f "delims=" %%L in (findstr /n "^" "%inputfile%") do (
set "txt=%%L"
set /a line+=1
setlocal enableDelayedExpansion
set "txt=!txt:*:=!"
if !line! equ 4 (
echo New line content here
) else (
echo(!txt!
)
endlocal
)
)
move /y "%inputfile%.new" "%inputfile%" >nul
endlocal
That is an awful lot of work for such a simple task, and it requires a lot of arcane knowledge.
There is a much quicker hack that works as long as
your first 4 lines do not exceed 1021 bytes
none of your first 3 lines have trailing control characters that need to be preserved
the remaining lines do not have <tab> characters that must be preserved (MORE converts <tab> into a string of spaces.
#echo off
setlocal enableDelayedExpansion
set "inputfile=variables.txt"
>"%inputfile%.new" (
<"%inputfile%" (
for /l %%N in (1 1 3) do (
set "ln="
set /p "ln="
echo(!ln!
)
)
echo New line content here
more +4 "%inputfile%"
)
move /y "%inputfile%.new" "%inputfile%"
That is still a lot of work and arcane knowledge.
I would use my JREPL.BAT utility
Batch is really a terrible tool for text processing. That is why I developed JREPL.BAT to manipulate text using regular expressions. It is a hybrid JScript/batch script that runs natively on any Windows machine from XP onward. It is extremely versatile, robust, and fast.
A minimal amount of code is required to solve your problem with JREPL. Your problem doesn't really require the regular expression capabilities.
jrepl "^" "" /jendln "if (ln==4) $txt='New content here'" /f "variables.txt" /o -
If used within a batch script, then you must use call jrepl ... because JREPL.BAT is also a batch script.
By default, the FOR command treats ; as the end-of-line character, so all those lines that start with ; are being ignored.
Add eol= to your FOR command, like this:
for /f "eol= delims=" %%l in (%inputfile%) do (
It looks like you're echoing just the line delimiter, not the whole line:
echo %%l>>%tempfile%
I'm rusty on ms-dos scripts, so I can't give you more than that.
Task in CMD.
1) How can I compare if string is in string? I checked manual here for "Boolean Test "does string exist ?"" But I can't understand the example or it does not work for me. This piece of code, it is just a try. I try to make a string compare of filter some sting if there is a tag <a> in a line.
FOR /f "tokens=* delims= usebackq" %%c in ("%source%") DO (
echo %%c
IF %%c == "<a" (pause)
)
So while I read a file, it should be paused if there is a link on a line.
2) I have one more ask. I would need to filter the line if there is a specific file in the link, and get content of the link. My original idea was to try to use findstr with regex, but it seems not to use sub-patterns. And next problem would be how to get the result to variable.
set "pdf=0_1_en.pdf"
type "%source%" | grep "%pdf%" | findstr /r /c:"%pdf%.*>(.*).*</a>"
So in summary, I want to go through file and if there is a link like this: REPAIRED: *
<b>GEN 0.1 Preface</b>
I forgot to style this as a code, so the inside of code was not displayed. Sorry.
Warnning: we don't know the path, only the basic filename.
Get the title GEN 0.1 Preface. But you should know, that there are also similar links with same link, which contain image, not a text inside a tag.
Code according Aacini to be changed a little bit:
#echo off
setlocal EnableDelayedExpansion
set "source=GEN 0 GENERAL.html"
set "pdf=0_1_en.pdf"
echo In file:%source%
echo Look for anchor:%pdf%
rem Process each line in %source% file:
for /F "usebackq delims=" %%c in ("%source%") do (
set "line=%%c"
rem Test if the line contain a "tag" that start with "<a" string:
set "tag=!line:*<a=!"
if not "!tag!" == "!line!" (
rem Take the string in tag that end in ">"
for /F "delims=^>" %%a in ("!tag!") do set "link=%%a"
echo Link found: !link!
if "!link!" == "GEN 0.1 Preface" echo Seeked link found
)
)
pause
Still not finished
Although your question is extensive it does not provide to much details, so I assumed several points because I don't know too much about .PDF files, tags, etc.
#echo off
setlocal EnableDelayedExpansion
set "source=GEN 0 GENERAL.html"
set "pdf=0_1_en.pdf"
echo In file: "%source%"
echo Look for anchor: "%pdf%"
rem Process each line in %source% file:
for /F "usebackq delims=" %%c in ("%source%") do (
set "line=%%c"
rem Test if the line contain "<a>" tag:
set "tag=!line:*<a>=!"
if not "!tag!" == "!line!" (
rem Test if "<a>" tag contain the anchor pdf file:
if not "!tag:%pdf%=!" == "!tag!" (
rem Get the value of "<b>" sub-tag
set "tag=!tag:<b>=$!"
set "tag=!tag:</b>=$!"
for /F "tokens=2 delims=$" %%b in ("!tag!") do set title=%%b
echo Title found: "!title!"
)
)
)
pause
Any missing point can be added or fixed, if you give me precise details about them.
EDIT: I fixed the program above after last indications from the OP. I used $ character to get the Title value; if this character may exist in original Tag, it must be changed by another unused one.
I tested this program with this "GEN 0 GENERAL.html" example file:
Line one
<a>href="/Dokumenter/EK_GEN_0_X_en.pdf" class="uline"><b>GEN 0.X Preface</b></a>
Line three
<a>href="/Dokumenter/EK_GEN_0_1_en.pdf" class="uline"><b>GEN 0.1 Preface</b></a>
Line five
and get this result:
In file: "GEN 0 GENERAL.html"
Look for anchor: "0_1_en.pdf"
Title found: "GEN 0.1 Preface"
EDIT: New faster method added
There is a simpler and faster method to solve this problem that, however, may fail if a line contains more than one tag:
#echo off
setlocal EnableDelayedExpansion
set "source=GEN 0 GENERAL.html"
set "pdf=0_1_en.pdf"
echo In file: "%source%"
echo Look for anchor: "%pdf%"
for /F "delims=" %%c in ('findstr /C:"<a>" "%source%" ^| findstr /C:"%pdf%"') do (
set "tag=%%c"
rem Get the value of "<b>" sub-tag
set "tag=!tag:<b>=$!"
set "tag=!tag:</b>=$!"
for /F "tokens=2 delims=$" %%b in ("!tag!") do set title=%%b
echo Title found: "!title!"
)
pause
First, one important question: does this really have to be implemented via a CMD script? Would you be able to go with VBScript, PowerShell, C#, or some other scripting/programming language? CMD is a notoriously painful scripting environment.
Secondly, I'm not sure if this answers your question--it's a bit unclear--but here's a quick trick you can use to see in CMD to see if a given string contains another substring:
setlocal enableextensions enabledelayedexpansion
set PATTERN=somepattern
for /f "delims=" %%f in (somefile.txt) do (
set CURRENT_LINE=%%f
if "!CURRENT_LINE:%PATTERN%=!" neq "!TEMP!" (
echo Found pattern in line: %%f
)
)
The idea is that you try to perform string replacement and see if anything was changed. This is certainly a hack, and it would be preferable if you could instead use a tool like findstr or grep, but if you're limited in your options, something like the above should work.
NOTE: I haven't actually run the above script excerpt, so let me know if you have any difficulty with it.
I have modified the way to do it. I realized that it is better to find name of pdf document first. This is my almost completed solution, but I ask you if you could help me with the last point. The last replacing statement does not work because I need to remove closing tag b. Just to get the title.
#echo off
setlocal EnableDelayedExpansion
set "source=GEN 0 GENERAL.html"
set "pdf=0_1_en.pdf"
echo In file:%source%
echo Look for anchor:%pdf%
rem Process each line in %source% file:
for /F "usebackq delims=" %%c in ("%source%") do (
set "line=%%c"
REM Test if the line contains pdf file I look for:
SET "pdfline=!line:%pdf%=!"
if not "!pdfline!" == "!line!" (
cls
echo Line: !line!
REM Test if the pdfline contains tag b
SET "tagline=!pdfline:*><b>=!"
if not "!tagline!" == "!pdfline!" (
cls
echo ACTUAL LINE: !tagline!
REM Remove closing tag b
SET "title=!tagline:</b*=!"
echo TITLE: !title!
pause
)
)
)
pause
BTW:
The html page I work with is here.
So I ask you to help complete/repair line SET "title=!tagline:</b*=!"
I want to nest a for loop inside a batch file to delete carriage return.
I tried it like you can see below but it does not work.
#echo off
setLocal EnableDelayedExpansion
for /f "tokens=* delims= " %%a in (Listfile.txt) do (
set /a N+=1
set v!N!=%%a
)
for /l %%i in (1, 1, %N%) do (
echo !v%%i!
for /r "tokens=* delims=" %%i in (windows.cpp) do (
echo %%i >> Linux11.cpp
)
)
pause
Here I want to check with windows.cpp. If its working I like to change windows .cpp with !v%%i!
You cannot do this in a batch file. You have no way of addressing or writing arbitrary characters. Every tool on Windows normally makes sure to output Windows line breaks (i.e. CR+LF). Some can read Unix-style line breaks just fine, which is why you can easily convert from them. But to them isn't possible.
Also as a word of caution: Source code files often contain blank lines (at least mine do) that are for readability. for /f skips empty lines which is why you're mangling the files for your human readers there. Please don't do that.
As for your question: When nesting two loops you have to make sure that they don't use the same loop variable. Show me a language where code like you wrote actually works.
Something like
for /l %%i in (1, 1, %N%) do (
echo !v%%i!
for /f "tokens=* delims=" %%l in ("!v%%i!") do (
rem do whatever you want to do with the lines
)
)
should probably work better (you missed the final closing parenthesis as well). Thing to remember: If you want to use a certain variable instead of a fixed file name it surely helps replacing that fixed file name by that variable.
It would be probably easiest to use some unix2dos/dos2unix converter to do that or some win32 flavor of sed.
The intrinsic issue of your code is already addressed by another answer, hence I am going to focus on the main task you are trying to accomplish, namely converting DOS/Windows-style end-of-line markers (or line-breaks) to Unix-style ones.
Doing this is very tricky in a batch file, but give the following script a try. Supposing it is called convert.bat, and the original text file is named convert.txt, run the script using the following command line:
convert.bat "convert.txt" LF
The name of the returned file will get the original file name with _converted_EOL appended. The second argument LF specifies Unix-style line-breaks; omitting it will return DOS/Windows-style ones.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem check whether or not an existing file is given as the first argument
>&2 (
if "%~1"=="" (
echo No file specified.
exit /B 2
) else if not exist "%~1" (
echo File "%~1" not found.
exit /B 1
)
)
rem get carriage-return character
for /F %%A in ('copy /Z "%~0" nul') do set "CR=%%A"
rem get line-feed character (the two empty lines afterwards are mandatory!)
(set ^"LF=^
%= blank line =%
^")
rem check which line-break is given by the second argument
rem (`CR` - carriage return (Mac); `LF` - line feed (Unix);
rem anything else or nothing - CR+LF (Windows, default))
setlocal EnableDelayedexpansion
set "BR=!CR!!LF!"
if /I "%~2"=="CR" set "BR=!CR!" & (>&2 echo CR not supported.) & exit /B 3
if /I "%~2"=="LF" set "BR=!LF!"
rem convert line-breaks; append `_converted_EOL` to file name
setlocal DisableDelayedExpansion
> "%~n1_converted_EOL%~x1" (
for /F delims^=^ eol^= %%L in ('
findstr /N /R "^" "%~1"
') do (
set "LINE=%%L"
rem firstly, precede every line with a dummy character (`:`) and
rem append the specified line-break in order to avoid the loss of
rem leading white-spaces or trouble with leading equal-to signs,
rem all caused by `set /P`, which is needed here to return the
rem line without a trailing DOS/Windows-style line-break (opposed
rem to `echo`); then, let `pause` strip off that character;
rem lastly, let `findstr` return the remainder;
rem (the `rem` suffix is just there to fix syntax highlighting)
cmd /V /C ^< nul set /P #="!LINE:*:=:!!BR!" | (> nul pause & findstr "^") & rem/ "^"
)
)
endlocal
endlocal
endlocal
exit /B
The following restrictions apply:
no line must be longer than about 8190 characters (this is a general limitation of batch files);
the file must not contain any null-bytes (well, a normal text file should not hold such, but Unicode-encoded do);
the last line of the returned file will always be terminated by a line-break, even if the respective original line is not;
And here is another solution for line-break conversions: Convert all CR to CRLF in text file using CMD