Resolver ruta absoluta desde ruta relativa y / o nombre de archivo

155

¿Hay alguna manera en un script por lotes de Windows para devolver una ruta absoluta desde un valor que contiene un nombre de archivo y / o ruta relativa?

Dado:

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

Necesito la ruta absoluta relativa al archivo por lotes.

Ejemplo:

  • "somefile.txt" se encuentra en "C: \ Foo \"
  • "test.bat" se encuentra en "C: \ Foo \ Bar".
  • El usuario abre una ventana de comando en "C: \ Foo" y llama Bar\test.bat ..\somefile.txt
  • En el archivo por lotes "C: \ Foo \ somefile.txt" se derivaría de %1
Nathan Taylor
fuente
1
Los caminos relativos no son el final de la historia. Considere también los enlaces simbólicos NTFS : lo más probable es que también necesite un análogo realpathpara una normalización de ruta sólida.
ulidtko
¡Probablemente no necesites una ruta exacta en absoluto! Simplemente puede agregar una ruta base: SET FilePath=%CD%\%1para que sea así C:\Foo\Bar\..\..\some\other\dir\file.txt. Los programas parecen entender un camino tan complicado.
Viernes
Muchas de estas respuestas son locas por complicadas, o simplemente con errores, pero, en realidad, esto es algo bastante fácil de hacer por lotes, eche un vistazo a mi respuesta a continuación .
BrainSlugs83

Respuestas:

160

En los archivos por lotes, como en los programas C estándar, el argumento 0 contiene la ruta al script que se está ejecutando actualmente. Puede usar %~dp0para obtener solo la parte de ruta del argumento 0 (que es el script actual): esta ruta siempre es una ruta totalmente calificada.

También puede obtener la ruta totalmente calificada de su primer argumento utilizando %~f1, pero esto proporciona una ruta de acuerdo con el directorio de trabajo actual, que obviamente no es lo que desea.

Personalmente, a menudo uso el %~dp0%~1idioma en mi archivo por lotes, que interpreta el primer argumento relativo a la ruta del lote en ejecución. Sin embargo, tiene una deficiencia: falla miserablemente si el primer argumento está completamente calificado.

Si necesita admitir rutas relativas y absolutas, puede utilizar la solución de Frédéric Ménez : cambiar temporalmente el directorio de trabajo actual.

Aquí hay un ejemplo que demostrará cada una de estas técnicas:

@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

Si guarda esto como c: \ temp \ example.bat y lo ejecuta desde c: \ Users \ Public como

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

... observará el siguiente resultado:

%~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"

la documentación para el conjunto de modificadores permitidos en un argumento por lotes se puede encontrar aquí: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call

Adrien Plisson
fuente
44
Puede manejar %0y de la %1misma manera: %~dpnx0para la ruta totalmente calificada + nombre del archivo por lotes en sí, %~dpnx1para la ruta totalmente calificada + nombre de su primer argumento [si ese es un nombre de archivo en absoluto]. (Pero, ¿cómo demonios nombraría un archivo en una unidad diferente si no proporcionara esa información de ruta completa en la línea de comandos de todos modos?)
Kurt Pfeifle
Este ejemplo es mucho más complejo que la respuesta de @ frédéric-ménez
srossross el
3
Esto falla en los enlaces simbólicos NTFS.
ulidtko
@KurtPfeifle d:\foo\..\bar\xyz.txttodavía se puede normalizar. Recomiendo usar este enfoque con la respuesta de @ axel-heider a continuación (usando una subrutina por lotes, luego puede hacerlo en cualquier variable, no solo en una variable numerada).
BrainSlugs83
@ulidtko puedes elaborar? Que falla - ¿Qué esperas que pase? - ¿Espera resolver la carpeta original? (Porque si hiciera eso , llamaría a eso un fracaso, es el punto de tener enlaces simbólicos en primer lugar ...)
BrainSlugs83
151

Me encontré con una necesidad similar esta mañana: cómo convertir una ruta relativa en una ruta absoluta dentro de un script de comando de Windows.

Lo siguiente hizo el truco:

@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%
Frédéric Ménez
fuente
2
Esto es REALMENTE útil. ¿Hay buenos recursos para trucos de archivos por lotes como este?
Danny Parker
8
No piense que es necesario tener el "pushd" seguido de "cd". Simplemente puede hacer "pushd% REL_PATH%". Eso guardará el directorio actual y cambiará a REL_PATH de una vez.
Eddie Sullivan
1
@DannyParker Una colección útil de técnicas de Batch y scripts de ejemplo es robvanderwoude.com/batchfiles.php . Consulte también stackoverflow.com/questions/245395/…
hfs el
66
@EddieSullivan Si falla el cambio al directorio (el directorio no existe, no hay permiso ...), el comando pushd también falla y en realidad no empujará un valor a la pila del directorio. La siguiente llamada (y todas las llamadas consecutivas) a popd mostrará el valor incorrecto. El uso pushd .previene este problema.
SvenS
1
Tenga en cuenta que esto no funcionará en rutas que son archivos o en rutas inexistentes donde hay un ... en algún lugar en el medio. No fue un obstáculo para mí, pero es una debilidad para una solución reutilizable.
Mihai Danila
97

