Batch files: List all files in a directory with relative paths

WindowsBatch FileCmdRelative Path

Windows Problem Overview


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 https://stackoverflow.com/questions/245698/list-files-recursively-in-linux-with-path-relative-to-the-current-directory, but just for Windows.

Windows Solutions


Solution 1 - Windows

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

Solution 2 - Windows

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
)

Solution 3 - Windows

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.

Solution 4 - Windows

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

Solution 5 - Windows

@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
)

Solution 6 - Windows

You could (mis-)use the xcopy command 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTim MeyerView Question on Stackoverflow
Solution 1 - WindowsdbenhamView Answer on Stackoverflow
Solution 2 - WindowsjebView Answer on Stackoverflow
Solution 3 - WindowsAndriy MView Answer on Stackoverflow
Solution 4 - WindowsAaciniView Answer on Stackoverflow
Solution 5 - WindowsKedarView Answer on Stackoverflow
Solution 6 - WindowsaschipflView Answer on Stackoverflow