August 3rd, 2012

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
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.