La mayoría de estas respuestas parecen locas por complicadas y súper defectuosas, aquí está la mía: funciona en cualquier variable de entorno, no %CD%o PUSHD/ POPDo for /fsin sentido, simplemente funciones de lote antiguas. - El directorio y el archivo ni siquiera tienen que existir.

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

ECHO "%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~f1
  EXIT /B
BrainSlugs83
fuente
77
De hecho, esta es una solución limpia, funcional y directa.
Razvan
3
Muy conciso. Estoy usando esta técnica ahora, ampliamente.
Paulo França Lacerda
1
¿Por qué es% ~ dpfn1 y no simplemente% ~ f1? ¿Es algún tipo de solución? % ~ f1 funciona bien para mí en Windows 7 y Windows 10 al menos.
il - ya
1
@ il - ya parece que %~f1es la abreviatura de, por %~dpfn1lo tanto, siéntase libre de usar %~f1en su lugar. 🙂 - en el formato largo ~significa eliminar las comillas, dsignifica unidad, psignifica ruta, nsolo sería nombre de archivo sin extensión, pero fnsignifica nombre de archivo con extensión. el 1significa el primer argumento. - (Creo que este formato es anterior a Windows 7.)
BrainSlugs83
1
¡Ah, me ganaste! Según un comentario en el código fuente nt4, el archivo nt4 \ private \ windows \ cmd \ clex.c (con fecha 27/08/1997), función MSCmdVar (), "//% ~ fi - expande% i a una ruta totalmente calificada nombre "Perdón por molestarte. Esperaba poder llegar al fondo de este error. Me encontré con% ~ dpfn mientras revisaba un código bastante reciente, me arranqué un par de pelos tratando de entender lo que se suponía que debía hacer (ten en cuenta que no me quedan muchos), y me dispuse a investigar cómo surgió. Una venganza personal.
il - ya
35

Sin tener que tener otro archivo por lotes para pasar argumentos (y usar los operadores de argumentos), puede usar FOR /F:

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

donde iin %%~fies la variable definida en /F %%i. p.ej. si cambiaste eso, /F %%aentonces la última parte sería %%~fa.

Para hacer lo mismo en el símbolo del sistema (y no en un archivo por lotes), reemplace %%con %...

Peter Ritchie
fuente
Directorio no cambia es importante, ya que hace que la receta robusta al hecho de que el cdcomando no cambia necesariamente la %CD%variable de entorno (por ejemplo, si usted está en la unidad D:y la ruta de destino está en C:)
Jez
@jez Para eso podrías usarcd /D D:\on.other.drive
Sebastian
1
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fifallará si ..\relativePathcontiene espacios
Sebastian
1
Funcionó cuando eliminé el indicador / F y usé %% ~ dpfnI. Tenga en cuenta que for /?sugiere usar letras mayúsculas para nombres de variables, para evitar confusión con los parámetros de sustitución
Sebastian
1
La eliminación de @SebastianGodelet /Fno funcionará en rutas que no existen, y resolverá los caracteres comodín. Si quieres eso, genial, pero si quieres la funcionalidad de /F, entonces solo necesitas usar la tokenspalabra clave:FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI
SvenS
15

Esto es para ayudar a llenar los vacíos en la respuesta de Adrien Plisson (que debe ser votado tan pronto como lo edite ;-):

También puede obtener la ruta totalmente calificada de su primer argumento utilizando% ~ f1, pero esto le da una ruta de acuerdo con la ruta actual, que obviamente no es lo que desea.

desafortunadamente, no sé cómo mezclar los 2 juntos ...

Uno puede manejar %0y %1asimismo:

  • %~dpnx0para una unidad totalmente calificada + ruta + nombre + extensión del lote por sí mismo,
    %~f0también es suficiente;
  • %~dpnx1para una unidad completa + ruta + nombre + extensión de su primer argumento [si es un nombre de archivo],
    %~f1también es suficiente;

%~f1funcionará independientemente de cómo especificó su primer argumento: con rutas relativas o con rutas absolutas (si no especifica la extensión del archivo al nombrar %1, no se agregará, incluso si usa %~dpnx1).

Pero, ¿cómo demonios nombraría un archivo en una unidad diferente de todos modos si no proporcionara esa información de ruta completa en la línea de comandos en primer lugar?

