关于Windows:从相对路径和/或文件名解析绝对路径

Resolve absolute path from relative path and/or file name

Windows批处理脚本中是否有方法从包含文件名和/或相对路径的值返回绝对路径?

鉴于:

1
2
".."
"..\somefile.txt"

我需要相对于批处理文件的绝对路径。

例子:

  • "somefile.txt"位于"c:foo"
  • "test.bat"位于"c:fooar"中。
  • 用户在"c:foo"中打开命令窗口,调用Bar\test.bat ..\somefile.txt
  • 批处理文件"c:foosomefile.txt"将从%1派生。


在批处理文件中,与标准C程序一样,参数0包含当前执行脚本的路径。您可以使用%~dp0只获取第0个参数的路径部分(即当前脚本)-此路径始终是完全限定的路径。

您也可以通过使用%~f1获得第一个参数的完全限定路径,但这会根据当前工作目录给出一个路径,显然这不是您想要的路径。

就个人而言,我经常在批处理文件中使用%~dp0%~1习语,它解释与执行批处理路径相关的第一个参数。不过,它确实有一个缺点:如果第一个论点完全符合要求,它就惨不忍睹地失败了。

如果需要同时支持相对路径和绝对路径,可以使用fr_d_ric m_nez的解决方案:临时更改当前工作目录。

下面是一个例子,将演示这些技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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

如果您将其保存为c: empexample.bat,并从c:userspublic运行为

C:userspublic> empexample.bat..windows

…您将看到以下输出:

1
2
3
4
5
6
%~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"


今天早上我遇到了一个类似的需求:如何在Windows命令脚本中将相对路径转换为绝对路径。

以下是技巧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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%


这些答案中的大多数似乎都是对复杂和超小型车的疯狂,这里是我的——它适用于任何环境变量,没有%CD%PUSHDPOPDfor /f的废话——只是简单的旧批处理函数。--目录文件甚至不必存在。

1
2
3
4
5
6
7
8
9
10
11
CALL :NORMALIZEPATH"..\..\..\foo\bar.txt"
SET BLAH=%RETVAL%

ECHO"%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~dpfn1
  EXIT /B


不需要另一个批处理文件来传递参数(并使用参数运算符),您可以使用FOR /F

1
2
FOR /F %%i IN ("..
elativePath") DO echo absolute path: %%~fi

其中%%~fi中的i是在/F %%i中定义的变量。如果你把它改成/F %%a,那么最后一部分就是%%~fa

为了在命令提示下(而不是批处理文件中)正确地执行相同的操作,将%%替换为%


这有助于填补阿德里安·普里松(Adrien Plisson)答案中的空白(在他编辑答案后应立即进行上票);—:

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...

可以同样处理%0%1

  • %~dpnx0用于完全限定的驱动器+路径+名称+批处理文件本身的扩展名,%~f0也足够了;
  • %~dpnx1,用于完全限定的驱动器+路径+名称+第一个参数的扩展名[如果这是文件名],%~f1也足够了;

%~f1的工作方式与您指定第一个参数的方式无关:使用相对路径或绝对路径(如果在命名%1时未指定文件扩展名,则不会添加该扩展名,即使使用%~dpnx1—但是。

但是,如果你一开始不在命令行上提供完整的路径信息,你到底会如何在不同的驱动器上命名一个文件呢?

但是,如果您对路径(不带驱动器号)、文件名(不带扩展名)、带扩展名的完整文件名或文件名的扩展名感兴趣,那么%~p0%~n0%~nx0%~x0可能很有用。但请注意,虽然%~p1%~n1将努力找出第一个参数的路径或名称,但%~nx1%~x1不会添加+显示扩展名,除非您已经在命令行上使用它。


您还可以使用批处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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


对Brainslugs83优秀解决方案的小改进。通用化以允许在调用中命名输出环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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

如果从C:\project输出为:

1
C:\project\doc\build

我没有看到很多解决这个问题的方法。一些解决方案使用CD进行目录遍历,另一些解决方案使用批处理函数。我个人倾向于批处理功能,特别是dostips提供的makeabsolute功能。

该函数有一些真正的好处,主要是它不会改变当前的工作目录,其次是被评估的路径甚至不必存在。您也可以在这里找到一些关于如何使用函数的有用提示。

下面是一个示例脚本及其输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@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

输出:

1
2
3
4
5
6
7
8
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

我希望这有助于…它确实帮助了我:)再次感谢Dostips!你摇滚!


您可以将它们连接起来。

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

在你的道路中间有..看起来很奇怪,但它起作用了。不需要做任何疯狂的事情:)


在您的示例中,从bar est.bat,dir/b/s..somefile.txt将返回完整路径。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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


文件查看所有其他答案

名录

由于..是你的相对路径,假设你目前在D:\Projects\EditorProject中:

cd .. & cd & cd EditorProject(相对路径)

返回绝对路径,例如

D:\Projects


Stijn的解决方案与C:\Program Files (86)\下的子文件夹一起工作,

1
2
3
4
5
6
@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%

PowerShell现在很常见,所以我经常使用它作为一种快速调用C的方法,因为它具有几乎所有功能:

1
2
3
4
5
@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%

虽然速度有点慢,但除非不使用实际的脚本语言,否则很难击败所获得的功能。