1

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!
jensa
  • 2,792
  • 2
  • 21
  • 36
  • 1
    you Need [delayed expansion](http://stackoverflow.com/a/30284028/2152082) – Stephan Dec 30 '15 at 13:29
  • I have `setlocal ENABLEDELAYEDEXPANSION` at the top – jensa Dec 30 '15 at 13:30
  • that only enables delayed expansion. You need to use it. Reference your variable with `!t!` inside the block. – Stephan Dec 30 '15 at 13:31
  • `setlocal ENABLEDELAYEDEXPANSION` is useles when you are doing `echo %count%`. You have to do `echo !count!` instead. – MichaelS Dec 30 '15 at 13:32
  • 1
    @MichaelS `%count%` is ok. It's outside the block. The Problem is with `echo %t%` – Stephan Dec 30 '15 at 13:33
  • I have changed to `echo !t!`, it still shows 0 though. I think the problem is the line `@set t=findstr /r /n "^" %%a | find /C ":"` ? Do I need to do `@set !t!=....` ? – jensa Dec 30 '15 at 13:34
  • 1
    well, that's the Syntax for far more modern programming languages. In `cmd` you can't use objects. Use a `for` instead (like you did with `dir /b *.SQL`) – Stephan Dec 30 '15 at 13:36
  • @Stephan I´m sorry I don't understand your last comment. – jensa Dec 30 '15 at 13:38
  • The only way you can assign the result of a command to a variable is using the FOR command. Just like you are doing with the DIR command. – Squashman Dec 30 '15 at 13:42
  • @Stephan See my update, it's not working, it gets stuck at the inner loop. – jensa Dec 30 '15 at 13:43
  • it's `/f`, not `/L`... – Stephan Dec 30 '15 at 13:45
  • Thanks, that fixed it! – jensa Dec 30 '15 at 13:47
  • just a note: `set /a` has an advanced syntax: you can use variables without `%` or `!`: `set /a count+=t` works fine (even in delayed expansion context). – Stephan Dec 30 '15 at 14:06

2 Answers2

2
@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.

Stephan
  • 53,940
  • 10
  • 58
  • 91
2

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
aschipfl
  • 33,626
  • 12
  • 54
  • 99