代码之家  ›  专栏  ›  技术社区  ›  pschill

批处理从绝对路径获取相对路径

  •  1
  • pschill  · 技术社区  · 7 年前

    如何在批处理中将绝对路径转换为相对路径?我有一个目录的绝对路径 A 和参考目录 B ,我需要路径 A. 相对于 B . 例如,应打印以下批处理脚本 ..\other\somedir\ .

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

    我试过了 relativePath=!absolutePath:%referencePath%=! ,但这会产生绝对路径 C:\Users\xyz\other\somedir\ .

    我需要一些类似于python函数的东西 os.path.relpath :

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

    我需要这个,因为我有一个批处理文件,其中的命令行参数与上述文件名类似。此批处理文件创建另一个批处理文件 startup.bat 它设置一些环境变量并启动应用程序。这个 启动。球棒 可能通过网络调用,所以我必须使用相对路径。对于绝对路径,环境变量将指向错误计算机上的文件。

    6 回复  |  直到 4 年前
        1
  •  2
  •   MichaelS    7 年前

    下面是一个快速的“n”肮脏黑客:

    @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!
    

    如果文件夹位于不同的驱动器上,这将不会为您提供propper输出,因此您必须自己处理这种特殊情况。

    编辑 (评论):嗯,这不会有你自己想不出来的那么难。如果 / \ 是混合的(这是个坏主意-我们在Windows上!Windows表示\在路径中,UNIX等。表示/在路径中),您应该替换/为:

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

    如果路径相等,则无需执行以下操作:

    IF %referencePath%==%absolutePath% (
        SET complRelPath=.\
        GOTO WHATEVER
    )
    
        2
  •  2
  •   Magoo    7 年前
    @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
    

    如果你能告诉我们你想要的产量是多少,那将很有帮助。告诉我们您当前代码的输出是什么,并且隐含地说这不是您所期望的,然后如何使用其他平台获得一些东西并没有特别大的帮助。

    问题是,您试图在包含冒号的字符串中替换包含冒号的字符串。cmd `很困惑,因为它不知道三个冒号中的哪个是哪个。

    此解决方案是受限的,因为它假定要删除的路径部分正好是的父目录 referencepath . 在没有更多信息的情况下,我只能猜测。。。

        3
  •  1
  •   Compo    7 年前

    以及一个利用 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
    

    这显然只适用于实际的现有路径

        4
  •  1
  •   aschipfl    4 年前

    通常,我强烈建议不要对文件或目录路径进行字符串操作,因为这很容易失败。但对于这样一个不依赖现有路径的任务,似乎没有出路。

    无论如何,为了以可靠的方式这样做,必须考虑以下问题:

    • Windows使用不区分大小写的路径,所有路径比较也都是以这种方式进行的!
    • 避免子字符串替换,因为它很麻烦 = -符号和其他一些字符!
    • 确保使用正确解析提供的路径 ~f -的修改器 for -循环元变量!这确保了输入路径确实是绝对的,它们不包含双分隔符( \\ ),并且没有 . .. (如 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路径。

        5
  •  0
  •   Gerhard    7 年前

    虽然我完全理解你想要做什么,但我不喜欢这种方法。

    相反,为什么不使用您的环境路径,我们将在其中搜索路径中的相关文件。

    @echo off
    for %%g in ("bin\startup-batch.cmd") do @set "pathTobat=%%~$PATH:g"
    echo "%pathTobat%"
    
        6
  •  0
  •   jifb    4 年前

    @MichaelS的解决方案很好,但也存在一些问题:

    • 如果子目录的名称与其父目录的名称相同或相似,则不起作用。
    • 无endlocal的setlocal
    • 脚本中不存在相同的路径条件
    • 无命令行界面(CLI)

    改进版本如下:(另存为 setComRelPath.bat )(您需要 strlen.cmd 从…起 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%
    

    如果要查看其功能,请取消注释 echo 线