Batch File - Find two lines then copy everything between those lines - batch-file

I need to parse a text file.
I want to find the firstline in the text file
: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
and find the last line
:: the last line to find
set lastLine=*******************************************************************************************************************
Then I need to export to a new file everything between those two line.
echo >> M:\TESTING\Output.txt
I'm a beginner with this and I've searched for days, but am not finding how to do this.
I looked at for loops and if statements, but I'm still puzzled.
for /f "tokens=1 delims= " %%f in (M:\TESTING\*.txt) do (
:: sets then the line variable to the line just read
set line=%%f
:: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
:: the last line to find
set lastLine=*******************************************************************************************************************
Then if %line% = %fistLine% start the export.....
Any direction will be appreciated. thanks.

#DennisvanGils' approach is a good start and will do well in many cases.
However, it will not produce an exact copy of the text file content between the given lines:
leading whitespaces (SPACE and TAB) will be removed (due to tokens=* option),
lines starting with ; will be skipped (due to the default option eol=; of for /F), and
empty lines will be skipped as well (as for /F always skips such).
To get an exact copy of the text file portion, you could use the following code snippet:
#echo off
set "INFILE=M:\TESTING\input.txt"
set "OUTFILE=M:\TESTING\output.txt"
set "FIRSTLINE=------------------------------------------------------------------------------------------------------------------"
set "LASTLINE=*******************************************************************************************************************"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%OUTFILE%" (
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(!LINE!
endlocal
) else (
endlocal
)
)
)
:CONTINUE
endlocal
Core function here is findstr, which is configured so that every line in the file is returned, prefixed with a line number and a colon :. Its output is then parsed by a for /F loop. Because of the prefix, no line appears to be empty and therefore every one is iterated by the loop. In the body of the loop, the prefix is removed by the set "LINE=!LINE:*:=!" command for each line.
The variable FLAG is used to decide whether or not the current line is to be output; if it is defined, that is, a value is assigned, the command echo !LINE! is executed; otherwise it is not. FLAG is set if the current line matches the string in %FIRSTLINE%; if the line matches the string in %LASTLINE%, a goto command is executed which breaks the loop. This means also that only the first block between %FIRSTLINE% and %LASTLINE% matches is output.
If there might occur multiple %FIRSTLINE% and %LASTLINE% matches within the text file and you want to output every block, replace the goto command line by set "FLAG=".
Note that this approach does not check whether %FIRSTLINE% occurs before %LASTLINE%, nor does it even check for existence of %LASTLINE% (all remaining lines to the end of file are output in case). If all this is important, the logic need to be improved and even a second loop will be required most likely.

