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
realpath
for robust path normalization.
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.
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://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call
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%
pushd .
prevents this issue.
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
%~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.)
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 %
...
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:
)
cd /D D:\on.other.drive
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fi
will fail if the ..\relativePath
contains spaces
for /?
suggests to use capital letters for variable names, to avoid confusion with the substitution parameters
/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
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.
%~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.
%~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?"
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
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
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!
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 :)
echo %~dp0..\SomeFile.txt
In your example, from Bar\test.bat, DIR /B /S ..\somefile.txt would return the full path.
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'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%
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
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
Success story sharing
%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?)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).