Resolver caminho absoluto a partir do caminho relativo e / ou nome do arquivo

155

Existe uma maneira em um script em lote do Windows retornar um caminho absoluto de um valor que contém um nome de arquivo e / ou caminho relativo?

Dado:

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

Eu preciso do caminho absoluto em relação ao arquivo em lotes.

Exemplo:

  • "somefile.txt" está localizado em "C: \ Foo \"
  • "test.bat" está localizado em "C: \ Foo \ Bar".
  • O usuário abre uma janela de comando em "C: \ Foo" e chama Bar\test.bat ..\somefile.txt
  • No arquivo em lotes "C: \ Foo \ somefile.txt" seria derivado de %1
Nathan Taylor
fonte
1
Caminhos relativos não são o fim da história. Considere também links simbólicos NTFS : provavelmente você também precisará de um análogo realpathpara uma normalização de caminho robusta.
ulidtko
Provavelmente você não precisa de um caminho exato! Você pode apenas adicionar um caminho base: SET FilePath=%CD%\%1para que possa ser assim C:\Foo\Bar\..\..\some\other\dir\file.txt. Os programas parecem entender um caminho tão complicado.
Fr0sT
Muitas dessas respostas são loucas por complicados, ou simplesmente buggy - mas, na verdade, é uma coisa muito fácil de fazer em lote, dê uma olhada na minha resposta abaixo .
precisa saber é o seguinte

Respostas:

160

Nos arquivos em lote, como nos programas C padrão, o argumento 0 contém o caminho para o script atualmente em execução. Você pode usar %~dp0para obter apenas a parte do caminho do argumento 0 (que é o script atual) - esse caminho é sempre um caminho totalmente qualificado.

Você também pode obter o caminho completo do seu primeiro argumento usando %~f1, mas isso fornece um caminho de acordo com o diretório de trabalho atual, que obviamente não é o que você deseja.

Pessoalmente, geralmente uso o %~dp0%~1idioma no meu arquivo em lotes, que interpreta o primeiro argumento em relação ao caminho do lote em execução. Porém, ele tem uma falha: falha miseravelmente se o primeiro argumento for totalmente qualificado.

Se você precisar oferecer suporte a caminhos relativos e absolutos, poderá usar a solução de Frédéric Ménez : altere temporariamente o diretório de trabalho atual.

Aqui está um exemplo que demonstrará cada uma dessas 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

Se você salvar isso como c: \ temp \ example.bat e executá-lo em c: \ Users \ Public como

c: \ Usuários \ Público> \ temp \ example.bat .. \ windows

... você observará a seguinte saída:

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

a documentação para o conjunto de modificadores permitidos em um argumento em lote pode ser encontrada aqui: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call

Adrien Plisson
fonte
4
Você pode manipular %0e da %1mesma forma: %~dpnx0para o caminho completo + nome do próprio arquivo em lote, %~dpnx1para o caminho completo + nome do seu primeiro argumento [se for um nome de arquivo]. (Mas como na terra que você nomear um arquivo em uma unidade diferente se você não daria essa informação caminho completo na linha de comando afinal?)
Kurt Pfeifle
Este exemplo é muito mais complexa do que @ resposta Frédéric-Ménez
srossross
3
Isso falha nos links simbólicos do NTFS.
ulidtko
@KurtPfeifle d:\foo\..\bar\xyz.txtainda pode ser normalizado. Eu recomendo usar essa abordagem com a resposta de @ axel-heider abaixo (usando uma sub-rotina em lote - você pode fazer isso em qualquer variável, não apenas em uma variável numerada).
BrainSlugs83
@ulidtko você pode elaborar? O que falha? - O que você está esperando que aconteça? - Você espera resolver para a pasta original? (Porque se ele fez isso , eu chamaria que um fracasso - é uma espécie de ponto de ter links simbólicos em primeiro lugar ...)
BrainSlugs83
151

Me deparei com uma necessidade semelhante nesta manhã: como converter um caminho relativo em um caminho absoluto dentro de um script de comando do Windows.

O seguinte fez o truque:

@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
fonte
2
Isso é realmente útil. Existem bons recursos para truques de arquivos em lote como este?
Danny Parker
8
Não pense que é necessário ter o "pushd" seguido de "cd". Você pode simplesmente fazer "pushd% REL_PATH%". Isso salvará o diretório atual e mudará para REL_PATH de uma só vez.
Eddie Sullivan
1
@DannyParker Uma coleção útil de técnicas em lote e scripts de exemplo é robvanderwoude.com/batchfiles.php . Veja também stackoverflow.com/questions/245395/…
hfs 31/10/12
6
@EddieSullivan Se a alteração para o diretório falhar (o diretório não existe, nenhuma permissão ...), o comando pushd também falhará e não enviará realmente um valor para a pilha de diretórios. A próxima chamada (e todas as chamadas consecutivas) ao popd exibirá o valor errado. O uso pushd .evita esse problema.
Svens
1
Observe que isso não funcionará em caminhos que são arquivos ou em caminhos inexistentes onde existe um .. em algum lugar no meio. Não foi um impedimento para mim, mas é um pouco de fraqueza para uma solução reutilizável.
Mihai Danila
97

