extracting specified match from find/findstr results - batch-file

so this is related to
compare #defines in two C files using a batch file
but different:
System is Win 7(no Linux commands).
I have 2 files with content like
Fil1.c
#define V1 6
...
int Var[V1] ;
******************************
Fil2.c
ifdef USE_V1
#define V1 6
#else
#define V1 5
#endif
I need to compare V1 with V2 in a batch file, and throw an error if they don't match.
The solution
for /f "tokens=3" %%A in ('find "#define V1" "Fil1.c"') DO (
set Var1=%%A)
for /f "tokens=3" %%A in ('find "#define V1" "Fil2.c") DO (
set Var2=%%A)
if "%VAR1%" neq "%VAR2%" goto Error2
works well when USE_V1 preprocessor is set to 0.However, when set to 1, V1 in File2.c still reads value 5 ,as findtsr finds 2 occurrences of #define , and gets the last one.
Is there a way for findstr to read only 1st or second occurrence of #define ( which I can specify based on GOTO, wherein I read USE_V1 defined in VS project xml and decide which findstr I need to execute, that which reads first occurence or that which reads second one.)
For example, I can try :
TYPE nul >%tmpPth%
findstr /N /C:"#define V1" "Fil2.c" > %tmpPth%
writes following to tmp.txt
46:#define V1 6
49:#define V1 5
but then, I should be able to read token 3 using find/findstr either from first line or second line.
I have seen solutions on stack overflow ([Batch file to output last line of findstr2) that read/print only last occurrence of
search string but I need solution where I can probably choose which line out of the matched results I need to re-run the search for, and not just print but extract tokens out of the line result.
Update: I was able to do following:
set USE_V1="0"
for /f "delims= " %%A in ('findstr /C:"USE_V1" %VCProjPath%') DO (
set USE_V1="1")
set Var3="0"
for /f "tokens=3" %%A in ('find "#define V1" "Fil1.c"') DO (
set Var1=%%A)
if %USE_V1% equ "1" goto USE_V1_USED
for /f "tokens=3" %%A in ('find "#define V1" "Fil2.c"') DO (
set Var2=%%A)
goto MOVEON
:USE_V1_USED
TYPE nul >%tmpPth1%
findstr /C:"#define V1" "Fil2.c" > %tmpPth1%
for /f "tokens=3 delims= " %%A in ('findstr /C:"#define V1" %tmpPth1%') Do (
set Var2=%%A
if "%VAR2%" neq "%VAR3%" goto MOVEON
)
:MOVEON
if "%VAR1%" neq "%VAR2%" goto Error0
The problem i face is that supposing #define V1 match in File1.c and File2.c and
findstr /C:"#define V1" "Fil2.c" > %tmpPth1%
writes to tmp file as
#define NUMATTR1MEMS 6
#define NUMATTR1MEMS 5
which when read using
for /f "tokens=3 delims= " %%A in ('findstr /C:"#define V1" %tmpPth1%') Do (
set Var2=%%A
if "%VAR2%" neq "%VAR3%" goto MOVEON
)
give op with %VAR2% from both files as
As seen, the second value corresponding to first line read from temp file has extra blank spaces ,so, even though values match numerically, the command to match these fails.
So, is there a way to redirect findstr op to text file without extra blanks at line endings?
Thanks
sedy

Let us go through the quite huge amount of tasks step by step...
At first, let's concentrate on retrieving the definition of USE_V1. As far as I understand, you have a project definition file that contains USE_V1; if USE_V1 is present in that file literally, it is considered as defined, otherwise not.
The following code snippet seeks USE_V1 and sets %VVALGLOB% if found (I place a couple of set statements for initially setting up everything at one place for convenience):
#echo off
rem global project definitions
set FILEPROJ="\path\to\project\file"
set VNAMGLOB=USE_V1
set VVALGLOB=
rem seek %VNAMGLOB% in project file and set %VVALGLOB% if found
for /F %%A in ('findstr /L /I "%VNAMGLOB%" %FILEPROJ%') do (
set VVALGLOB=1
)
The next thing is to extract the value of variable V1 from Fil1.c. Here I assume that always the first occurrence of the #define directive is the one to regard:
rem definition of data of file 1
set FILE1="Fil1.c"
set VNAM1=V1
set VVAL1=
rem processing file 1, always take FIRST occurrence of "#define"
for /F "tokens=3" %%A in ('findstr /R /I /C:"^ *#define *%VAR1%" %FILE1%') do (
set /A "VVAL1=%%A"
goto :CONT1
)
:CONT1
I used set /A rather than set here to treat the gathered value as a number.
Now let's extract the value of V1 from Fil2.c conditionally: Here I check whether or not %VVALGLOB% is defined (remember it is when USE_V1 has been defined); if it is, the first occurrence of the #define directive is taken; otherwise, the last one is checked:
rem definition of data of file 2
set FILE2="Fil2.c"
set VNAM2=V1
set VVAL2=
rem processing file 2, take FIRST OR LAST occurrence of "#define" conditionally
for /F "tokens=3" %%A in ('findstr /R /I /C:"^ *#define *%VAR2%" %FILE2%') do (
set /A "VVAL2=%%A"
if defined VVALGLOB goto :CONT2
)
:CONT2
Alternatively, if only the first or second occurrence of #define is relevant, which is the case when there are more than two such directives, the for /F portion of the code must be replaced by the following lines:
rem processing file 2, take FIRST OR SECOND occurrence of "#define" conditionally
for /F "tokens=3" %%A in ('findstr /R /I /C:"^ *#define *%VAR2%" %FILE2%') do (
if defined VVAL2 (
set "VVAL2=%%A"
goto :CONT2
)
set /A "VVAL2=%%A"
if defined VVALGLOB goto :CONT2
)
:CONT2
Having extracted the correct values from the two files, we need to compare them. For this, I assumed that they must not equal zero to be considered as valid. If one or both are zero, or if they are not equal, a message is thrown and the batch script is exited; otherwise, the batch script continues executing:
rem compare found values for equality in a NUMERICAL manner (both != 0)
if %VVAL1% neq 0 if %VVAL2% neq 0 if %VVAL1% equ %VVAL2% goto :MOVEON
echo They don't match! & exit /B
:MOVEON
rem ...
Since there are no double-quotes around all the values, a numerical comparison is done rather than a string comparison. Hence any trailing spaces do not matter.
Notes:
I added the /I switch to all findstr occurrences in order to do case-insensitive searches. However, if all the variable names like V1 are case-sensitive, just remove it.
For searching the #define directive, I established a regular expression rather than a literal search string, telling findstr to allow leading spaces and more than one space between the keyword and the variable name.
As already mentioned in the above I treat variable (V1) values of zero as invalid to avoid trouble with values other than integers. For instance, if V1 is a string abc in your file, the batch script reads 0.

Related

Batch scripting - parsing file line by line and finding string

I'm trying to parse a .txt file using batch script, line by line, untill I find "arg =" string and then get the following number. To put it into context, I'm trying to parse this gdb.txt file
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x00007c2c in ?? ()
Loading section .sec1, size 0x20000 lma 0x0
Start address 0x8560, load size 131072
Transfer rate: 103 KB/sec, 1110 bytes/write.
Command Executed successfully: semihosting enable
Breakpoint 1 at 0x790a: file C:\LMA\ws_new\wam_sdk1886.31.001.1C_ver1\src\sdk\wam\bsp\detail/exit.c, line 21.
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 1, exit (arg=0) at C:\LMA\ws_new\wam_sdk1886.31.001.1C_ver1\src\sdk\wam\bsp\detail/exit.c:21
21 volatile std::uint8_t a = 0;
arg = 0
[Inferior 1 (Remote target) detached]
I've come up with these few lines of batch script:
#echo off
for /f delims^=^ eol^= %%A in (gdb.txt) Do (
echo %%A
findstr /c:"arg =" %%A>nul 2>nul
echo %errorlevel%
)
I would like the script to recognize the line with "arg =" so I can read 0 afterwards.
However this script seems not to be able to recognize "arg =" and always prints %errorlevel% as 1.
What am I missing here?
It's much easier to filter the file for the wanted line instead of searching through each line (and much faster, especially with big files):
for /f "tokens=2 delims== " %%A in ('type gdb.txt^|findstr /bic:"arg = "') Do set "var=%%A"
echo arg is %var%.
Note: should there be more than one matching line, this will give you the last result.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q64803488.txt"
SET "arg="
FOR /f "usebackq tokens=1-3" %%a IN ("%filename1%") DO (
IF /i "%%a"=="arg" IF "%%b"=="=" SET "arg=%%c"
)
ECHO arg found was "%arg%"
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q64803488.txt containing your data for my testing.
The usebackq option is only required because I chose to add quotes around the source filename.
Set arg to empty to ensure it isn't already set.
For each line in the file, tokenise using the default delimiter set (which includes space) and select the first 3 tokens. If the first (%%a) is arg (/i to make case-insensitive) and the second in %%b is = then assign the third (%%c) to arg.
If I was doing this in a batch-file, I'd do it like this:
#For /F Tokens^=* %%G In ('%SystemRoot%\System32\findstr.exe /BIRC:"arg = [0123456789]" "gdb.txt" 2^> NUL') Do #Set /A %%G 2> NUL
#Set arg & Pause
The second line is there just to show you the results. You should replace that with your own code.
The first line could possibly be made shorter too, (although I wouldn't recommend it)!
#For /F Tokens^=* %%G In ('FindStr /BIRC:"arg = [0-9]" gdb.txt')Do #Set /A %%G

Remove lines from hosts file with batch if already exists

I have a batch script to add new entries based on the given IP address:
#echo off
SET NEWLINE=^& echo.
set /p ipAddress=What is the IPv4 address of the instance?
FIND /C /I "storage.app.lab" %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 ECHO %NEWLINE%^%ipAddress% storage.app.lab>>%WINDIR%\System32\drivers\etc\hosts
FIND /C /I "home.app.lab" %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 ECHO %NEWLINE%^%ipAddress% home.app.lab>>%WINDIR%\System32\drivers\etc\hosts
FIND /C /I "api.app.lab" %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 ECHO %NEWLINE%^%ipAddress% api.app.lab>>%WINDIR%\System32\drivers\etc\hosts
pause
However, I want to be able to overwrite existing entries with the domain name if a new ip address is entered. For example, if an entry with the domain name of "storage.app.lab" already exists, replace it with the new IP address.
How can I achieve that without using a backup hosts file?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q64587777.txt"
:: I'll just use a fixed string for the IPaddress
set "IPaddress=User-input IP address"
:: remove variables starting #
FOR /F "delims==" %%a In ('set # 2^>Nul') DO SET "%%a="
FOR /f "tokens=1*delims=:" %%a IN (
'findstr /v /N /L /C:"storage.app.lab" /C:"home.app.lab" /C:"api.app.lab" "%filename1%"'
) DO set "#%%a=%%b"
(
FOR /F "tokens=1*delims==" %%a In ('set # 2^>Nul') DO echo %%b
for %%a in ("storage.app.lab" "home.app.lab" "api.app.lab") do echo %IPaddress% %%~a
)>"%filename1%"
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q64587777.txt containing some dummy data for my testing.
The first few lines simply establish filename variables for testing, and a recognisable string to save re-entering data in testing.
The procedure will use variables named #* for temporary storage of the "other" lines in the file in question, so first clear out any variables that may exist that start #.
Then execute findstr and "print" lines that do NOT contain (/V) any of the /L literal strings provided as /c:"string-to-EXclude" and /N number thos lines with a leading serial number followed by a colon.
The for /f tokenises the line using the : separator as a delimiter and assigning the line number to %%a (token 1) and the remainder of the line (the data in question) to %%b. Set the environment variable #%%a to the lines found.
Then use the same principle on a set # list, which will list all variables starting # in the format #1=line one, delimiting on = and selecting the 2nd token, which is the line data originally read from the file.
And add the three new lines by construction.
Parenthesising the two for statements together gathers the echoed output which is then redirected to the original file, overwriting it.
Note that OP's code appended the (up to) three new lines. The requirement is (apparently) that the 3 lines will appear in the file, replacing any existing data for those three entries.

Avoid a null value in last for loop iteration?

I have a text file with one string per line (only a couple lines total). This file needs to be searched and each string stored. The script will ultimately prompt the user to choose one of the stored strings (if more than one string/line is present in the file), but the for loop is iterating an extra time when I don't want it to.
I'm using a counter to check the number of iterations, and I also read that it's probably a NL CR regex issue, but the finstr /v /r /c:"^$" in the for file-set like in this post Batch file for loop appears to be running one extra time/iteration doesn't work for me (but I probably don't understand it correctly).
The "pref" term is because the strings are to be eventually used as a prefix of files in the same folder...
#echo off
setlocal enabledelayedexpansion
set /a x=1
for /f %%a in (sys.txt) do (
set pref!x!=%%a && echo %^%pref!X!%^% && set /a x+=1
)
echo last value of x = !x!
for /L %%a in (1,1,!x!) do (
echo !pref%%a!
)
REM The rest would be to prompt user to choose one (if multiple) and
REM then use choice as a prefix with a ren %%a %prefX%%%a
If the "sys.txt" contains three lines with strings A, B, C respectively, then the output I currently get is:
pref1
pref2
pref3
last value of x = 4
A
B
C
ECHO is off.
ECHO is off. is not desired, clearly.
You just need to change your increment structure like this. (set it before each line starting from a base of 0)
#Echo Off
Setlocal EnableDelayedExpansion
Set "i=0"
For /F "UseBackQ Delims=" %%A In ("sys.txt") Do (
Set/A "i+=1"
Set "pref!i!=%%A"
Echo(pref!i!)
Echo(last value of i = %i%
For /L %%A in (1,1,%i%) Do Echo(!pref%%A!

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).

Command: for | It won't accept my variable in skip=%var% option

Well, I am kinda stuck on the following part of my code.
This program will count the number of lines in the file usernames_list.txt. On each line there is a name of a folder I want to enter. I want that program to enter each folder in my list, create a file called test_1 and then go to it's parent folder. This should be reapeated until it reaches the end of the list.
Am I doing it right? :/
For some reason the "skip" option won't accept my variable.
for /f %%C in ('Find /V /C "" ^< usernames_list.txt') do set lines=%%C
set times=0
set /A skip_value=%lines%-(%lines%-%times%)
:redo
FOR /F "skip=%skip_value%" %%b IN (usernames_list.txt) DO (
cd %%b
echo > test_1
cd ..
set /A times=%times%+1
if /i {%times%}=={%lines%} (goto continue)
goto redo
)
:continue
pause
As currently written, set /A skip_value=%lines%-(%lines%-%times%) will always evaluate to 0 because times = 0.
The SKIP value must be >= 1. Setting SKIP=0 results in a syntax error.
It seems to me your logic for computing skip_value is flawed. But even if you fix the logic, you still have to worry about values that are <= 0. I handle that situation by defining a SKIP variable with the entire option text only if the value is >= 1.
set "skip="
if %skip_value% geq 1 set "skip=skip=%skip_value%"
for /f "%skip%" %%b in (usernames_list.txt) ...
You might need additional FOR /F options. That is not a problem. For example:
for /f "%skip% delims=" ...

Resources