0

I am looking to capture the Project Version in a set of files stored in folder and All sub-folders.

Using:

@echo off<br>
findstr /s "ProjectVersion" C:\srm2\reg\*.cpf > C:\RESULTS.txt

Returns every .cpf file in the folders.

e.g.

C:\srm2\reg\132L\132L-20151111-150804.cpf:  <ProjectVersion Major="8" Minor="0" Build="22" Revision="20312" />
C:\srm2\reg\132L\132L.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="20312" />
C:\srm2\reg\132L\132L18122014121012.cpf:  <ProjectVersion Major="8" Minor="0" Build="22" Revision="20312" />
C:\srm2\reg\132L\132L18122014121731.cpf:  <ProjectVersion Major="8" Minor="0" Build="22" Revision="20312" />
C:\srm2\reg\DANNY\DANNY-20151112-134545.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="0" />
C:\srm2\reg\DANNY\DANNY-20151113-083530.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="0" />
C:\srm2\reg\DANNY\DANNY-20151113-085621.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="0" />
C:\srm2\reg\DANNY\DANNY-20151113-102018.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="0" />
C:\srm2\reg\DANNY\DANNY.cpf:  <ProjectVersion Major="11" Minor="0" Build="0" Revision="0" />

How can I filter to return only the last modified .cpf file in every sub-folder, as some folders have many versions of the same file?

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Mick
  • 1
  • 1

3 Answers3

1

I copied the answer at this post, that indicate: "You may write a recursive algorithm in Batch that gives you exact control of what you do in every nested subdirectory", and slightly modified it in order to solve this problem. The approach is the same than other answers: walk through the tree and in each sub-folder execute findstr with just the first file ordered by last modified date; the difference is that this process is explicitly achieved via code, not via for /R or dir /S commands.

@echo off
setlocal EnableDelayedExpansion
cd C:\srm2\reg
call :treeProcess > C:\RESULTS.txt
goto :EOF


:treeProcess
set "output="
for /F "delims=" %%f in ('dir /B /A:-D /O:D *.cpf') do if not defined output (
   for /F "delims=" %%o in ('findstr "ProjectVersion" "%%f"') do set "output=!cd!\%%f: %%o"
)
if defined output echo !output!

rem Recursive call
for /D %%d in (*) do (
    cd %%d
    call :treeProcess
    cd ..
)

