67

Concerning Windows batch files: Is there a way to list all the files (or all of a specific type) in a certain directory and its subdirectories, including the paths relative to the current (or the search) directory in the list?

For example, if I want all the .txt files in the current directory and subdirectories with their full paths, I can do

for /r . %%g in (*.txt) do echo %%g >> C:\temp\test.txt

or

dir *.txt /b /s >> C:\temp\test.txt

and I will get something like

C:\test\Doc1.txt
C:\test\subdir\Doc2.txt
C:\test\subdir\Doc3.txt

If I do

for /r . %%g in (*.txt) do echo %%~nxg >> C:\temp\test.txt

I will get something like

Doc1.txt
Doc2.txt
Doc3.txt

But what I really want is:

Doc1.txt
subdir\Doc2.txt
subdir\Doc3.txt

Is it possible?

If my post is too confusing: I basically want List files recursively in Linux CLI with path relative to the current directory, but just for Windows.

6 Answers 6

45

The simplest (but not the fastest) way to iterate a directory tree and list relative file paths is to use FORFILES.

forfiles /s /m *.txt /c "cmd /c echo @relpath"

The relative paths will be quoted with a leading .\ as in

".\Doc1.txt"
".\subdir\Doc2.txt"
".\subdir\Doc3.txt"


To remove quotes:

for /f %%A in ('forfiles /s /m *.txt /c "cmd /c echo @relpath"') do echo %%~A


To remove quotes and the leading .\:

setlocal disableDelayedExpansion
for /f "delims=" %%A in ('forfiles /s /m *.txt /c "cmd /c echo @relpath"') do (
  set "file=%%~A"
  setlocal enableDelayedExpansion
  echo !file:~2!
  endlocal
)

or without using delayed expansion