A maioria dessas respostas parece louca por complicados e super bugs, aqui está a minha - ela funciona em qualquer variável de ambiente, nenhuma %CD%ou PUSHD/ POPDou for /fbobagem - apenas funções simples de lote antigas. - O diretório e o arquivo nem precisam existir.

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

ECHO "%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~f1
  EXIT /B
BrainSlugs83
fonte
7
Esta é realmente uma solução limpa, funcional e direta.
Razvan
3
Muito conciso. Agora estou usando essa técnica amplamente.
Paulo França Lacerda
1
Por que é% ~ dpfn1 e não simplesmente% ~ f1? É algum tipo de solução alternativa? % ~ f1 funciona bem para mim no Windows 7 e Windows 10, pelo menos.
il - ya
1
@ il - parece que %~f1é apenas uma abreviação de %~dpfn1- então, fique à vontade para usar %~f1. 🙂 - no formato longo ~significa remover as aspas, dsignifica dirigir, psignifica caminho, nsozinho seria o nome do arquivo sem extensão, mas fnsignifica o nome do arquivo com extensão. o 1significa o primeiro argumento. - (Eu acredito que este formato antecede o Windows 7.)
BrainSlugs83
1
Ah, você me venceu! De acordo com um comentário no código-fonte nt4, o arquivo nt4 \ private \ windows \ cmd \ clex.c (datado de 27/08/1997), função MSCmdVar (), "//% ~ fi - expande% i para um caminho totalmente qualificado nome "Desculpe por incomodar você. Eu esperava poder chegar ao fundo desse equívoco. Me deparei com% ~ dpfn enquanto revisava um código relativamente recente, puxei alguns fios para entender o que deveria fazer (lembre-se de que não tenho muito o que fazer) e parti para investigar como ele surgiu como uma vingança pessoal.
il - ya
35

Sem ter que ter outro arquivo em lote para transmitir argumentos (e usar os operadores de argumento), você pode usar FOR /F:

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

onde iin %%~fié a variável definida em /F %%i. por exemplo. se você mudou para /F %%aentão a última parte seria %%~fa.

Para fazer a mesma coisa no prompt de comando (e não em um arquivo em lotes), substitua %%por %...

Peter Ritchie
fonte
Não mudar de diretório é importante, porque faz com que a receita robusta para o fato de que o cdcomando não necessariamente alterar a %CD%variável de ambiente (se, por exemplo, você está na unidade D:e seu caminho de destino está em C:)
Jez
@jez Por que você poderia usarcd /D D:\on.other.drive
Sebastian
1
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fifalhará se ..\relativePathcontiver espaços #
2193 Sebastian
1
Funcionou quando removi o sinalizador / F e usei %% ~ dpfnI. Observe que isso for /?sugere o uso de letras maiúsculas para nomes de variáveis, para evitar confusão com os parâmetros de substituição
Sebastian
1
A remoção do @SebastianGodelet /Fnão funcionará em caminhos que não existem e resolverá caracteres curinga. Se você quer isso, ótimo, mas se você quer a funcionalidade de /F, basta usar a tokenspalavra-chave:FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI
SvenS
15

Isso serve para ajudar a preencher as lacunas na resposta de Adrien Plisson (que deve ser votada assim que ele a editar ;-):

você também pode obter o caminho completo do seu primeiro argumento usando% ~ f1, mas isso fornece um caminho de acordo com o caminho atual, que obviamente não é o que você deseja.

infelizmente, eu não sei como misturar os 2 juntos ...

Pode-se lidar %0e da %1mesma forma:

  • %~dpnx0para unidade qualificada + caminho + nome + extensão do próprio arquivo em lote,
    %~f0também é suficiente;
  • %~dpnx1para drive totalmente qualificado + caminho + nome + extensão de seu primeiro argumento [se é que é um nome de arquivo],
    %~f1também é suficiente;

