Batch files: List all files in a directory with relative paths
Asked Answered
A

6

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.

Attainture answered 5/12, 2011 at 12:40 Comment(0)
A
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
)
Armallas answered 5/12, 2011 at 14:47 Comment(0)
P
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
Perspicuity answered 31/8, 2012 at 4:41 Comment(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?Stagehand
@Stagehand - Not a clue. If you are sure you have not forgotten to use the /S option, then that sounds worthy of a new question.Perspicuity
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 helpStagehand
it was outputting the sub dir files into a new file the subdirectoriesStagehand
Nice solution(s), but wow FORFILES is slow! What on earth is it doing?Semiliquid
@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.Perspicuity
A
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
)
Armallas answered 5/12, 2011 at 14:47 Comment(0)
S
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.

Stercoricolous answered 5/12, 2011 at 19:16 Comment(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 )Brambly
D
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
Dragnet answered 6/12, 2011 at 10:32 Comment(4)
Works like charm! I like this approach :-)Airla
like this too as at the first look in seems less mindscrewing than the delayed expansion tricksFairfax
Is there a limit (like 8) to the maximum depth?Bullhorn
@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.Dragnet
F
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
)
Foetation answered 26/10, 2012 at 5:45 Comment(0)
W
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.

Waters answered 20/9, 2021 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.