183

Is there a way in a Windows batch script to return an absolute path from a value containing a filename and/or relative path?

Given:

"..\"
"..\somefile.txt"

I need the absolute path relative to the batch file.

Example:

  • "somefile.txt" is located in "C:\Foo\"
  • "test.bat" is located in "C:\Foo\Bar".
  • User opens a command window in "C:\Foo" and calls Bar\test.bat ..\somefile.txt
  • In the batch file "C:\Foo\somefile.txt" would be derived from %1
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nathan Taylor
  • 24,423
  • 19
  • 99
  • 156
  • 1
    Relative paths are not the end of the story. Consider also **NTFS symlinks**: most likely you'll also need an analog of [`realpath`](http://www.manpagez.com/man/1/realpath/) for robust path normalization. – ulidtko Dec 10 '14 at 14:03
  • Probably you don't need an exact path at all! You can just add a base path: `SET FilePath=%CD%\%1` so that it could be like `C:\Foo\Bar\..\..\some\other\dir\file.txt`. Programs seem to understand such a complicated path. – Fr0sT Jun 19 '15 at 14:00
  • Lots of these answers are crazy over complicated, or just plain buggy -- but, this is actually a pretty easy thing to do in batch, [take a look at my answer below](http://stackoverflow.com/a/33404867/398630). – BrainSlugs83 Jul 03 '16 at 20:24

14 Answers14

180

In batch files, as in standard C programs, argument 0 contains the path to the currently executing script. You can use %~dp0 to get only the path portion of the 0th argument (which is the current script) - this path is always a fully qualified path.

You can also get the fully qualified path of your first argument by using %~f1, but this gives a path according to the current working directory, which is obviously not what you want.

Personally, I often use the %~dp0%~1 idiom in my batch file, which interpret the first argument relative to the path of the executing batch. It does have a shortcoming though: it miserably fails if the first argument is fully-qualified.

If you need to support both relative and absolute paths, you can make use of Frédéric Ménez's solution: temporarily change the current working directory.

Here's an example that'll demonstrate each of these techniques:

@echo off
echo %%~dp0 is "%~dp0"
echo %%0 is "%0"
echo %%~dpnx0 is "%~dpnx0"
echo %%~f1 is "%~f1"
echo %%~dp0%%~1 is "%~dp0%~1"

rem Temporarily change the current working directory, to retrieve a full path 
rem   to the first parameter
pushd .
cd %~dp0
echo batch-relative %%~f1 is "%~f1"
popd

If you save this as c:\temp\example.bat and the run it from c:\Users\Public as

c:\Users\Public>\temp\example.bat ..\windows

...you'll observe the following output:

%~dp0 is "C:\temp\"
%0 is "\temp\example.bat"
%~dpnx0 is "C:\temp\example.bat"
%~f1 is "C:\Users\windows"
%~dp0%~1 is "C:\temp\..\windows"
batch-relative %~f1 is "C:\Windows"

the documentation for the set of modifiers allowed on a batch argument can be found here: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/call

Adrien Plisson
  • 22,486
  • 6
  • 42
  • 73
  • 5
    You can handle `%0` and `%1` likewise: `%~dpnx0` for fully qualified path+name of the batchfile itself, `%~dpnx1` for fully qualified path+name of its first argument [if that's a filename at all]. (But how on earth would you name a file on a different drive if you wouldn't give that full path info on the commandline anyway?) – Kurt Pfeifle Aug 13 '10 at 19:45
  • This example is far more complex than @frédéric-ménez answer – srossross Nov 05 '14 at 17:52
  • 3
    This fails on NTFS symlinks. – ulidtko Dec 10 '14 at 14:00
  • @KurtPfeifle `d:\foo\..\bar\xyz.txt` can still be normalized. I recommend using this approach with @axel-heider's answer below (using a batch subroutine -- then you can do it on any variable, not just a numbered variable). – BrainSlugs83 Oct 29 '15 at 02:04
  • @ulidtko can you elaborate? What fails? -- What are you expecting to happen? -- Are you expecting to resolve to the original folder? (Because if it did *that*, I would call *that* a failure -- it's kind of the point of having symlinks in the first place...) – BrainSlugs83 Oct 29 '15 at 02:11
  • Note that %~dp0 does not work reliably if the current script was resolved from PATH, rather than relative to CD or by double-click. If your script is resolved from PATH, %~dp0 will act as if the script is in CD regardless of where it was found. – Tydaeus Oct 25 '17 at 21:58
  • Could someone add a link to the official documentation of these modifiers? – Bram Jul 19 '19 at 09:13
  • A nice resource on that matter: [wikibook](https://en.wikibooks.org/wiki/Windows_Batch_Scripting#Percent_tilde). Also a nice guide for batch scripting. – jmon12 Apr 27 '20 at 14:35
  • Thanks "%~f1" was exactly what I was looking for. If a relative path is passed as argument 1, it will be resolved as absolute path. Otherwise the received absolute path is kept. – Lothre1 Mar 16 '23 at 09:49
167

I came across a similar need this morning: how to convert a relative path into an absolute path inside a Windows command script.

The following did the trick:

@echo off

set REL_PATH=..\..\
set ABS_PATH=

rem // Save current directory and change to target directory
pushd %REL_PATH%

rem // Save value of CD variable (current directory)
set ABS_PATH=%CD%

rem // Restore original directory
popd

echo Relative path: %REL_PATH%
echo Maps to path: %ABS_PATH%
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Frédéric Ménez
  • 1,802
  • 1
  • 11
  • 7
  • 3
    This is REALLY useful. Are there any good resources for batch file tricks such as this? – Danny Parker Feb 17 '12 at 15:25
  • 8
    Don't think it's necessary to have the "pushd" followed by "cd". You can just do "pushd %REL_PATH%". That will save the current directory and switch to REL_PATH in one go. – Eddie Sullivan Sep 27 '12 at 14:15
  • 1
    @DannyParker A useful collection of Batch techniques and example scripts is http://www.robvanderwoude.com/batchfiles.php . See also http://stackoverflow.com/questions/245395/hidden-features-of-windows-batch-files – hfs Oct 31 '12 at 09:27
  • 6
    @EddieSullivan If changing to the directory fails (directory does not exist, no permission...), the pushd command fails as well and will not actually push a value onto the directory stack. The next call (and all consecutive calls) to popd will pop the wrong value. Using `pushd .` prevents this issue. – SvenS Feb 13 '13 at 15:48
  • 2
    Note that this won't work on paths that are files or on non-existent paths where there is a .. somewhere in the middle. It wasn't a show stopper for me, but it is a bit of a weakness to a reusable solution. – Mihai Danila Jun 05 '13 at 22:55
  • 1
    This will fail with UNC names, as popd might disconnect the temporary drive Windows assigns – Sebastian May 29 '14 at 10:31
  • 1
    This will fail with NTFS symlinks, too. – ulidtko Dec 10 '14 at 14:05
  • 1
    But if `pushd` fails, you probably don't want to continue, so just add `if errorlevel 1 goto failed` or something like that right after `pushd`. – rustyx Nov 17 '17 at 10:53
  • What's inside %CD% ? How does this work? – YTG Nov 09 '22 at 17:26
146

Most of these answers seem crazy over complicated and super buggy, here's mine -- it works on any environment variable, no %CD% or PUSHD/POPD, or for /f nonsense -- just plain old batch functions. -- The directory & file don't even have to exist.

CALL :NORMALIZEPATH "..\..\..\foo\bar.txt"
SET BLAH=%RETVAL%

ECHO "%BLAH%"

:: ========== FUNCTIONS ==========
EXIT /B

:NORMALIZEPATH
  SET RETVAL=%~f1
  EXIT /B
BrainSlugs83
  • 6,214
  • 7
  • 50
  • 56
  • 7
    This is indeed a clean, working and straightforward solution. – Razvan Dec 15 '15 at 16:01
  • 3
    Very concise. I'm using this techinique now, widely. – Paulo França Lacerda Oct 08 '18 at 02:51
  • 1
    Why is it %~dpfn1 and not simply %~f1? Is it some sort of workaround? %~f1 works fine for me on windows 7 and windows 10 at least. – il--ya Jun 25 '19 at 11:10
  • 5
    @il--ya looks like `%~f1` is just short for `%~dpfn1` -- so, feel free to use `%~f1` instead. -- in the long format `~` means remove the quotes, `d` means drive, `p` means path, `n` alone would be filename with no extension, but `fn` means filename with extension. the `1` means the first argument. -- (I believe this format predates Windows 7.) – BrainSlugs83 Aug 03 '19 at 03:54
  • @BrainSlugs83 these variables are available since XP (in cmd.exe), but their use haven't changed since then: %~f1 - expands %1 to a fully qualified path name; %~d1 - expands %1 to a drive letter only; %~p1 - expands %1 to a path only; %~n1 - expands %1 to a file name only; %~x1 - expands %1 to a file extension only. There is no special case for %~fn1, it doesn't produce name+extension, f switch simply takes precedence over d, p, n and x when they are combined. There seems to be no good reason for adding d, p and n. – il--ya Sep 25 '19 at 00:09
  • @il--ya feel free to ping me out of band to discuss it more, but it's off-topic to the answer at this point. The bottom line is that's how I learned to do it (from textbooks, in Windows 2000 Server; which is older than XP). -- Maybe something changed, or maybe the textbook I had was wrong, but it definitely had us using that format; at any rate, in Windows 10 the option is clearly redundant, and your `%~f1` appears to work fine. – BrainSlugs83 Oct 05 '19 at 02:04
  • 3
    Ah, you beat me to it! According to a comment in nt4 source code, file nt4\private\windows\cmd\clex.c (dated 27/08/1997), function MSCmdVar(), "// %~fi - expands %i to a fully qualified path name " Sorry for pestering you. I was hoping that I could get to the bottom of this misconception. I came across %~dpfn while reviewing some fairly recent code, pulled a couple of my hairs trying to understand what it's supposed to do (mind you I don't have many left), and set out to investigate how it sprang to life as a personal vendetta. – il--ya Oct 08 '19 at 05:11
  • 1
    Maybe it's a time to replace `%~dpfn1` with `%~f1`? This will make the answer better. – Sasha Jul 07 '20 at 12:51
  • @il--ya -- that's crazy! -- I went ahead and updated the answer (per @Sasha; sorry for the delay, I missed your original post!). -- I'm as dumbfounded as you though, it's just what we were taught back in college, and I remember it working back then... ‍♂️ -- IIRC, I think even the textbook used that pattern! -- Out of curiosity, Is `%~dpfni` mentioned anywhere in the source? -- Now, I'm wondering why they taught us that pattern... – BrainSlugs83 Jul 24 '20 at 02:40
  • 1
    even better: You can avoid the RETVAL variable by utilize kind of by_ref parameters. `SET %~1=%~f2` inside the NORMALIZEPATH sub will interpret the first parameter as variable name and set it to the absolute path of 2nd parameter – Merilix2 Feb 20 '23 at 16:28
38

Without having to have another batch file to pass arguments to (and use the argument operators), you can use FOR /F:

FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fi

where the i in %%~fi is the variable defined at /F %%i. eg. if you changed that to /F %%a then the last part would be %%~fa.

To do the same thing right at the command prompt (and not in a batch file) replace %% with %...

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • Not changing directory is important, because it makes the recipe robust to the fact that the `cd` command doesn't necessarily change the `%CD%` environment variable (if for example you're on drive `D:` and your target path is on `C:`) – jez Nov 20 '15 at 18:59
  • @jez For that you could use ```cd /D D:\on.other.drive``` – Sebastian Mar 21 '16 at 03:42
  • 2
    ```FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fi``` will fail if the ```..\relativePath``` contains spaces – Sebastian Mar 21 '16 at 05:04
  • 1
    It worked when I removed the /F flag and used %%~dpfnI. Note that ```for /?``` suggests to use capital letters for variable names, to avoid confusion with the substitution parameters – Sebastian Mar 21 '16 at 09:00
  • @SebastianGodelet removing the /F might be necessary for paths with spaces in them. – Peter Ritchie Mar 22 '16 at 20:23
  • 1
    @SebastianGodelet removing `/F` will not work on paths that don't exist, and it will resolve wildcard characters. If you want that, great, but if you want the functionality of `/F`, then you just need to use the `tokens` keyword: `FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI` – SvenS Dec 14 '16 at 11:34
  • Background: `For /F` splits up the content in the double quotes at specific characters (by default the same handling as for splitting command lines into an argument list, i.e. whitespace and semicolon) and puts each into its own variable, starting with the one provided (%%I). So you get "relative" in %%I and "Path" in %%J. `tokens=*` tells FOR to put everything into the first variable instead of splitting. – SvenS Dec 14 '16 at 11:37
  • The space issue can be resolved by defining another delimiter instead of space: `FOR /F "delims=|" %%i IN ("..\relative Path") DO echo absolute path: %%~fi` – Piper Jun 23 '17 at 06:58
15

This is to help fill in the gaps in Adrien Plisson's answer (which should be upvoted as soon as he edits it ;-):

you can also get the fully qualified path of your first argument by using %~f1, but this gives a path according to the current path, which is obviously not what you want.

unfortunately, i don't know how to mix the 2 together...

One can handle %0 and %1 likewise:

  • %~dpnx0 for fully qualified drive+path+name+extension of the batchfile itself,
    %~f0 also suffices;
  • %~dpnx1 for fully qualified drive+path+name+extension of its first argument [if that's a filename at all],
    %~f1 also suffices;

%~f1 will work independent of how you did specify your first argument: with relative paths or with absolute paths (if you don't specify the file's extension when naming %1, it will not be added, even if you use %~dpnx1 -- however.

But how on earth would you name a file on a different drive anyway if you wouldn't give that full path info on the commandline in the first place?

However, %~p0, %~n0, %~nx0 and %~x0 may come in handy, should you be interested in path (without driveletter), filename (without extension), full filename with extension or filename's extension only. But note, while %~p1 and %~n1 will work to find out the path or name of the first argument, %~nx1 and %~x1 will not add+show the extension, unless you used it on the commandline already.

Community
  • 1
  • 1
Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
  • the problem with `%~dpnx1` is that it gives the fully qualified path of the argument relative to the _current directory_, while the OP wants the path relative to _the directory where the batch file resides_. – Adrien Plisson Aug 17 '10 at 08:56
  • 1
    @Adrien Plisson: `%~dpnx1` gives fully qualified path of 1st argument. Not *relative* at all, and not relative to current dir either. The `d` is for drive, the `p` is for path, the `n` is for filename sans suffix, the `x` is for suffix, the `1` is for first argument. -- And Nathan's question was: *"Is there any way to return an absolute path from a value containing a filename and/or relative path?"* – Kurt Pfeifle Aug 17 '10 at 09:38
  • Both, absolute and relative paths [**usage**](https://github.com/it3xl/cmd-utils/blob/master/.test/abs_path_without_dots.bat) and [**source with an description**](https://github.com/it3xl/cmd-utils/blob/master/abs_path_without_dots.bat). Appreciative! – it3xl Feb 12 '18 at 11:54
12

Small improvement to BrainSlugs83's excellent solution. Generalized to allow naming the output environment variable in the call.

@echo off
setlocal EnableDelayedExpansion

rem Example input value.
set RelativePath=doc\build

rem Resolve path.
call :ResolvePath AbsolutePath %RelativePath%

rem Output result.
echo %AbsolutePath%

rem End.
exit /b

rem === Functions ===

rem Resolve path to absolute.
rem Param 1: Name of output variable.
rem Param 2: Path to resolve.
rem Return: Resolved absolute path.
:ResolvePath
    set %1=%~dpfn2
    exit /b

If run from C:\project output is:

C:\project\doc\build
12

You can also use batch functions for this:

@echo off
setlocal 

goto MAIN
::-----------------------------------------------
:: "%~f2" get abs path of %~2. 
::"%~fs2" get abs path with short names of %~2.
:setAbsPath
  setlocal
  set __absPath=%~f2
  endlocal && set %1=%__absPath%
  goto :eof
::-----------------------------------------------

:MAIN
call :setAbsPath ABS_PATH ..\
echo %ABS_PATH%

endlocal
Axel Heider
  • 557
  • 4
  • 14
5

I have not seen many solutions to this problem. Some solutions make use of directory traversal using CD and others make use of batch functions. My personal preference has been for batch functions and in particular, the MakeAbsolute function as provided by DosTips.

The function has some real benefits, primarily that it does not change your current working directory and secondly that the paths being evaluated don't even have to exist. You can find some helpful tips on how to use the function here too.

Here is an example script and its outputs:

@echo off

set scriptpath=%~dp0
set siblingfile=sibling.bat
set siblingfolder=sibling\
set fnwsfolder=folder name with spaces\
set descendantfolder=sibling\descendant\
set ancestorfolder=..\..\
set cousinfolder=..\uncle\cousin

call:MakeAbsolute siblingfile      "%scriptpath%"
call:MakeAbsolute siblingfolder    "%scriptpath%"
call:MakeAbsolute fnwsfolder       "%scriptpath%"
call:MakeAbsolute descendantfolder "%scriptpath%"
call:MakeAbsolute ancestorfolder   "%scriptpath%"
call:MakeAbsolute cousinfolder     "%scriptpath%"

echo scriptpath:       %scriptpath%
echo siblingfile:      %siblingfile%
echo siblingfolder:    %siblingfolder%
echo fnwsfolder:       %fnwsfolder%
echo descendantfolder: %descendantfolder%
echo ancestorfolder:   %ancestorfolder%
echo cousinfolder:     %cousinfolder%
GOTO:EOF

::----------------------------------------------------------------------------------
:: Function declarations
:: Handy to read http://www.dostips.com/DtTutoFunctions.php for how dos functions
:: work.
::----------------------------------------------------------------------------------
:MakeAbsolute file base -- makes a file name absolute considering a base path
::                      -- file [in,out] - variable with file name to be converted, or file name itself for result in stdout
::                      -- base [in,opt] - base path, leave blank for current directory
:$created 20060101 :$changed 20080219 :$categories Path
:$source http://www.dostips.com
SETLOCAL ENABLEDELAYEDEXPANSION
set "src=%~1"
if defined %1 set "src=!%~1!"
set "bas=%~2"
if not defined bas set "bas=%cd%"
for /f "tokens=*" %%a in ("%bas%.\%src%") do set "src=%%~fa"
( ENDLOCAL & REM RETURN VALUES
    IF defined %1 (SET %~1=%src%) ELSE ECHO.%src%
)
EXIT /b

And the output:

C:\Users\dayneo\Documents>myscript
scriptpath:       C:\Users\dayneo\Documents\
siblingfile:      C:\Users\dayneo\Documents\sibling.bat
siblingfolder:    C:\Users\dayneo\Documents\sibling\
fnwsfolder:       C:\Users\dayneo\Documents\folder name with spaces\
descendantfolder: C:\Users\dayneo\Documents\sibling\descendant\
ancestorfolder:   C:\Users\
cousinfolder:     C:\Users\dayneo\uncle\cousin

I hope this helps... It sure helped me :) P.S. Thanks again to DosTips! You rock!

Community
  • 1
  • 1
dayneo
  • 482
  • 5
  • 12
3

You can just concatenate them.

SET ABS_PATH=%~dp0 
SET REL_PATH=..\SomeFile.txt
SET COMBINED_PATH=%ABS_PATH%%REL_PATH%

it looks odd with \..\ in the middle of your path but it works. No need to do anything crazy :)

Kristen
  • 39
  • 1
1

In your example, from Bar\test.bat, DIR /B /S ..\somefile.txt would return the full path.

George Sisco
  • 621
  • 3
  • 9
  • That's not a bad idea... should I loop over the result of the DIR with a FOR and just grab the first result? – Nathan Taylor Oct 29 '09 at 23:10
  • I thought about the problem with multiple files. You didn't specify whether multiples were a problem, so I went with it. As for a FOR loop, I'm having trouble feeding "../" to a for loop, but ymmv. If you can't get it going on the DIR command, you might redirect output to a file and loop through that. I'm not saying the 'standard' way of extracting the path is bad, it's just that I thought mine did more of what you asked. Both solutions do 'something', and they both have their own set of issues. – George Sisco Oct 30 '09 at 14:02
  • Oh..and, if you loop through DIR successfully, you could overwrite redirect to a file and get the last result. If you reverse the order in a manner that is acceptable to you, you can get just the first result. If you have to loop through the file, reversing the order to gain the first result which would be in the last position may also help. The reason I suggest that is it may be easier to get and discard lots of things rather than actually deal with them. I don't know if my way of thinking makes sense to you. Just putting it out there as an idea. – George Sisco Oct 30 '09 at 14:09
  • If you need to normalize a path to a folder, I suggest this solution: https://stackoverflow.com/questions/48764076/normalize-file-path-in-batch?noredirect=1&lq=1 – uceumern Dec 14 '18 at 11:41
0

PowerShell is pretty common these days so I use it often as a quick way to invoke C# since that has functions for pretty much everything:

@echo off
set pathToResolve=%~dp0\..\SomeFile.txt
for /f "delims=" %%a in ('powershell -Command "[System.IO.Path]::GetFullPath( '%projectDirMc%' )"') do @set resolvedPath=%%a

echo Resolved path: %resolvedPath%

It's a bit slow, but the functionality gained is hard to beat unless without resorting to an actual scripting language.

stijn
  • 34,664
  • 13
  • 111
  • 163
0

stijn's solution works with subfolders under C:\Program Files (86)\,

@echo off
set projectDirMc=test.txt

for /f "delims=" %%a in ('powershell -Command "[System.IO.Path]::GetFullPath( '%projectDirMc%' )"') do @set resolvedPath=%%a

echo full path:    %resolvedPath%
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Files See all other answers

Directories

With .. being your relative path, and assuming you are currently in D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (the relative path)

returns absolute path e.g.

D:\Projects

Engineer
  • 8,529
  • 7
  • 65
  • 105
0
SET CD=%~DP0

SET REL_PATH=%CD%..\..\build\

call :ABSOLUTE_PATH    ABS_PATH   %REL_PATH%

ECHO %REL_PATH%

ECHO %ABS_PATH%

pause

exit /b

:ABSOLUTE_PATH

SET %1=%~f2

exit /b