exit /b
Community
  • 1
  • 1
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Great approach, @Aacini, +1! what about performance of such recursive calls? – aschipfl Nov 13 '15 at 17:25
  • Works on server, but does spit out ECHO is off a lot :) – Mick Nov 13 '15 at 18:39
  • @Mick: This happen when one sub-folder does _not_ have any `*.cpf` file! Just add `if defined output` before `echo !output!` in the code. I already did this in the answer... – Aacini Nov 13 '15 at 18:56
  • @Aacini Thanks for the code and explanations. I wasn't complaining about the ECHO, apologies in advance. You guys on the forums are such valuable information stations to minions like myself. – Mick Nov 13 '15 at 18:59
  • @aschipfl: Well, the same than any other code with `call`'s! However, in this case the `call` is performed in each sub-folder level, so it may be more efficient than other code that do a `call` on each file. – Aacini Nov 13 '15 at 19:01
  • @Aacini, I think the number of `call`s is even more in your code than in [mine](http://stackoverflow.com/a/33695640/5047996), because I do it only once per each sub-directory that contains at least one file that matches the given pattern `*.cpf`, but you do it for _every_ sub-directory; that was mainly in my mind when asking about the performance, not knowing how many directories to _not_ contain any `*.cpf` file... – aschipfl Nov 13 '15 at 23:54
0
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION 
SET "sourcedir=U:\sourcedir"
SET "lastdir="
FOR /f "delims=" %%a IN ('DIR /s /b /a-d /o-d "%sourcedir%\*.bat" ') DO IF "!lastdir!" neq "%%~dpa" (
 FINDSTR /i "z" "%%a" >NUL
 IF NOT ERRORLEVEL 1 (
  ECHO(%%a
  SET "lastdir=%%~dpa"
 )
)

GOTO :EOF

You would need to change the setting of sourcedir to suit your circumstances.

The one line of code you provide does not work at all.

This procedure simply produces a directory list of *.bat files in basic form (full-filename only) but sorted in reverse-date order and excluding directorynames.

The report is in reverse-date/time order within each directoryname, so the first matching filename for any directory is the latest file.

If the directory-name has changed, then execute a findstr to locate the required keystring. If it's found, errorlevel is set to 0, hence it is not 1 or greater so the filename is echoed and lastdir is set to the file's directoryname.

I simply used *.bat files and searched for "z" witin those files for testing. Adjust as required.

Magoo
  • 77,302
  • 8
  • 62
  • 84
0

You could use for /R /D to recursively walk through all directories, then use dir /B /A:-D /O:-D to return a list of files sorted per date (newest first) and parse its output by for /F taking only the first item, so you enumerate only the one newest file in every directory:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
> "C:\results.txt" (
    for /D /R %%D in ("C:\srm2\reg\*.*") do (
        call :SUB FILE LINE "%%~fD\*.cpf" "ProjectVersion"
        if defined LINE (
            echo/!FILE!:!LINE!
        )
    )
)
endlocal
exit /B

:SUB var_file var_line pattern string
set "%~1=" & set "%~2="
for /F "eol=| delims=" %%F in ('dir /B /A:-D /O:-D "%~3"') do (
    set "%~1=%%~fF"
    for /F "delims=" %%L in ('findstr /L "%~4" "%%~fF"') do (
        set "%~2=%%L"
        goto :EOF
    )
    goto :EOF
)

Let us take a look at the subroutine :SUB first, which is intended to execute once per each directory. This requires 4 parameters:

  • (%~1) name of variable to hold the path to the retrieved file,
  • (%~2) name of variable to hold the line that matches the search string,
  • (%~3) the pattern to get a sorted list of files (newest first), and
  • (%~4) the search string (expected to occur at most once per file).

The outer for /F loop parses the output of the command dir /B /A:-D /O:-D "%~3", which actually builds the sorted list of files. The loop wants to iterate through every single line, but since there is a goto :EOF at the end, the loop is broken after the first iteration, so only the first (and therefore the newest) list item is processed. The path to this item is assigned to the first variable given as an argument. If no items match the file pattern, the variable remains empty.

The inner for /F loop parses the output of the search command findstr. Since only one occurrence of the search string is expected at most, this loop should iterate once only at most. If a match is found, the containing line is assigned to the second variable given as an argument. If no match is found, the variable remains empty.

Now let us check out the main routine. This walks through the given directory tree recursively using for /D /R and iterates through all directories. For each one, the subroutine :SUB is called, passing over the following arguments:

  • variable name FILE (will receive the newest file in the currently itereted directory),
  • variable name LINE (will receive the line in the file that matches the search string),
  • the currently iterated directory with the file pattern reg*.cpf appended, and
  • the demanded search string ProjectVersion.

Finally, the variable LINE is checked against emptiness, and if not, a line containing the file path, a colon and the found line is output by echo.

Notice that all output is redirected into file C:\results.txt.

This approach using a subroutine has been chosen, because there is a goto command used to terminate one certain for loop upon its first iteration (see description above), but not all of them. goto actually breaks all block contexts it is placed in, even all nested ones. for loops are just a kind of block command, like if clauses, or also simple grouped commands (). Using a subroutine prevents blocks of the main routine from being broken by a goto placed in the subroutine.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • I did a small rework as I did not see the backslash between `reg` and `*.cpf`, so I treated `reg` as part of the file name rather than a directory; however, the functional code remains the same... – aschipfl Nov 13 '15 at 23:59