for /f "tokens=1* delims=\" %%A in (
  'forfiles /s /m *.txt /c "cmd /c echo @relpath"'
) do for %%F in (^"%%B) do echo %%~F
6
  • your solution works when I output the values to cmd but if I echo ´@relpath >> cache.appcache´, only the files in the parent directory get listed. Do you know what could be causing that?
    – rory
    Commented Jan 18, 2016 at 15:04
  • @rory - Not a clue. If you are sure you have not forgotten to use the /S option, then that sounds worthy of a new question.
    – dbenham
    Commented Jan 18, 2016 at 15:58
  • definitely not, it outputs the sub dirs to cmd window but not the txt file. I'll post a new question so, thanks for your help
    – rory
    Commented Jan 18, 2016 at 16:12
  • it was outputting the sub dir files into a new file the subdirectories
    – rory
    Commented Jan 18, 2016 at 16:57
  • 1
    @TimAngus - It launches a new cmd.exe process for each iteration. Yes, it is very slow compared to other forms of iteration. But the convenience is seductive.
    – dbenham
    Commented Apr 17, 2019 at 17:34
25

You could simply get the character length of the current directory, and remove them from your absolute list

setlocal EnableDelayedExpansion
for /L %%n in (1 1 500) do if "!__cd__:~%%n,1!" neq "" set /a "len=%%n+1"
setlocal DisableDelayedExpansion
for /r . %%g in (*.log) do (
  set "absPath=%%g"
  setlocal EnableDelayedExpansion
  set "relPath=!absPath:~%len%!"
  echo(!relPath!
  endlocal
)
0
10

This answer will not work correctly with root paths containing equal signs (=). (Thanks @dbenham for pointing that out.)


EDITED: Fixed the issue with paths containing !, again spotted by @dbenham (thanks!).

Alternatively to calculating the length and extracting substrings you could use a different approach:

  • store the root path;

  • clear the root path from the file paths.

Here's my attempt (which worked for me):

@ECHO OFF
SETLOCAL DisableDelayedExpansion
SET "r=%__CD__%"
FOR /R . %%F IN (*) DO (
  SET "p=%%F"
  SETLOCAL EnableDelayedExpansion
  ECHO(!p:%r%=!
  ENDLOCAL
)

The r variable is assigned with the current directory. Unless the current directory is the root directory of a disk drive, it will not end with \, which we amend by appending the character. (No longer the case, as the script now reads the __CD__ variable, whose value always ends with \ (thanks @jeb!), instead of CD.)

In the loop, we store the current file path into a variable. Then we output the variable, stripping the root path along the way.

1
  • This works great. For anyone to get *.txt files in abc subfolder: @echo off setlocal DisableDelayedExpansion set "r=%__CD__%abc\" for /r abc %%f in (*.txt) do ( set "p=%%f" setlocal EnableDelayedExpansion echo !p:%r%=! endlocal ) Commented Jun 7, 2016 at 9:08
8

Of course, you may write a recursive algorithm in Batch that gives you exact control of what you do in every nested subdirectory:

@echo off
set mypath=
call :treeProcess
goto :eof

:treeProcess
setlocal
for %%f in (*.txt) do echo %mypath%%%f
for /D %%d in (*) do (
    set mypath=%mypath%%%d\
    cd %%d
    call :treeProcess
    cd ..
)
endlocal
exit /b
4
  • Works like charm! I like this approach :-)
    – K.Mulier
    Commented Jun 28, 2016 at 14:08
  • like this too as at the first look in seems less mindscrewing than the delayed expansion tricks
    – grenix
    Commented Jun 19, 2017 at 13:21
  • Is there a limit (like 8) to the maximum depth? Commented Oct 7, 2018 at 14:34
  • @PeterMortensen: Yes. There is a limit in the number of nested setlocal commands that is version dependant. In my WIndows 8.1 it is 32.
    – Aacini
    Commented Oct 7, 2018 at 16:14
4
@echo on>out.txt
@echo off
setlocal enabledelayedexpansion
set "parentfolder=%CD%"
for /r . %%g in (*.*) do (
  set "var=%%g"
  set var=!var:%parentfolder%=!
  echo !var! >> out.txt
)
0
2

You could (mis-)use the xcopy command1 that can list relative paths of files to be copied and that features the option /L to not copy but just list files that would be copied without.

rem // First change into the target directory:
cd /D "C:\test"
rem // Now let `xcopy` list files relative to the current directory preceded with `.\`:
xcopy /L /S /Y /R /I ".\*.txt" "%TEMP%"
rem // Or let `xcopy` list files relative to the current directory preceded with the drive:
xcopy /L /S /Y /R /I "*.txt" "%TEMP%"

This would produce an output like such:

.\Doc1.txt
.\subdir\Doc2.txt
.\subdir\Doc3.txt
3 File(s)

C:Doc1.txt
C:subdir\Doc2.txt
C:subdir\Doc3.txt
3 File(s)

The destination can be an arbitrary directory path on an existing and accessible drive that does not reside within the source directory tree.

Note that this only works with file but not with directory paths.


To delete the summary line … File(s) let findstr filter it out:

cd /D "C:\test"
rem // Filter out lines that do not begin with `.\`:
xcopy /L /S /Y /R /I ".\*.txt" "%TEMP%" | findstr "^\.\\"
rem // Filter out lines that do not begin with a drive letter + `:`:
xcopy /L /S /Y /R /I "*.txt" "%TEMP%" | findstr "^.:"

Alternatively use find to filter such lines out:

cd /D "C:\test"
rem // Filter out lines that do not contain `.\`:
xcopy /L /S /Y /R /I ".\*.txt" "%TEMP%" | find ".\"
rem // Filter out lines that do not contain `:`:
xcopy /L /S /Y /R /I "*.txt" "%TEMP%" | find ":"

To remove the .\ or the drive prefix, capture the xcopy output with for /F and use an appropriate delimiter:

cd /D "C:\test"
rem // The 1st token is a `.`, the remaining token string `*` is going to be empty for the summary line:
for /F "tokens=1* delims=\" %%I in ('
    xcopy /L /S /Y /R /I ".\*.txt" "%TEMP%"
') do (
    rem // Output the currently iterated item but skip the summary line:
    if not "%%J"=="" echo(%%J
)

It should be quite obvious how to do the same for the drive prefix:

cd /D "C:\test"
rem // The 2nd token is empty for the summary line and is not even going to be iterated:
for /F "tokens=2 delims=:" %%I in ('
    xcopy /L /S /Y /R /I "*.txt" "%TEMP%"
') do (
    rem // Simply output the currently iterated item:
    echo(%%I
)

This is the related sample output:

Doc1.txt
subdir\Doc2.txt
subdir\Doc3.txt

1) The basic approach derives from this answer by user MC ND.

Not the answer you're looking for? Browse other questions tagged or ask your own question.