1

How can I convert an absolute path to a relative path in batch? I have an absolute path to a directory A and a reference directory B, and I need the path to A relative to B. As example, the following batch script should print ..\other\somedir\.

setlocal enabledelayedexpansion set referencePath=C:\Users\xyz\project\ set absolutePath=C:\Users\xyz\other\somedir\ set relativePath=... echo %relativePath% 

I tried relativePath=!absolutePath:%referencePath%=!, but this yields the absolute path C:\Users\xyz\other\somedir\.

I need something similar to the python function os.path.relpath:

>>> os.path.relpath("C:\\Users\\xyz\\other\\somedir", "C:\\Users\\xyz\\project\\") "..\\other\\somedir" 

I need this because I have a batch file with command line arguments similar to the above file names. This batch file creates another batch file startup.bat which sets some environment variables and starts an application. The startup.bat may be called over network, so I have to use relative paths. With absolute paths, the environment variables would point to the files on the wrong machine.

0

6 Answers 6

2

Here is a quick'n'dirty hack:

@echo off setlocal enabledelayedexpansion set referencePath=C:\Users\xyz\project\ set absolutePath=C:\Users\xyz\other\somedir\ set relativePath= :LOOP for /F "tokens=1 delims=\" %%a in ("%referencePath%") do (set ref=%%a) for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do (set rel=%%a) if /i !ref!==!rel! ( set referencePath=!referencePath:%ref%\=! set absolutePath=!absolutePath:%rel%\=! goto LOOP ) :RELLOOP for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do ( set absolutePath=!absolutePath:%%a\=! set relativePath=!relativePath!..\ ) if not "%absolutePath%"=="" goto RELLOOP set complRelPath=%relativePath%%referencePath% echo !complRelPath! 

This won't give you propper output if the folders are on different drives so you'll have to handle this special case yourself.

EDIT (comment): Well, this can't be that hard that you couldn't figure it out yourself. If / and \ are mixed (which is a bad idea - we are on Windows! Windows means \ in paths, UNIX etc. means / in paths) you should replace / by :

SET referencePath=%referencePath:/=\% SET absolutePath=%absolutePath:/=\% 

If the paths are equal, you have nothing to do so:

IF %referencePath%==%absolutePath% ( SET complRelPath=.\ GOTO WHATEVER ) 
Sign up to request clarification or add additional context in comments.

5 Comments

I had to switch referencePath and absolutePath, but then it works. Thank you :)
Your example creates an endless loop if both paths are the same and produces strange output if slashes and backslashes are mixed. Can I edit your solution and add my fixes? Or is it better to create a new answer for that?
I think this is a misunderstanding: I did create a solution on my own and asked whether I should post a new answer or edit yours with my solution.
Even after your edit there are some bugs: The output is wrong if a directory name is repeated within one of the paths, for example, xyz is repeated in `C:\Users\xyz\project\xyz`. Also, the batch script runs forever if the paths are the some but one path has a trailing backslash while the other has none.
@pdchill, backslashes are not allowed as characters in Windows. They are separators, meaning that you shouldn't end a path with one, because it isn't being used as a separator! Please also note, that there were three other answers, so instead of concentrating on only one of them, consider testing the others too. Those responders are no less worthy of your consideration after giving their time in an effort to assist you.
2
@ECHO OFF setlocal enabledelayedexpansion set referencePath=C:\Users\xyz\project\ set absolutePath=C:\Users\xyz\other\somedir\ FOR %%a IN ("%absolutepath%") DO FOR %%r IN ("%referencepath%.") DO ( SET "abspath=%%~pa" SET "relativepath=!abspath:%%~pr=..\!" ) echo %relativePath% GOTO :EOF 

It would be of assistance if you were to tell us what your desired output is. Telling us what the output of your current code is, and that implicitly that's not what you expect, and then how to obtain something using some other platform is not particularly helpful.

The problem is that you are attempting to replace a string containing a colon within a string contining a colon. cmd` gets confused as it doesn't know which colon of the three is which.

This solution is resticted, since it assumes that the part of the path to be removed is exactly the parent directory of referencepath. In the absence of more information, it's as far as I'm prepared to guess...

3 Comments

The beginning of my question says "As example, the following batch script should print ..\other\somedir\." So the desired output is `..\other\somedir`.
Fair 'nuff. You'll want me to use my glasses, not just wear them, eh?
Is it possible to remove the restriction that the removed path is exactly the parent directory? Some of my use cases have a deeper reference path, for example C:\Users\xyz\project\data\ , so that project\data\ needs to be removed.
1

…and an example which leverages PowerShell:

@Echo Off Set "referencePath=C:\Users\xyz\project" Set "absolutePath=C:\Users\xyz\other\somedir" Set "relativePath=" Set "_=" If /I Not "%CD%"=="%referencePath%" (Set "_=T" PushD "%referencePath%" 2>Nul || Exit /B) For /F "Delims=" %%A In (' PowerShell -C "Resolve-Path -LiteralPath '%absolutePath%' -Relative" ') Do Set "relativePath=%%A" If Defined _ PopD If Defined relativePath Echo %relativePath% Pause 

This obviously only works with actual existing paths

Comments

1

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.

Comments

0

Though I understand exactly what you want to do, I do not like this method.

Instead, why not use your environment path, where we will search for the relevant file in path.

@echo off for %%g in ("bin\startup-batch.cmd") do @set "pathTobat=%%~$PATH:g" echo "%pathTobat%" 

Comments

0

The solution of @MichaelS is good, but has problems:

  • If subdirectories have same or similar names as their parents, it doesn't work.
  • setlocal without endlocal
  • Same-path condition not in script
  • No command-line-interface (CLI)

Improved version follows: (save as setComRelPath.bat) (you need strlen.cmd from https://ss64.com/nt/syntax-strlen.html)

@echo off rem Usage: call setCompRelPath.bat C:\A\B C:\i\am\here rem echo "%compRelPath%" rem rem Alternernatively, rem set position_target=D:\44_Projekte\imagine\ rem set position_now=%CD%\ rem call setCompRelPath.bat rem echo "%compRelPath%" if not [%1]==[] set position_target=%1 if not [%2]==[] set position_now=%2 set __absolutePath=%position_now% set __referencePath=%position_target% if %__referencePath%==%__absolutePath% ( set complRelPath=.\ exit /b ) rem echo __referencePath=%__referencePath% rem echo __absolutePath=%__absolutePath% set relativePath= :LOOP for /F "tokens=1 delims=\" %%a in ("%__referencePath%") do (set ref=%%a) for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do (set rel=%%a) if /i not %ref%==%rel% goto RELLOOP call strlen.cmd "x%ref%" _strlen call set __referencePath=%%__referencePath:~%_strlen%%% call set __absolutePath=%%__absolutePath:~%_strlen%%% rem echo abs^> %__absolutePath% goto LOOP :RELLOOP for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do call :SUB_relpath %%a if not "%__absolutePath%"=="" goto RELLOOP goto FIN :SUB_relpath set ARG=%1 call strlen.cmd "x%ARG%" _strlen rem echo abs: %__absolutePath% // ARG=%ARG% // rel: %relativePath% call set __absolutePath=%%__absolutePath:~%_strlen%%% set relativePath=%relativePath%..\ exit /b :FIN set compRelPath=%relativePath%%__referencePath% 

If you want to see it functioning, uncomment the echo lines

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.