Well, usually I strongly recommend not to do string manipulation on file or directory paths, because it is quite prone to failures. But for a task like this, which does not rely on existing paths, there appears no way around.
Anyway, for doing so in a reliable fashion, the following issues must be considered:
- Windows uses case-insensitive paths, so do all path comparisons in such manner as well!
- Avoid sub-string substitution, because it is troublesome with
=-signs and a few other characters! - Ensure to properly resolve the provided paths, using the
~f-modifier of for-loop meta-variables! This ensures that the input paths are really absolute, they do not contain doubled separators (\\), and there are no sequences with . and .. (like abc\..\.\def, which is equivalent to def), that make comparison difficult. - Regard that paths may be provided in an ugly way, like with trailing
\ or \., bad quotation (like abc\"def ghi"\jkl), or using wrong path separators (/ instead of \, which is the Windows standard).
Alright, so let us turn to a script that I wrote for deriving the common path and the relative path between two absolute paths, regarding all of the said items (see the rem comments):
@echo off setlocal EnableExtensions DisableDelayedExpansion rem // Define constants here: set "referencePath=C:\Users\"xyz"\dummy\..\.\project" set "absolutePath=C:\Users\"xyz"\other\\somedir\" rem /* At first resolve input paths, including correction of bad quotes and wrong separators, rem and avoidance of trailing separators (`\`) and also other unwanted suffixes (`\.`): */ set "referencePath=%referencePath:"=%" set "absolutePath=%absolutePath:"=%" for %%P in ("%referencePath:/=\%") do for %%Q in ("%%~fP.") do set "referencePath=%%~fQ" for %%P in ("%absolutePath:/=\%") do for %%Q in ("%%~fP.") do set "absolutePath=%%~fQ" rem // Initially clean up (pseudo-)array variables: for /F "delims==" %%V in ('2^> nul ^(set "$ref[" ^& set "$abs["^)') do set "%%V=" rem // Split paths into their elements and store them in arrays: set /A "#ref=0" & for %%J in ("%referencePath:\=" "%") do if not "%%~J"=="" ( set /A "#ref+=1" & setlocal EnableDelayedExpansion for %%I in (!#ref!) do endlocal & set "$ref[%%I]=%%~J" ) set /A "#abs=0" & for %%J in ("%absolutePath:\=" "%") do if not "%%~J"=="" ( set /A "#abs+=1" & setlocal EnableDelayedExpansion for %%I in (!#abs!) do endlocal & set "$abs[%%I]=%%~J" ) rem /* Determine the common root path by comparing and rejoining the array elements; rem also build the relative path herein: */ set "commonPath=\" & set "relativePath=." & set "flag=#" & set /A "#cmn=#ref+1" for /L %%I in (1,1,%#abs%) do ( if defined flag ( set "flag=" & if defined $ref[%%I] ( setlocal EnableDelayedExpansion if /I "!$abs[%%I]!"=="!$ref[%%I]!" for %%J in ("!commonPath!\!$ref[%%I]!") do ( endlocal & set "commonPath=%%~J" & set "flag=#" & set /A "#cmn=%%I+1" ) ) ) if not defined flag ( setlocal EnableDelayedExpansion & for %%J in ("!relativePath!\!$abs[%%I]!") do ( endlocal & set "relativePath=%%~J" ) ) ) rem // Complete the relative path by preceding enough level-up (`..`) items: for /L %%I in (%#cmn%,1,%#ref%) do ( setlocal EnableDelayedExpansion & for %%J in (".\.!relativePath!") do ( endlocal & set "relativePath=%%~J" ) ) set "relativePath=%relativePath:*\=%" & if "%commonPath:~-1%"==":" set "commonPath=%commonPath%\" set "commonPath=%commonPath:~2%" & if not defined commonPath set "relativePath=%absolutePath%" rem // Eventually return results: set "referencePath" set "absolutePath" set "commonPath" 2> nul || echo commonPath= set "relativePath" endlocal exit /B
Note that this approach does not support UNC paths.