FORFILES, for your fancier batch file enumeration needs


Crack open open the champagne: Batch File Week is finally over!

Variations on the for /f %%i in ('dir /b ...') will let you repeat an operation on the contents of a directory, possibly even recursively if you add the /s option, with some basic attribute-level filtering if you add the /a or /a- flags.

For your fancy recursive file operations, there's a tool called FORFILES which iterates through the contents of a directory (recursively if requested), executing a command on each item it finds. It also has additional filtering capability, like selecting files based on their last-modified time. For example, you could copy all files in the current directory which were modified today:

forfiles /D +0 /c "cmd /c copy @file \\server\today"

Unfortuantely, the /D option is not as flexible as one might like. For example, while it can pick files modified today, it can't pick files modified in the last week, because the relative-date-picker knows only how to pick files modified on or before a date in the past or files modified on or after a date in the future. (Who the heck wants to operate on files modified in the future? Except perhaps the Microsoft Research folks who are working on that time machine.)

You can type FORFILES /? for more information on what you can do (and by seeing what's omitted, what you can't do).

If the command you want to execute is rather long, you can offload it back into the batch file being executed:

@echo off
if "%1"=="/callback" goto callback
forfiles /D +0 /c "cmd /c call "%~f0" /callback @isdir @file @fsize"
goto :eof
:callback
rem %2 = @isdir
rem %3 = @file
rem %4 = @fsize
if %2==TRUE echo Skipping directory %3.&goto :eof
echo Copying file %3 to \\server\today (%4 bytes)

One gotcha here is that since each command runs in a sub-shell, it can read environment variables, but any modifications it makes to environment variables will be lost since the command is modifying only its local environment variables. A workaround for this is to use FORFILES to select the data to operate on, but use FOR to actually perform the operation. Since FOR runs inside the main command interpreter, it can modify environment variables.

set TOTALSIZE=0
for /f %%i in ('forfiles /d +0 /c "cmd /c if @isdir==FALSE echo @fsize"') ^
do set /a TOTALSIZE=TOTALSIZE + %%i

Here, we use FORFILES to enumerate all the files (not directories) modified today and print their sizes. We wrap this inside a FOR which reads the sizes and adds them up.

If the operation you want to perform on each file is complex, you can of course offload it into a subroutine call.

for /f %%i ^
in ('forfiles /d +0 /c "cmd /c if @isdir==FALSE echo @fsize"') ^
do call :subroutine %%i

I'm cheating here because I know that @fsize doesn't contain spaces. If you are processing file names, then you need to be more careful.