%~f1funcionará independentemente de como você especificou seu primeiro argumento: com caminhos relativos ou com caminhos absolutos (se você não especificar a extensão do arquivo ao nomear %1, ele não será adicionado, mesmo se você usar %~dpnx1- no entanto.

Mas como diabos você nomearia um arquivo em uma unidade diferente de qualquer maneira, se não desse essas informações de caminho completo na linha de comando?

No entanto, %~p0, %~n0, %~nx0e %~x0pode vir a calhar, você deve estar interessado no caminho (sem driveletter), nome do arquivo (sem extensão), nome completo com extensão ou apenas a extensão do nome de arquivo. Mas observe, while %~p1e %~n1trabalhará para descobrir o caminho ou o nome do primeiro argumento %~nx1e %~x1não adicionar + mostrar a extensão, a menos que você já o tenha usado na linha de comando.

Kurt Pfeifle
fonte
o problema %~dpnx1é que ele fornece o caminho completo do argumento em relação ao diretório atual , enquanto o OP deseja o caminho em relação ao diretório em que o arquivo em lote reside .
Adrien Plisson
1
@ Adrien Plisson: %~dpnx1fornece um caminho totalmente qualificado para o 1º argumento. Não em relação a todos, e não em relação à dir atual também. O dé para a unidade, o pé para o caminho, o né para o nome do arquivo sem sufixo, o xé para o sufixo e o 1é para o primeiro argumento. - E a pergunta de Nathan era: "Existe alguma maneira de retornar um caminho absoluto a partir de um valor que contém um nome de arquivo e / ou caminho relativo?"
Kurt Pfeifle
Uso de caminhos absolutos e relativos e origem com uma descrição . Apreciativo!
it3xl
10

Você também pode usar funções em lote para isso:

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

Pequena melhoria na excelente solução do BrainSlugs83 . Generalizado para permitir nomear a variável de ambiente de saída na chamada.

@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

Se executar a partir da C:\projectsaída for:

C:\project\doc\build

fonte
4

Não vi muitas soluções para esse problema. Algumas soluções fazem uso de passagem de diretório usando CD e outras fazem uso de funções em lote. Minha preferência pessoal foi pelas funções em lote e, em particular, pela função MakeAbsolute , conforme fornecida pelo DosTips.

A função tem alguns benefícios reais, principalmente porque ela não altera seu diretório de trabalho atual e, em segundo lugar, que os caminhos que estão sendo avaliados nem precisam existir. Você pode encontrar algumas dicas úteis sobre como usar a função aqui também.

Aqui está um exemplo de script e suas saídas:

@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

E a saída:

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 isso ajude ... Com certeza me ajudou :) PS Mais uma vez obrigado ao DosTips! Você é demais!

dayneo
fonte
obrigado @ulidtko. Eu nunca tentei contra links simbólicos antes.
dayneo
2

Você pode apenas concatená-los.

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

parece estranho com \ .. \ no meio do seu caminho, mas funciona. Não há necessidade de fazer nada louco :)

Kristen
fonte
Isso simplesmente funciona. Pode ser ainda mais simplificado paraecho %~dp0..\SomeFile.txt
cowlinator
1

No seu exemplo, em Bar \ test.bat, DIR / B / S .. \ somefile.txt retornaria o caminho completo.

George Sisco
fonte
Isso não é uma má idéia ... devo fazer um loop sobre o resultado do DIR com um FOR e apenas pegar o primeiro resultado?
21339 Nathan Taylor
Eu pensei sobre o problema com vários arquivos. Você não especificou se múltiplos eram um problema, então eu fui com ele. Quanto a um loop FOR, estou tendo problemas para alimentar "../" para um loop for, mas sim. Se você não conseguir ativá-lo no comando DIR, poderá redirecionar a saída para um arquivo e percorrer isso. Não estou dizendo que a maneira 'padrão' de extrair o caminho seja ruim, é só que pensei que o meu fizesse mais do que você pediu. Ambas as soluções fazem 'alguma coisa' e ambas têm seu próprio conjunto de problemas.
George Sisco
Ah ... e, se você percorrer o DIR com êxito, poderá sobrescrever o redirecionamento para um arquivo e obter o último resultado. Se você reverter a ordem de uma maneira aceitável para você, poderá obter apenas o primeiro resultado. Se você precisar percorrer o arquivo, reverter a ordem para obter o primeiro resultado que estaria na última posição também pode ajudar. A razão pela qual sugiro que é mais fácil obter e descartar muitas coisas do que realmente lidar com elas. Não sei se meu modo de pensar faz sentido para você. Apenas colocando isso como uma idéia.
George Sisco
Se você precisar normalizar um caminho para uma pasta, sugiro esta solução: stackoverflow.com/questions/48764076/…
uceumern
0

Atualmente, o PowerShell é bastante comum, então eu o uso frequentemente como uma maneira rápida de invocar o C #, pois ele possui funções para praticamente tudo:

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

É um pouco lento, mas é difícil superar a funcionalidade obtida, a menos que seja necessário recorrer a uma linguagem de script real.

stijn
fonte
0

A solução da stijn trabalha com subpastas em 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
fonte
0

Arquivos Veja todas as outras respostas

Diretórios

Com ..sendo o seu caminho relativo, e supondo que você está atualmente em D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (o caminho relativo)

retorna caminho absoluto, por exemplo

D:\Projects

Engenheiro
fonte
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
fonte