What you should do in this case is use a variable like a boolean to know if you encountered the startline and endline yet, and to know if you have to output the lines.
Also, you should use setlocal ENABLEDELAYEDEXPANSION with ! instead of % so you can change variables in loops and ifs (for more information about that, see this. The usage of () after if is not needed in this case, since the if is on one line, but they make things easier to read in my opinion. If you want to output the start and endline too, switch the checks for the start and endlines.
#echo off & setlocal ENABLEDELAYEDEXPANSION
set start=0
:: the first line to find
set firstLine=------------------------------------------------------------------------------------------------------------------
:: the last line to find
set lastLine=*******************************************************************************************************************
for /F "tokens=*" %%A in (TEST.txt) do (
:: sets then the line variable to the line just read
set line=%%A
if "!line!"=="!lastLine!" (set start=0)
if !start! equ 1 (echo !line!>>TESTOUTPUT.txt)
if "!line!"=="!firstLine!" (set start=1)
)
This should do what you want. Note that when you encounter a startline a second time it starts reading again.

Related

First character disappearing from command reading [Batch file]

I'm trying to read the output of a command (which outputs into multiple lines), and use an arbitrary number of those lines. Because I know neither the number of total lines, nor the number of lines that will be used, I need to analyse and possibly use each line in a loop, which is why I have setlocal enabledelayedexpansion.
Below is a snippet of the code that shows the process of taking the command and reading each line (not using it yet, just reading it to make sure this works (which it doesn't)):
#echo off
setlocal enabledelayedexpansion
for /f "tokens=*" %%i in ('svn status') do (
echo %%i
set file=%%i
echo *!file!
)
The problem that I'm running into is that the %%i values that are being read in are not correct in the for line. The first character is missing from the first line of the input (which is important because I use the first line to decide whether or not to use that line).
The output I get from my code looks like this:
Dir0\TestDoc7.txt
? StatusFile.txt
Whereas if I run this code:
copy /y NUL StatusFile.txt >NUL
>StatusFile.txt (
svn status
)
(Which is just another way of me seeing what the real output of svn status is) I get a proper output into the text file:
! Dir0\TestDoc7.txt
? StatusFile.txt
I'm probably making a fairly clear mistake as I'm rather new to batch scripting.
Thanks in advance.
The cause is EnableDelayedExpansion which will eat the exclamation marks,
Your choice of tokens=* will also strip all leading spaces from the lines.
#echo off
for /f "tokens=1*" %%A in ('svn status') do (
if "%%A" equ "!" (
Rem do whatever
) else If "%%A" equ "?" (
Rem do something else rest of the line is in %%B
) else (
Rem no ! or ? first space sep. content is in %%A rest of the line is in %%B
)
)

BATCH: How to properly use a %% variable within a for loop

This is the very first time i tried batch scripting so please bear with me.
I just wanted to read each line of my hosts file, and replace the line if it contains/matches a substring. I've seen a lot of answered questions about substrings here but I just can't make it work by using the provided solutions.
I have this code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "hostspath=%SystemRoot%\System32\drivers\etc\hosts"
set "hostsbackuppath=c:\hosts"
>"%hostsbackuppath%.new" (
rem Parse the hosts file, skipping the already present hosts from our list.
rem Blank lines are preserved using findstr trick.
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set str1=%%b
if not x!str1:mydomainname=!==x!str1! (
rem Match found, replace this line.
echo "match!"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
I was trying out this solution to check for substring match among other else: Batch file: Find if substring is in string (not in a file)
Can someone help me? Thanks
SETLOCAL ENABLEDELAYEDEXPANSION
set "matched=true"
>"%hostsbackuppath%.new" (
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set "str1=%%b"
if not "!str1:mydomainname=!"=="!str1!" (
rem Match found, replace this line.
echo "match at %%b in line %%a"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
Hooley-dooley! Someone needs to learn to name variables appropriately.
First, you need to use setlocal enabledelayedexpansion - please see a thousand-and-one SO articles about delayed expansion.
Since str1 is varied within the loop, you need to use setlocal enabledelayedexpansion and !var! to access the varying value of var as %var% is the value at the time the for was encountered.
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. set /a can safely be used "quoteless".
FOr the same reason, quoting each side of a comparison is preferred as it makes a single token of a string containing separators like spaces.
Then you have a comment "match found" after which you set matched to false ?? Therefore you need to initialise match (to true)
Now quite what you want to do is obscure. On re-reading, you probably want to set "matched=true" as the first line within the loop, not outside as I have it, so that the value is re-set to true for each line found and then set to false if a match is found.
All this negative logic is insane. I need a strong cup of coffee.

print specific lines from a batch file

I am trying to print Line 4, Col 21-50 out of a text file, can this be simply done under Windows somehow? I've been trying to do this:
FOR /F "usebackq tokens=1 delims=-" %G IN (%COMPUTERNAME%.txt) DO ECHO %G
This is just working out terribly. Can't I just print a specific set of lines?
I need this script to be run on multiple computers, ideally I'd like to convert it to a variable for use with slmgr -ipk, maybe someone has a better suggestion?
Contents of text file (I want the XXXXX-XXXXX-XXXXX-XXXXX-XXXXX portion):
==================================================
Product Name : Windows 7 Professional
Product ID : 00371-OEM-9044632-95844
Product Key : XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
Installation Folder : C:\Windows
Service Pack : Service Pack 1
Computer Name : LIBRA
Modified Time : 6/4/2015 7:26:54 PM
==================================================
if you want only the "Product Key" line you can try with
type %COMPUTERNAME%.txt|find /i "Product Key"
or
for /f "tokens=2 delims=:" %%# in (' type %COMPUTERNAME%.txt^|find /i "Product Key"') do echo %%#
For the task at hand, npocmaka's answer is the best suitable approach, as it does not insist on a fixed position of the string to extract from the file.
However, I want to provide a variant that sticks to a certain position.
The following code extracts the string placed at columns 21 to 50 in line 4 of file list.txt (the result is echoed (enclosed in "") and stored in variable LINE_TXT (without ""):
#echo off
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" "list.txt"'
) do (
if %%L equ 4 (
set "LINE_TXT=%%M"
goto :NEXT
)
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
The goto :NEXT command terminates the for /F loop at the given line; this is not mandatory but will improve performance for huge files (as long as the given line number is quite small).
To be more flexible, the following code can be used (define the string position in the initial set block):
#echo off
rem Define the string position here:
set FILE_TXT="list.txt"
set LINE_NUM=4
set COL_FROM=21
set COL_UPTO=50
setlocal EnableDelayedExpansion
set /A COL_UPTO-=COL_FROM
set /A COL_FROM-=1
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" %FILE_TXT%'
) do (
if %%L equ %LINE_NUM% (
set "LINE_TXT=%%M"
if defined LINE_TXT (
set "LINE_TXT=!LINE_TXT:~%COL_FROM%,%COL_UPTO%!"
)
goto :NEXT
)
)
:NEXT
endlocal & set "LINE_TXT=%LINE_TXT%"
echo."%LINE_TXT%"
Both of the above code snippets rely on the output of findstr /N /R ".*", which returns every line that matches the regular expression .*, meaning zero or more characters, which in turn is actually true for every line in the file; however, the switch /N defines to prefix each line with its line number, which I extract and compare with the originally defined one.
Here is another variant which uses for /F to directly loop through the content (lines) of the given text file, without using findstr:
#echo off
for /F "usebackq skip=3 eol== delims=" %%L in (
"list.txt"
) do (
set "LINE_TXT=%%L"
goto :NEXT
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
This method has got the better performance, because there is the skip option which skips parsing of and iterating through all lines (1 to 3) before the line of interest (4), opposed to the findstring variant.
However, there is one disadvantage:
for /F features an eol option which defines a character interpreted as line comment (and defaults to ;); there is no way to switch this option off as long as delims= defines no delimiters (last position in option string), which is mandatory here to return the line as is; so you have to find a character that does not appear as the first one in any line (I defined = here because your sample text file uses this as header/footer character only).
To extract a string from line 1, remove the skip option as skip=0 results in a syntax error.
Note that goto :NEXT is required here; otherwise, the last (non-empty) line of the file is extracted.
Although for /F does not iterate any empty lines in the file, this is no problem here as the skip option does not check the line content and skip over empty lines as well.
Finally, here is one more approach using more +3 where no text parsing is done. However, a temporary file is needed here to pass the text of the desired line to the variable LINE_TXT:
#echo off
set LINE_TXT=
more +3 "list.txt" > "list.tmp"
set /P LINE_TXT= < "list.tmp"
del /Q "list.tmp"
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
exit /B 0
This method avoids for /F and therefore the problem with the unwanted eol option as mentioned in the above solution. But this does not handle tabs correctly as more substitutes them with spaces (8 indent spaces as per default and configurable by the /Tn switch where n is the number of spaces).

Batch File: How to read only sections in a INI file

I am very much a novice at Batch Scripting and i'm trying to write a simple script to READ from an INI file based on the Parameters that is passed when the batch file is called.
This is an example for what the INI file would look like:
[SETTING1]
Value1=Key1
Value2=Key2
Value3=Key3
[SETTING2]
Value1=Key1
Value2=Key2
Value3=Key3
[SETTING3]
Value1=Key1
Value2=Key2
Value3=Key3
I am running into a problem when it comes to reading ONLY the section that is called. It will read from any section that matches the "Value" and "Key" and i don't know how to limit it to only read the section with the settings.
The file is being called with this Parameter: run.bat run.ini setting2. My code below is what I have so far and I feel as if i have officially hit a wall. Can anyone help with this? Any help would greatly be appreciated.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET INIFile="%~f1"
for /f "usebackq delims=" %%a in (!INIFile!) do (
if %%a==[%2] (
SET yesMatch=%%a
for /f "tokens=1,2 delims==" %%a in (!yesMatch!) do (
if %%a==Value1 set Key1=%%b
if %%a==Value2 set Key2=%%b
if %%a==Value3 set Key3=%%b
)
ECHO !yesMatch!
ECHO !Key1!
ECHO !Key2!
ECHO !Key3!
pause
)
)
pause
exit /b
#ECHO OFF
SETLOCAL
SET INIFile="%~f1"
SET "FLAG="
for /f "usebackq tokens=1,*eol=|delims==" %%a in (%INIFile%) do (
IF "%%b"=="" (
REM No "=" so section
IF /i "%%a"=="[%2]" (SET flag=Y) ELSE (SET "flag=")
) ELSE IF defined flag (
REM data line - only if FLAG is defined
REM set values defined
SET "%%a=%%b"
REM pick particular values
if /i "%%a"=="Value1" set "Key1=%%b"
if /i "%%a"=="Value2" set "Key2=%%b"
if /i "%%a"=="Value3" set "Key3=%%b"
)
)
SET key
SET val
GOTO :EOF
Here's a way to get your values.
The data in the file is either [section] or name=value so settling delims to = will assign either section-only to %%a or name to %%a and value to %%b.
The flag is only set (defined) after its appropriate section is encountered, and cleared on the next section. Only of it is defined will the assignment take place.
The advantage of the simple set %%a=%%b is that it results in setting whatever values are defined in the section - no changes to the code need to take place if new values are added. Your original version has the advantage of picking particular values and setting only those. You pays your money, you takes your choice.
Note that the /i switch on an if statement makes the comparison case-insensitive.
Nota also the use of set "value=string" which ensures that trailing spaces on a line are not included in the value assigned.
edit : By default, the end of line character is ; so any line starting ; is ignored by for/f. The consequence is that the values for the ;-commented-out section would override the values set for the previous section.
Setting eol to | should cure the problem. It really doesn't matter what eol is set to; it's exactly one character which may not appear anywhere in the INI-file (else that line would appear truncated.)
It is possible, if necessary, to set eol to control-Z but selecting an unused character is easier...
Consequently, a one-line change - the eol parameter is included in the for /f options.

Multiline text file, how to put into an environment variable

i have a file called file.txt which contains:
this is line one ^
this is line two ^
this is the last line
how can i put that into an env var?
i can do this from a batch file:
test.bat
set LF=^
[blank line]
[blank line]
rem two blank lines needed above
set multi=Line 1!LF!Line 2!LF!Last line
echo !multi!
this outputs three lines:
Line 1
Line 2
Last line
so how can i get file.txt into envvar inside a batch file?
As dbenham said, it can be done also with for/f but it's a bit more complicated.
The simple 80% solution is
setlocal EnableDelayedExpansion
set "var="
set LF=^
rem *** Two empty lines are required for the linefeed
FOR /F "delims=" %%a in (myFile.txt) do (
set "var=!var!!LF!%%a"
)
echo !var!
But it fails with:
- If a line is blank it will be skipped
- If a line begins with ; the EOL-character
- If a line contains ! (and carets)
But then you could use a bit more complex solution
#echo off
SETLOCAL DisableDelayedExpansion
set "all="
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ aux1.txt"`) do (
set "line=%%a"
SETLOCAL EnableDelayedExpansion
set "line=!line:#=#S!"
set "line=!line:*:=!"
for /F "delims=" %%p in ("!all!#L!line!") do (
ENDLOCAL
set "all=%%p"
)
)
SETLOCAL EnableDelayedExpansion
if defined all (
set "all=!all:~2!"
set ^"all=!all:#L=^
!"
set "all=!all:#S=#!"
)
echo !all!
What the code do?
First, the findstr /n ^^ will prepend each line with a line number and a colon, like
1:My first Line
2:; beginning with a semicolon
3:
4:there was an empty line
This solves the problem of empty lines and also the standard EOL-character ; can be ignored.
To get the content of the line, the value is set to a variable while delayed expansion is disabled, this solves the problem with ! and ^ characters.
To remove the line number and the colon, the delayed expansion will be enabled (no, a delim of : can't solve it).
Then all # are replaced with #S, this will be done first, as after the prefix removing the line could be empty and the replacement would fail.
But why I replace it?
That's because I can't insert the linefeeds here, as the following FOR/F would fail with embedded linefeeds,
so I only add linefeed marker (in this case I use #L), but the content of the file could contain also a #L, but by replacing all # with #S all markers are unique.
After the marker, there is the problem to close/disable the delayed expansion with an endlocal, but preserve the content of the modified all and line variable.
This is done with the FOR/F-endlocal trick, as the %%p can transport content behind the endlocal barrier.
Then after reading the complete file, I check if the all is defined, as it would be empty for an empty file.
Then the first linefeed marker #L will be removed, and all other markers are replaced with a real linefeed character.
Then the sharp safer #S will be reverted to #.
That's all, so even this solution is obviously...
You were almost there. You need to read each line of text and then append the line plus a line feed to the variable.
FOR /F could be used, but it doesn't play well with delayed expansion if the content contains ! characters. It is also awkward to preserve blank lines and awkward to disable the EOL option.
A simpler solution is to use SET /P to read the lines. The limitations with this technique are:
1) It trims trailing control characters from each line
2) The file must use Windows standard line terminators of carriage return, line feed. It will not work with Unix style line feed.
3) It is limited to reading 1023 bytes per line (not including the line terminator characters)
Bear in mind that an environment variable can only hold a little less than 8 kbytes of data. So you are limited to only loading a very small file into a variable with this technique.
#echo off
setlocal enableDelayedExpansion
set LF=^
:: Two blank lines above needed for definition of LF - do not remove
set file="test.txt"
set "var="
for /f %%N in ('find /c /v "" ^<%file%') do set lineCnt=%%N
<%file% (
for /l %%N in (1 1 %lineCnt%) do (
set "ln="
set /p "ln="
set "var=!var!!ln!!lf!"
)
)
set var

Resources