for /f "tokens=*" %%i ^
in ('forfiles /d +0 /c "cmd /c if @isdir==FALSE echo @fname"') ^
do call :subroutine %%i
Comments (20)
  1. Antonio Rodríguez says:

    Crack open open the champagne: Batch File Week is finally over!

    Nooooooooo!!!! At least, it's only 51 weeks for next years Batch File Week.

    I actually enjoyed it: I learned a few tricks that may make my life easier. I wish it had been more variated, instead on focusing so much in fors. But, hey, it's Raymond's blog, and Raymond writes what he wants to.

  2. I too enjoyed Batch File Week and hope to see it again next year (I do much so prefer it to CLR week, fwiw). This has been a great week filled with a ton of useful information (seriously!). Thanks Raymond.

  3. Adam Rosenfield says:

    Unix equivalent: find(1).  For example, to copy all files in the current directory to /server/today which were modified today:

    find . -maxdepth 1 -type f -daystart -mtime 0 -exec cp -t /server/today '{}' +

    Remove -maxdepth 1 to recurse into subdirectories.  The variety of file time tests, along with the -a (AND), -o (OR) and ! (NOT) operators, allow for pretty much any time you can imagine, including finding files modified after a date in the future.

    Like any Unix process, find can't modify environment variables in its parent shell.  If you really wanted to do that (which by its very nature is not idiomatic), you could produce as output a set of NAME=VALUE mappings and pipe that into the 'source' shell builtin, e.g.:

    source <(command which produces a set of NAME=VALUE mappings)

  4. Simon Buchan says:

    A neat trick for a common case: "for %i in ([file pattern]) do [command]", and "for /d %i in ([dir pattern]) do [command]", useful to do things like "for %i in (*.flac) do @ffmpeg -i %i %~ni.mp3"

    I too think Batch File Week was fun: the only problem with it is that I needed to know some of this a couple of weeks ago :)

  5. Stefan Kanthak says:

    @Simon:

    FOR /R [root] %I IN ([file pattern]) DO …

    processes the selected files in the whole directory tree beyond "root".

    @Raymond: 'DIR /B /S' can typically be replaced with FOR /R

  6. Danny Moules says:

    Batch File Week has taught me a great many things. Largely because I've being looking at Powershell to see what the sensible way to solve most of these problems is :)

  7. Dan says:

    How come I've never heard of FORFILES before? I actually had to try it out myself because I couldn't believe this comes preinstalled with Windows. Always used PowerShell or a short C# snippet because I find it hard to remember the verbose FOR syntax.

  8. Programmerman says:

    I, too, enjoyed the delve into batch file writing. Doesn't mean I want to write a batch file now, but I still enjoyed hearing about it.

  9. Scott says:

    Fascinating.  As someone that's contorted "for" to do all sorts things, I never knew about this command.  It looks most helpful.

  10. How did I not know about this command?!!! Thanks.

  11. Andrew from Vancouver says:

    This is a great series Raymond, I hope you see fit to continue it! I'm a Windows batch file veteran and I still learned a thing or two.

    By the way, the forfiles.exe is included in Vista/7/2K8 but for W2K3 and earlier it was in the Resource Kit. The W2K3 version runs fine on WXP. But you need the W2K version on W2K.

  12. anonymous says:

    It does not show up in the list of commands whey you type "HELP".  

    In MSDN it is documented as Applies To: Windows Server 2003, Windows Server 2003 R2, Windows Server 2003 with SP1, Windows Server 2003 with SP2

    so probably won't work for @xpclient

  13. Karellen says:

    Does anyone know how old "forfiles" is? On the MSDN pages I can't find any mention of the earliest verison of Windows (or DOS?) that it was shipped with. It says "Applies To: Windows Server 2008, Windows Vista", but it says the same thing about "cd", which definitely shipped with earlier versions than that! :-)

  14. Karellen says:

    Hey, when I posted that comment, the last comment was Ben L's. What gives?

  15. Neil says:

    FOR (and FOR /R) won't work with hidden files, and I couldn't work out how to get FOR … ('DIR/S/A') to work with Unicode file names (yes I tried CMD /U), so FORFILES turned out to be really useful in that case.

    For completeness, as someone pointed out in a previous post's comment, tokens=* doesn't work, you need to use delims= to properly handle file names including spaces.

  16. Mark says:

    Raymond, do you secretly like batch files?  (Of course, if it's a secret, you won't admit it, making this a silly question, but I had to ask.)

  17. Legolas says:

    Thanks for this series! I only read the last one and I learned something already. It's a real pity this command isn't more obviously available. Any idea where I could've learned about it without knowing it exists? I've looked at the output of 'help' in the command line, but it isn't there. Is there something like help advanced or so, with all the advanced stuff in it?

    [It's not listed in HELP because the command was added later, and the people who added it didn't ask the CMD folks "Hey, could you add this to HELP?" There are lots of commands not included in the HELP list, like WHERE and PowerShell. -Raymond]
  18. @Legolas

    This is why I often browse through System32 on a new version of Windows to see the additions/changes. I knew about these and a few more because they are in the System32 directory.

  19. Cthu says:

    "Who the heck wants to operate on files modified in the future? Except perhaps the Microsoft Research folks who are working on that time machine."

    Cue a flood of people taking that statement seriously and bothering Microsoft support for information about the secret time machine project…

  20. Luca says:

    @Adam Rosenfield

    foghting with this in the last few days

    forfiles /d -30 /c "cmd /c set test=1"

    may you help me with this please?

Comments are closed.