I want to store the result of a command in a variable in a for loop in a batch script. I want to loop through a series of .sql files, count how many lines each file consists of and sum up the line counts.
Here is my attempt so far:
#echo off
setlocal ENABLEDELAYEDEXPANSION
set /a count=0
set /a t=0
for /f %%a in ('dir /b *.sql') do (
#echo %%a <- THIS PRINTS THE FILE NAME
#set t=findstr /r /n "^" %%a | find /C ":" <- THIS IS INCORRECT
echo %t% <- I WANT TO PRINT THE LINE COUNT FOR THE FILE
#set /a count+=%t% <- INCREASE THE COUNTER
)
echo %count% <- PRINT TOTAL LINE COUNT
When I run
findstr /r /n "^" *.sql | find /C ":"
in a command window it works, and I know I could use that for the end objective, but this question is about the variable assignment.
Where is my mistake? The variable t is assigned the value 0 all the time.
UPDATE: (Still not working)
#echo off
setlocal ENABLEDELAYEDEXPANSION
set /a count=0
set /a t=0
for /f %%a in ('dir /b *.sql') do (
#echo %%a
for /L %%b in ('findstr /r /n "^" %%a ^| find /C ":"') do (
set /a count+=%%b
)
)
echo !count!
For anyone interested reading this later, here is the final (working version):
#ECHO off
SETLOCAL ENABLEDELAYEDEXPANSION
SET /a count=0
SET /a t=0
for /f %%a in ('dir /b *.sql') do (
#echo %%a
for /f %%b in ('findstr /r /n "^" %%a ^| find /C ":"') do (
set t=%%b
)
ECHO !t!
#SET /a count+=!t!
)
ECHO "Total:" !count!
#set t=findstr /r /n "^" %%a | find /C ":" <- THIS IS INCORRECT
doesn't work. It sets the variable t to the string findstr /r /n "^" %%a and filters the Output of the set command (which Outputs nothing) with | find /C ":", which Counts exactly Zero colons in nothing.
The best way of doing it, is:
for /f %%x in ('findstr /r /n "^" %%a ^| find /C ":"') do set t=%%x
Edit I was so focussed on repairing your code, that I didn't really realize, that you just want to count all lines of all *.SQL files. That's quite easy. No need for delayed expansion:
set count=0
for /f %%a in ('type *.sql 2^>nul ^| find /n /v ""') do set /a count+=1
echo %count%
2>nul prevents type to print the file names to the screen.
Edit2
my solution with 203 files, 47763 lines in total needs about 24 seconds. Aschipfl's edit2 solution about seven milliseconds. Impressive... find /c must have a really efficient method to count the lines.
My slightly changed code to use this capability:
set count=0
for /f %%a in ('type *.sql 2^>nul ^| find /c /n /v ""') do set /a count+=%%a
echo %count%
which also needs about 7ms. No big surprise, as it's basically the same code as Aschipfl's - just formatted differently.
There is no need to use findstr /N /R "^" to precede each line with line number and : and count the number of lines containing : afterwards.
To count the number of lines a text file contains, you only need to redirect it into find /C /V "", like this:
< "\path\to\file.txt" find /C /V ""
To use this within your for loop, you could do this:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set /A COUNT=0
set /A T=0
for /F "eol=| delims=" %%A in ('dir /B "*.sql"') do (
setlocal DisableDelayedExpansion
echo(%%~A
for /F %%B in ('^< "%%~A" find /C /V ""') do (
endlocal
set /A T=%%B
)
echo(!T!
set /A COUNT+=T
)
echo Total: %COUNT%
endlocal
exit /B
The toggling of delayed expansion is done to avoid trouble with exclamation marks ! in any of the *.sql file names, which would be ignored otherwise and the error message The system cannot find the file specified. would appear. Delayed expansion is only required because of the (debug) line echo(!T!; if you remove that, you can disable delayed expansion for the entire script.
Edit #1:
Here is a compacted variant of the above script, without any temporary variable T and with delayed expansion disabled:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set /A COUNT=0
for /F "eol=| delims=" %%A in ('dir /B "*.sql"') do (
for /F %%B in ('^< "%%~A" find /C /V ""') do (
echo(%%~A: %%B
set /A COUNT+=%%B
)
)
echo TOTAL: %COUNT%
endlocal
exit /B
And here is a script that uses a single for /F loop to retrieve the number of lines, which lets find search for the *.sql files like find /C /V "" "*.sql", then takes its output that looks like ---------- file.sql: 5 for instance, splits off the count after : and sums up all of them. This approach needs delayed expansion again:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set /A COUNT=0
for /F "eol=| delims=" %%A in ('find /C /V "" "*.sql"') do (
set "LINE=%%~A"
echo(%%~A
setlocal EnableDelayedExpansion
set /A T=!LINE:*: =!
for /F "delims=" %%B in ("!T!") do (endlocal & set /A COUNT+=%%B)
)
echo TOTAL: %COUNT%
endlocal
exit /B
Yes, there is a second for /F loop nested as well, but this is required for transferring the value of T over the endlocal barrier.
Edit #2:
Here is probably the best solution for determining the total number of lines over multiple files. This lets type output every line of all the *.sql files, which are then counted by find. Therefore the for /F loop iterates once only, so the overall performance of this script is quite great:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set /A COUNT=0
for /F "delims=" %%A in ('
2^>nul type "*.sql" ^| find /C /V ""
') do (
set /A COUNT+=%%A
)
echo TOTAL: %COUNT%
endlocal
exit /B
Related
I'm having trouble assigning a command that counts the number of times a file in a directory appears to a variable.
I found that I should do this - (^ |) but when I do it and echo the variable, nothing is displayed, and there should be a number of occurrences.
My code:
#echo off
setlocal enableextensions
for /f "tokens=*" %%i in ('dir /b C:\Users\AD10FC\IdeaProjects\collateral\db-resources\release | findstr /B "!first%!-" | find /c /v ""') do set VAR=%%i
echo %VAR%
There is no need to use findstr.exe in your code to filter the leading strings, because you can filter those directly in Dir:
#Echo Off
SetLocal EnableExtensions
Set "first=LeadingString"
For /F "EOL=? Delims=" %%G In ('"Dir /B /A:-D "%UserProfile%\IdeaProjects\collateral\db-resources\release\%first%-*" | "%SystemRoot%\System32\find.exe" /V /C """') Do Set "VAR=%%G"
Echo(%VAR%
Pause
Also for the specific task you're performing, you may find the following works better for you:
#Echo Off
SetLocal EnableExtensions
Set "first=LeadingString"
For /F %%G In ('""%SystemRoot%\System32\xcopy.exe" "%UserProfile%\IdeaProjects\collateral\db-resources\release\%first%-*" . /LQH"') Do Set "VAR=%%G"
Echo(%VAR%
Pause
I've this batch file to execute.
#echo off
set /a count=0
for /f "tokens=1delims=:" %%i in ('findstr /n "^" "foto1.txt"') do set /a count=%%i
set /a rd=%random%%%count
if %rd% equ 0 (set "skip=") else set "skip=skip=%rd%"
set "found="
for /f "%skip%tokens=1*delims=:" %%i in ('findstr /n "^" "foto1.txt"') do if not defined found set "found=%%i"&set "var=%%j"
echo.%var%
break > urlfoto1.txt
echo %var% >> urlfoto1.txt
set /a count=0
for /f "tokens=1delims=:" %%i in ('findstr /n "^" "foto2.txt"') do set /a count=%%i
set /a rd=%random%%%count
if %rd% equ 0 (set "skip=") else set "skip=skip=%rd%"
set "found="
for /f "%skip%tokens=1*delims=:" %%i in ('findstr /n "^" "foto2.txt"') do if not defined found set "found=%%i"&set "var=%%j"
echo.%var%
break > urlfoto2.txt
echo %var% >> urlfoto2.txt
set /a count=0
for /f "tokens=1delims=:" %%i in ('findstr /n "^" "foto3.txt"') do set /a count=%%i
set /a rd=%random%%%count
if %rd% equ 0 (set "skip=") else set "skip=skip=%rd%"
set "found="
for /f "%skip%tokens=1*delims=:" %%i in ('findstr /n "^" "foto3.txt"') do if not defined found set "found=%%i"&set "var=%%j"
echo.%var%
break > urlfoto3.txt
echo %var% >> urlfoto3.txt
This script is generating random jpg url from a list on a file and create a txt new file with 1 random jpg url.
When I exectute it manually, it work fine.
But when I go to scheduling it with Windows Task Scheduler I retrieve a (0xFF) ERROR.
What does (0xff) mean? And why the scheduling is not working?
And similarly to LotPings answer:
#Echo Off
CD /D "%~dp0"
For %%A In (1 2 3) Do Call :Sub "%%A"
Pause
GoTo :EOF
:Sub
For /F "Delims==" %%A In ('Set line[ 2^>Nul') Do Set "%%A="
Set "total="&Set "randlinenum="
For /F "Tokens=1*Delims=[]" %%A In ('Find /V /N ""^<"foto%~1.txt"'
)Do Set "line[%%A]=%%B"&Set "total=%%A"
Set /A randlinenum=1+(%RANDOM% %% total)
SetLocal EnableDelayedExpansion
Echo(!line[%randlinenum%]!
(Echo(!line[%randlinenum%]!)>"urlfoto%~1.txt"
EndLocal
Exit /B
Please note that the Find /V /N "", (or FindStr /N "^"), construct will also include any empty lines, so if those exist in any of your foto* files this could feasibly randomly select and output an empty line.
I don't like the redundancy in your batch, use a counting for /l and a call :sub
using a find /c is simpler to get the line count of a file.
using more +x avoids the need to differentiate the skip count.
when outputting only one line, directly use > to overwrite previous content.
Here using %~dp0 (batch folder) for input/output.
:: Q:\Test\2019\05\02\SO_55951454.cmd
#echo off
cd /d "%~dp0"
for /L %%L in (1,1,3) do Call :Sub %%L
Goto :Eof
:Sub
for /f %%i in ('find /C /V "" ^<"foto%1.txt"') do set count=%%i
set /a "rd=%random% %% count"
for /f "delims=" %%i in ('more +%rd% "foto%1.txt"') do set "var=%%i"&goto :cont
:cont
echo.%var%
>"urlfoto%1.txt" echo.%var%
Goto :Eof
I need to set a variable to a substring I get from a find command, but it is returning the command
findstr /is "CouId:" C:\dev\AssayInfo.txt
set number=findstr /is "CouId:" C:\dev\AssayInfo.txt
echo %number%
pause
This is what the file that I'm using findstr looks like:
Protocol: NVD_RCP_Fluidic_Accuracy_v0.4
ProtocolVersion: 1
SampleId: FLQC+20191126+00111280+96
InstrumentId: 123456789001
CouId: 138527011
CouSlot: 1
The variable should have the value 138527011 in this case.
Here you go:
#echo off
for /f "tokens=1,* delims=: " %%i in ('type "C:\dev\AssayInfo.txt" ^| findstr /i CouID') do set "number=%%j"
echo %number%
:# Here you can add your if statements etc.
pause
Note, that this is exactly what you required, as per this question and not the other question and therefore this is all I can give you now.
However, this is not even needed, you can do it without the set variable:
#echo off
for /f "tokens=1,* delims=: " %%i in ('type "C:\dev\AssayInfo.txt" ^| findstr /i CouID') do (
echo %%i
:# Here you can add your if statements etc.
)
pause
Personally, I would use:
#echo off
for /F "tokens=2" %%A IN ('findstr /ibc:"CouId: " C:\dev\AssayInfo.txt') do set "num=%%A"
rem Do some code here:
pause
exit /b 0
which is much shorter.
Note that direct comparisons can be done directly inside the for loop like:
#echo off
setlocal EnableDelayedExpansion
for /F "tokens=2" %%A IN ('findstr /ibc:"CouId: " C:\dev\AssayInfo.txt') do (
set "num=%%A"
rem Do sum comparisons with '!num!', not '%num%'!:
)
pause
exit /b %errorlevel%
I have a batch file that processes scanned PDFs using ghostscript. One of the user prompts is for the resolution of the desired output. I wrote a crude autodetect routine like this:
for /f "delims=" %%a in ('findstr /C:"/Height 1650" %1') do set resdect=150
for /f "delims=" %%a in ('findstr /C:"/Height 3300" %1') do set resdect=300
for /f "delims=" %%a in ('findstr /C:"/Height 6600" %1') do set resdect=600
echo %resdect% DPI detected.
%1 is the filename passed to the batch script.
This should return the the highest resolution detected of some common sizes we see. My question to the community is: Is there a faster or more efficient way to do this other than search the file multiple times?
Assuming that the value of RESDECT is the /Height value divided by 11, and that no line contains more than one /Height token, the following code might work for you:
#echo off
for /F delims^=^ eol^= %%A in ('findstr /R /I /C:"/Height *[0-9][0-9]*" "%~1"') do (
set "LINE=%%A"
setlocal EnableDelayedExpansion
set "RESDECT=!LINE:*/Height =!"
set /A "RESDECT/=11"
echo/!RESDECT!
endlocal
)
If you only want to match the dedicated /Height values 1650, 3300, 6600, you could use this:
#echo off
for /F delims^=^ eol^= %%A in ('findstr /I /C:"/Height 1650" /C:"/Height 3300" /C:"/Height 6600" "%~1"') do (
set "LINE=%%A"
setlocal EnableDelayedExpansion
set "RESDECT=!LINE:*/Height =!"
set /A "RESDECT/=11"
echo/!RESDECT!
endlocal
)
To gather the greatest /Height value appearing in the file, you can use this script, respecting the aforementioned assumptions:
#echo off
set "RESDECT=0"
for /F delims^=^ eol^= %%A in ('findstr /R /I /C:"/Height *[0-9][0-9]*" "%~1"') do (
set "LINE=%%A"
setlocal EnableDelayedExpansion
set "HEIGHT=!LINE:*/Height =!"
for /F %%B in ('set /A HEIGHT/11') do (
if %%B gtr !RESDECT! (endlocal & set "RESDECT=%%B") else endlocal
)
)
echo %RESDECT%
Of course you can again exchange the findstr command line like above.
Here is another approach to get the greatest /Height value, using (pseudo-)arrays, which might be faster than the above method, because there are no extra cmd instances created in the loop:
#echo off
setlocal
set "RESDECT=0"
for /F delims^=^ eol^= %%A in ('findstr /R /I /C:"/Height *[0-9][0-9]*" "%~1"') do (
set "LINE=%%A"
setlocal EnableDelayedExpansion
set "HEIGHT=!LINE:*/Height =!"
set /A "HEIGHT+=0, RES=HEIGHT/11" & set "HEIGHT=0000000000!HEIGHT!"
for /F %%B in ("$RESOLUTIONS[!HEIGHT:~-10!]=!RES!") do endlocal & set "%%B"
)
for /F "tokens=2 delims==" %%B in ('set $RESOLUTIONS[') do set "RESDECT=%%B"
echo %RESDECT%
endlocal
At first all heights and related resolutions are collected in an array called $RESOLUTIONS[], where the /Height values are used as indexes and the resolutions are the values. The heights become left-zero-padded to a fixed number of digits, so set $RESOLUTIONS[ return them in ascending order. The second for /F loop returns the last arrays element whose value is the greatest resolution.
I do have to admit that this was inspired by Aacini's nice answer.
get the corresponding line to a variable and work with that instead of the whole file. Instead of your three for loops, you can use just one, when you change the logic a bit:
#echo off
setlocal enabledelayedexpansion
for /f "delims=" %%a in ('findstr /C:"/Height " %1') do (
set "line=%%a"
set "line=!line:*/Height =!"
for /f "delims=/ " %%b in ("!line!") do set "hval=!hval! %%b"
)
for %%a in (1650,3300,6600) do #(
echo " %hval% " | find " %%a " >nul && set /a resdect=%%a/11
)
echo %resdect% DPI detected.
A solution with jrepl.bat could look something like:
for /f %a in ('type t.txt^|find "/Height "^|jrepl ".*/Height ([0-9]{4}).*" "$1"^|sort') do set /a dpi==%a / 11
(given, all valid Heights have 4 digits)
Note: for use in batchfiles, use %%a instead of %a
I barely scratched the surface of jrepl - I'm quite sure, there is a much more elegant (and probably faster) solution.
You may directly convert the Height value into the highest resolution in a single operation using an array. However, to do that we need to know the format of the line that contain the Height value. In the code below I assumed that the format of such a line is /Height xxxx, that is, that the height is the second token in the line. If this is not true, just adjust the "tokens=2" value in the for /F command.
EDIT: Code modified as requested in comments
In this modified code the Height value may appear anywhere in the line.
#echo off
setlocal EnableDelayedExpansion
rem Initialize "resDect" array
for %%a in ("1650=150" "3300=300" "6600=600") do (
for /F "tokens=1,2 delims==" %%b in (%%a) do (
set "resDect[%%b]=%%c"
)
)
set "highResDect=0"
for /F "delims=" %%a in ('findstr "/Height" %1') do (
set "line=%%a"
set "line=!line:*/Height =!"
for /F %%b in ("!line!") do set /A "thisRectDect=resDect[%%b]"
if !thisRectDect! gtr !highResDect! set "highResDect=!thisRectDect!"
)
echo %highResDect% DPI detected.
For the record, the final code was:
setlocal enabledelayedexpansion
set resdetc=0
for /f "delims=" %%a in ('findstr /C:"/Height " %1') do (
set "line=%%a"
set "line=!line:*/Height =!"
for /f "delims=/ " %%b in ("!line!") do set "hval=!hval! %%b"
)
for %%a in (1650,3300,6600) do #(
echo " %hval% " | find " %%a " >nul && set /a resdetc=%%a/11
)
if %resdetc%==0 SET resDefault=3
if %resdetc%==150 SET resDefault=1
if %resdetc%==300 SET resDefault=3
if %resdetc%==600 SET resDefault=6
ECHO.
ECHO Choose your resolution
ECHO ----------------------
ECHO 1. 150 4. 400
ECHO 2. 200 5. 500
ECHO 3. 300 6. 600
ECHO.
IF NOT %RESDETC%==0 ECHO 7. Custom (%resdetc% DPI input detected)
IF %RESDETC%==0 ECHO 7. Custom
ECHO ----------------------
choice /c 1234567 /T 3 /D %resDefault% /N /M "Enter 1-7 (defaults to %resDefault% after 3 sec.): "
IF errorlevel==7 goto choice7
IF errorlevel==6 set reschoice=600 & goto convert
IF errorlevel==5 set reschoice=500 & goto convert
[...]
Thanks everyone for the help!
I have a Java program that appends new builds information in the last two lines of a file.
How can I read them in batch file?
This code segment do the trick...
for /F "delims=" %%a in (someFile.txt) do (
set "lastButOne=!lastLine!"
set "lastLine=%%a"
)
echo %lastButOne%
echo %lastLine%
EDIT: Complete TAIL.BAT added
This method may be modified in order to get a larger number of lines, that may be specified by a parameter. The file below is tail.bat:
#echo off
setlocal EnableDelayedExpansion
rem Tail command in pure Batch: Tail.bat filename numOfLines
rem Antonio Perez Ayala
for /F "delims=" %%a in (%1) do (
set /A i=%2, j=%2-1
for /L %%j in (!j!,-1,1) do (
set "lastLine[!i!]=!lastLine[%%j]!
set /A i-=1
)
set "lastLine[1]=%%a"
)
for /L %%i in (%2,-1,1) do if defined lastLine[%%i] echo !lastLine[%%i]!
2ND EDIT: New version of TAIL.BAT added
The version below is more efficient:
#echo off
setlocal EnableDelayedExpansion
rem Tail command in pure Batch, version 2: Tail.bat filename numOfLines
rem Antonio Perez Ayala
set /A firstTail=1, lastTail=0
for /F "delims=" %%a in (%1) do (
set /A lastTail+=1, lines=lastTail-firstTail+1
set "lastLine[!lastTail!]=%%a"
if !lines! gtr %2 (
set "lastLine[!firstTail!]="
set /A firstTail+=1
)
)
for /L %%i in (%firstTail%,1,%lastTail%) do echo !lastLine[%%i]!
This will solve the problem, where someFile.txt is the file where you want to read the lines from:
for /f %%i in ('find /v /c "" ^< someFile.txt') do set /a lines=%%i
echo %lines%
set /a startLine=%lines% - 2
more /e +%startLine% someFile.txt > temp.txt
set vidx=0
for /F "tokens=*" %%A in (temp.txt) do (
SET /A vidx=!vidx! + 1
set localVar!vidx!=%%A
)
echo %localVar1%
echo %localVar2%
del temp.txt
::change the values bellow with a relevant ones.
set "file=C:\some.file"
set "last_lines=2"
for /f %%a in ('findstr /R /N "^" "%file%" ^| find /C ":"') do #set lines=%%a
set /a m=lines-last_line
more +%m% "%file%"
Directly from the command line:
C:\>set "file=C:\some.file"
C:\>set "last_lines=5"
C:\>(for /f %a in ('findstr /R /N "^" "%file%" ^| find /C ":"') do #set lines=%a)&#set /a m=lines-last_lines&call more +%m% "%file%"