如何将绝对路径批量转换为相对路径?我有一个目录A
的绝对路径和一个引用目录B
,我需要相对于B
A
的路径。例如,以下批处理脚本应打印..othersomedir
。
setlocal enabledelayedexpansion
set referencePath=C:Usersxyzproject
set absolutePath=C:Usersxyzothersomedir
set relativePath=...
echo %relativePath%
我尝试了relativePath=!absolutePath:%referencePath%=!
,但这会产生绝对路径C:Usersxyzothersomedir
。
我需要类似于 python 函数的东西os.path.relpath
:
>>> os.path.relpath("C:\Users\xyz\other\somedir", "C:\Users\xyz\project\")
"..\other\somedir"
我需要这个,因为我有一个批处理文件,其中包含类似于上述文件名的命令行参数。此批处理文件创建另一个批处理文件startup.bat
该文件设置一些环境变量并启动应用程序。startup.bat
可以通过网络调用,因此我必须使用相对路径。使用绝对路径时,环境变量将指向错误计算机上的文件。
这是一个快速的肮脏黑客:
@echo off
setlocal enabledelayedexpansion
set referencePath=C:Usersxyzproject
set absolutePath=C:Usersxyzothersomedir
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!
如果文件夹位于不同的驱动器上,这不会为您提供属性输出,因此您必须自己处理此特殊情况。
编辑(评论):嗯,这不可能那么难,你自己无法弄清楚。如果/
和混合在一起(这是一个坏主意 - 我们在Windows上!Windows 表示 \ 在路径中,UNIX 等表示/在路径中)您应该将/替换为:
SET referencePath=%referencePath:/=%
SET absolutePath=%absolutePath:/=%
如果路径相等,则无需执行任何操作:
IF %referencePath%==%absolutePath% (
SET complRelPath=.
GOTO WHATEVER
)
@ECHO OFF
setlocal enabledelayedexpansion
set referencePath=C:Usersxyzproject
set absolutePath=C:Usersxyzothersomedir
FOR %%a IN ("%absolutepath%") DO FOR %%r IN ("%referencepath%.") DO (
SET "abspath=%%~pa"
SET "relativepath=!abspath:%%~pr=..!"
)
echo %relativePath%
GOTO :EOF
如果您告诉我们您想要的输出是什么,那将很有帮助。告诉我们您当前代码的输出是什么,并且隐含地这不是您所期望的,然后如何使用其他平台获取某些内容并不是特别有用。
问题是您正在尝试替换包含冒号的字符串中的冒号。 cmd' 感到困惑,因为它不知道三个冒号中的哪个是哪个。
此解决方案已恢复,因为它假定要删除的路径部分恰好是referencepath
的父目录。在没有更多信息的情况下,据我所知......
...以及一个利用PowerShell
的示例:
@Echo Off
Set "referencePath=C:Usersxyzproject"
Set "absolutePath=C:Usersxyzothersomedir"
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
这显然仅适用于实际的现有路径
好吧,通常我强烈建议不要对文件或目录路径进行字符串操作,因为它很容易失败。但是对于像这样不依赖于现有路径的任务,似乎没有办法。
无论如何,为了以可靠的方式这样做,必须考虑以下问题:
- Windows 使用不区分大小写的路径,因此也以这种方式进行所有路径比较!
- 避免子字符串替换,因为使用
=
符号和其他一些字符很麻烦! - 确保使用
for
循环元变量的~f
修饰符正确解析提供的路径!这确保了输入路径确实是绝对的,它们不包含双分隔符(\
),并且没有具有.
和..
的序列(如abc...def
,相当于def
),这使得比较变得困难。 - 考虑到路径可能以丑陋的方式提供,例如尾随
或
.
,错误的引号(如abc"def ghi"jkl
),或使用错误的路径分隔符(/
而不是,这是Windows标准)。
好的,让我们转向我编写的一个脚本,用于派生两个绝对路径之间的公共路径和相对路径,关于所有上述项目(请参阅rem
注释):
@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
请注意,此方法不支持 UNC 路径。
虽然我完全理解你想做什么,但我不喜欢这种方法。
相反,为什么不使用您的环境路径,我们将在其中搜索路径中的相关文件。
@echo off
for %%g in ("binstartup-batch.cmd") do @set "pathTobat=%%~$PATH:g"
echo "%pathTobat%"
@MichaelS的解决方案很好,但有问题:
- 如果子目录与其父目录具有相同或相似的名称,则它不起作用。
- 设置本地,不带结束本地
- 脚本中未包含相同路径条件
- 无命令行界面 (CLI)
改进版本如下:(另存为setComRelPath.bat
)(您需要从 https://ss64.com/nt/syntax-strlen.htmlstrlen.cmd
)
@echo off
rem Usage: call setCompRelPath.bat C:AB C:iamhere
rem echo "%compRelPath%"
rem
rem Alternernatively,
rem set position_target=D:44_Projekteimagine
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%
如果您想查看它的功能,请取消注释echo
行