Sin embargo, %~p0, %~n0, %~nx0y %~x0puede ser útil, si está interesado en la trayectoria (sin letraUnidad), nombre de archivo (sin extensión), nombre completo del archivo con la extensión o sólo la extensión del nombre de archivo. Pero tenga en cuenta, while %~p1y %~n1trabajará para encontrar la ruta o el nombre del primer argumento, %~nx1y %~x1no agregará + mostrará la extensión, a menos que ya lo haya usado en la línea de comandos.

Kurt Pfeifle
fuente
El problema %~dpnx1es que proporciona la ruta completa del argumento en relación con el directorio actual , mientras que el OP desea la ruta relativa al directorio donde reside el archivo por lotes .
Adrien Plisson
1
@Adrien Plisson: %~dpnx1da la ruta totalmente calificada del primer argumento. No es relativo en absoluto, y tampoco es relativo al directorio actual. El des para la unidad, pes para la ruta, nes para el nombre de archivo sin sufijo, xes para el sufijo, 1es para el primer argumento. - Y la pregunta de Nathan fue: "¿Hay alguna forma de devolver una ruta absoluta desde un valor que contenga un nombre de archivo y / o una ruta relativa?"
Kurt Pfeifle
Tanto el uso de rutas absolutas y relativas como la fuente con una descripción . ¡Agradecido!
it3xl
10

También puede usar funciones por lotes para esto:

@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
fuente
9

Pequeña mejora a la excelente solución de BrainSlugs83 . Generalizado para permitir nombrar la variable de entorno de salida en la llamada.

@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

Si se ejecuta desde la C:\projectsalida es:

C:\project\doc\build

fuente
4

No he visto muchas soluciones a este problema. Algunas soluciones utilizan el recorrido del directorio mediante CD y otras utilizan funciones por lotes. Mi preferencia personal ha sido para las funciones por lotes y, en particular, la función MakeAbsolute según lo dispuesto por DosTips.

La función tiene algunos beneficios reales, principalmente porque no cambia su directorio de trabajo actual y, en segundo lugar, que las rutas que se evalúan ni siquiera tienen que existir. Puede encontrar algunos consejos útiles sobre cómo usar la función aquí también.

Aquí hay un script de ejemplo y sus resultados:

@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

Y la salida:

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

Espero que esto ayude ... Seguro que me ayudó :) PD Gracias de nuevo a DosTips! ¡Tú Molas!

dayneo
fuente
gracias @ulidtko. No he intentado contra enlaces simbólicos antes.
dayneo
2

Puedes concatenarlos.

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

parece extraño con \ .. \ en el medio de su camino pero funciona. No hay necesidad de hacer nada loco :)

Kristen
fuente
Esto simplemente funciona. Se puede simplificar aún másecho %~dp0..\SomeFile.txt
cowlinator
1

En su ejemplo, desde Bar \ test.bat, DIR / B / S .. \ somefile.txt devolvería la ruta completa.

George Sisco
fuente
Esa no es una mala idea ... ¿debería recorrer el resultado del DIR con un FOR y simplemente tomar el primer resultado?
Nathan Taylor
Pensé en el problema con varios archivos. No especificó si los múltiplos eran un problema, por lo que fui con él. En cuanto a un bucle FOR, tengo problemas para alimentar "../" a un bucle for, pero ymmv. Si no puede hacerlo funcionar con el comando DIR, puede redirigir la salida a un archivo y recorrerlo. No digo que la forma "estándar" de extraer el camino sea mala, es solo que pensé que la mía hizo más de lo que pediste. Ambas soluciones hacen 'algo', y ambas tienen su propio conjunto de problemas.
George Sisco
Ah ... y, si recorres DIR con éxito, podrías sobrescribir la redirección a un archivo y obtener el último resultado. Si invierte el orden de una manera que sea aceptable para usted, puede obtener solo el primer resultado. Si tiene que recorrer el archivo, invertir el orden para obtener el primer resultado que estaría en la última posición también puede ayudar. La razón por la que sugiero es que puede ser más fácil obtener y descartar muchas cosas en lugar de lidiar con ellas. No sé si mi forma de pensar tiene sentido para ti. Solo poniéndolo ahí como una idea.
George Sisco
Si necesita normalizar una ruta a una carpeta, le sugiero esta solución: stackoverflow.com/questions/48764076/…
uceumern
0

PowerShell es bastante común en estos días, así que lo uso a menudo como una forma rápida de invocar C # ya que tiene funciones para casi todo:

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

Es un poco lento, pero la funcionalidad obtenida es difícil de superar a menos que no se recurra a un lenguaje de script real.

stijn
fuente
0

La solución de stijn funciona con subcarpetas debajo 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%
Irq Mishell
fuente
0

Archivos Ver todas las otras respuestas

Directorios

Con ..ser su ruta relativa, y suponiendo que usted está actualmente en D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (la ruta relativa)

devuelve la ruta absoluta, por ejemplo

D:\Projects

Ingeniero
fuente
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
Sergei Sachkov
fuente