Résoudre le chemin absolu à partir du chemin relatif et / ou du nom de fichier

155

Existe-t-il un moyen dans un script batch Windows de renvoyer un chemin absolu à partir d'une valeur contenant un nom de fichier et / ou un chemin relatif?

Donné:

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

J'ai besoin du chemin absolu par rapport au fichier batch.

Exemple:

  • "somefile.txt" se trouve dans "C: \ Foo \"
  • "test.bat" se trouve dans "C: \ Foo \ Bar".
  • L'utilisateur ouvre une fenêtre de commande dans "C: \ Foo" et appelle Bar\test.bat ..\somefile.txt
  • Dans le fichier de commandes "C: \ Foo \ somefile.txt" serait dérivé de %1
Nathan Taylor
la source
1
Les chemins relatifs ne sont pas la fin de l'histoire. Considérez également les liens symboliques NTFS : vous aurez très probablement besoin d'un analogue de realpathpour une normalisation robuste des chemins.
ulidtko
Vous n'avez probablement pas du tout besoin d'un chemin exact! Vous pouvez simplement ajouter un chemin de base: SET FilePath=%CD%\%1pour qu'il ressemble à C:\Foo\Bar\..\..\some\other\dir\file.txt. Les programmes semblent comprendre un chemin si compliqué.
Ven
Beaucoup de ces réponses sont folles plutôt que compliquées, ou tout simplement boguées - mais, c'est en fait une chose assez facile à faire par lots, jetez un œil à ma réponse ci-dessous .
BrainSlugs83

Réponses:

160

Dans les fichiers batch, comme dans les programmes C standard, l'argument 0 contient le chemin du script en cours d'exécution. Vous pouvez utiliser %~dp0pour obtenir uniquement la partie chemin du 0ème argument (qui est le script actuel) - ce chemin est toujours un chemin complet.

Vous pouvez également obtenir le chemin complet de votre premier argument en utilisant %~f1, mais cela donne un chemin en fonction du répertoire de travail courant, ce qui n'est évidemment pas ce que vous voulez.

Personnellement, j'utilise souvent l' %~dp0%~1idiome dans mon fichier batch, qui interprète le premier argument par rapport au chemin du batch en cours d'exécution. Il a cependant un inconvénient: il échoue lamentablement si le premier argument est pleinement qualifié.

Si vous avez besoin de supporter à la fois des chemins relatifs et absolus, vous pouvez utiliser la solution de Frédéric Ménez : changer temporairement le répertoire de travail courant.

Voici un exemple qui illustrera chacune de ces techniques:

@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 vous enregistrez ceci sous c: \ temp \ example.bat et que vous l'exécutez à partir de c: \ Users \ Public sous

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

... vous observerez la sortie suivante:

%~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 documentation de l'ensemble des modificateurs autorisés sur un argument batch peut être trouvée ici: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call

Adrien Plisson
la source
4
Vous pouvez gérer %0et de %1même: %~dpnx0pour le chemin complet + nom du fichier batch lui-même, %~dpnx1pour le chemin complet + nom de son premier argument [si c'est un nom de fichier du tout]. (Mais comment
diable nommeriez-
Cet exemple est beaucoup plus complexe que la réponse de @ frédéric-ménez
srossross
3
Cela échoue sur les liens symboliques NTFS.
ulidtko
@KurtPfeifle d:\foo\..\bar\xyz.txtpeut toujours être normalisé. Je recommande d'utiliser cette approche avec la réponse de @ axel-heider ci-dessous (en utilisant un sous-programme batch - alors vous pouvez le faire sur n'importe quelle variable, pas seulement une variable numérotée).
BrainSlugs83
@ulidtko pouvez-vous élaborer? Qu'est-ce qui échoue? - Qu'attendez-vous qu'il se passe? - Vous attendez-vous à résoudre le dossier d'origine? (Parce que si elle l'a fait que je qualifierais que un échec - c'est un peu le point d'avoir des liens symboliques en premier lieu ...)
BrainSlugs83
151

Je suis tombé sur un besoin similaire ce matin: comment convertir un chemin relatif en chemin absolu à l'intérieur d'un script de commande Windows.

Ce qui suit a fait l'affaire:

@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
la source
2
C'est VRAIMENT utile. Existe-t-il de bonnes ressources pour des astuces de fichiers batch comme celle-ci?
Danny Parker
8
Ne pensez pas qu'il soit nécessaire d'avoir le "pushd" suivi de "cd". Vous pouvez simplement faire "pushd% REL_PATH%". Cela sauvegardera le répertoire actuel et passera à REL_PATH en une seule fois.
Eddie Sullivan
1
@DannyParker Une collection utile de techniques Batch et d'exemples de scripts est robvanderwoude.com/batchfiles.php . Voir aussi stackoverflow.com/questions/245395/…
hfs
6
@EddieSullivan Si le passage au répertoire échoue (le répertoire n'existe pas, pas de permission ...), la commande pushd échoue également et ne poussera pas réellement une valeur sur la pile de répertoires. Le prochain appel (et tous les appels consécutifs) à popd affichera la mauvaise valeur. L'utilisation pushd .évite ce problème.
SvenS
1
Notez que cela ne fonctionnera pas sur les chemins qui sont des fichiers ou sur des chemins inexistants où il y a un .. quelque part au milieu. Ce n'était pas un obstacle pour moi, mais c'est un peu une faiblesse pour une solution réutilisable.
Mihai Danila
97

La plupart de ces réponses semblent folles sur compliquées et super boguées, voici la mienne - cela fonctionne sur n'importe quelle variable d'environnement, non %CD%ou PUSHD/ POPDou for /fabsurde - juste de vieilles fonctions batch. - Le répertoire et le fichier ne doivent même pas exister.

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

ECHO "%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~f1
  EXIT /B
BrainSlugs83
la source
7
C'est en effet une solution propre, fonctionnelle et simple.
Razvan
3
Très concis. J'utilise cette technique maintenant, largement.
Paulo França Lacerda
1
Pourquoi est-ce% ~ dpfn1 et pas simplement% ~ f1? Est-ce une sorte de solution de contournement? % ~ f1 fonctionne bien pour moi sur Windows 7 et Windows 10 au moins.
il - ya
1
@ il - ya ressemble à %~f1une abréviation de %~dpfn1- alors, n'hésitez pas à utiliser à la %~f1place. 🙂 - dans le format long ~signifie supprimer les guillemets, dsignifie lecteur, psignifie chemin, nseul serait un nom de fichier sans extension, mais fnsignifie un nom de fichier avec une extension. le 1signifie le premier argument. - (Je crois que ce format est antérieur à Windows 7.)
BrainSlugs83
1
Ah, vous m'avez battu! Selon un commentaire dans le code source nt4, fichier nt4 \ private \ windows \ cmd \ clex.c (daté du 27/08/1997), fonction MSCmdVar (), "//% ~ fi - développe% i en un chemin complet nom "Désolé de vous harceler. J'espérais pouvoir aller au fond de cette idée fausse. Je suis tombé sur% ~ dpfn en examinant un code assez récent, j'ai tiré quelques cheveux en essayant de comprendre ce qu'il était censé faire (attention, je n'en ai plus beaucoup), et je me suis mis à enquêter sur la façon dont il a pris vie. une vendetta personnelle.
il - ya
35

Sans avoir à avoir un autre fichier batch auquel passer des arguments (et utiliser les opérateurs d'argument), vous pouvez utiliser FOR /F:

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

iin %%~fiest la variable définie à /F %%i. par exemple. si vous changiez cela en /F %%aalors la dernière partie serait %%~fa.

Pour faire la même chose directement à l'invite de commande (et non dans un fichier batch), remplacez %%par %...

Peter Ritchie
la source
Ne pas changer de répertoire est important, car cela rend la recette robuste au fait que la cdcommande ne change pas nécessairement la %CD%variable d'environnement (si par exemple vous êtes sur le lecteur D:et que votre chemin cible est C:
activé
@jez Pour que vous puissiez utilisercd /D D:\on.other.drive
Sebastian
1
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fiéchouera si le ..\relativePathcontient des espaces
Sebastian
1
Cela a fonctionné lorsque j'ai supprimé l'indicateur / F et utilisé %% ~ dpfnI. Notez que for /?suggère d'utiliser des lettres majuscules pour les noms de variables, pour éviter toute confusion avec les paramètres de substitution
Sebastian
1
La suppression de @SebastianGodelet /Fne fonctionnera pas sur les chemins qui n'existent pas et résoudra les caractères génériques. Si vous voulez cela, super, mais si vous voulez la fonctionnalité de /F, il vous suffit d'utiliser le tokensmot clé:FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI
SvenS
15

C'est pour aider à combler les lacunes de la réponse d'Adrien Plisson (qui devrait être votée pour dès qu'il la modifie ;-):

vous pouvez également obtenir le chemin complet de votre premier argument en utilisant% ~ f1, mais cela donne un chemin en fonction du chemin actuel, ce qui n'est évidemment pas ce que vous voulez.

malheureusement, je ne sais pas comment mélanger les 2 ensemble ...

On peut gérer %0et de %1même:

  • %~dpnx0pour lecteur complet + chemin + nom + extension du fichier batch lui-même, cela
    %~f0suffit également;
  • %~dpnx1pour lecteur complet + chemin + nom + extension de son premier argument [si c'est un nom de fichier du tout], cela
    %~f1suffit également;

%~f1fonctionnera indépendamment de la manière dont vous avez spécifié votre premier argument: avec des chemins relatifs ou avec des chemins absolus (si vous ne spécifiez pas l'extension du fichier lors du nommage %1, il ne sera pas ajouté, même si vous utilisez %~dpnx1- cependant.

Mais comment diable nommeriez-vous un fichier sur un lecteur différent de toute façon si vous ne donniez pas ces informations de chemin complet sur la ligne de commande en premier lieu?

Cependant, %~p0, %~n0, %~nx0et %~x0peut être utile, si vous êtes intéressé par chemin (sans driveletter), nom de fichier (sans extension), nom complet avec l' extension ou l'extension de nom de fichier uniquement. Mais notez que tandis que %~p1et %~n1fonctionnera pour trouver le chemin ou le nom du premier argument, %~nx1et %~x1n'ajoutera pas + afficher l'extension, sauf si vous l'avez déjà utilisé sur la ligne de commande.

Kurt Pfeifle
la source
le problème avec %~dpnx1est qu'il donne le chemin complet de l'argument par rapport au répertoire courant , tandis que l'OP veut le chemin relatif au répertoire où réside le fichier de commandes .
Adrien Plisson
1
@Adrien Plisson: %~dpnx1donne le chemin complet du 1er argument. Pas du tout relatif , et pas non plus relatif au répertoire actuel. Le dest pour le lecteur, le pest pour le chemin, le nest pour le nom de fichier sans suffixe, le xest pour le suffixe, le 1est pour le premier argument. - Et la question de Nathan était: "Est-il possible de renvoyer un chemin absolu à partir d'une valeur contenant un nom de fichier et / ou un chemin relatif?"
Kurt Pfeifle
Utilisation et source des chemins absolus et relatifs avec une description . Reconnaissant!
it3xl
10

Vous pouvez également utiliser les fonctions batch pour cela:

@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
la source
9

Petite amélioration de l'excellente solution de BrainSlugs83 . Généralisé pour permettre de nommer la variable d'environnement de sortie dans l'appel.

@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 exécuter à partir de la C:\projectsortie est:

C:\project\doc\build

la source
4

Je n'ai pas vu beaucoup de solutions à ce problème. Certaines solutions utilisent la traversée de répertoires à l'aide de CD et d'autres utilisent des fonctions de traitement par lots. Ma préférence personnelle a été pour les fonctions batch et en particulier, la fonction MakeAbsolute fournie par DosTips.

La fonction présente de réels avantages, principalement qu'elle ne modifie pas votre répertoire de travail actuel et d'autre part que les chemins en cours d'évaluation ne doivent même pas exister. Vous trouverez quelques conseils utiles sur la façon d'utiliser la fonction ici aussi.

Voici un exemple de script et ses sorties:

@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

Et la sortie:

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

J'espère que cela aide ... Cela m'a certainement aidé :) PS Merci encore à DosTips! Tu gères!

dayneo
la source
merci @ulidtko. Je n'ai jamais essayé contre les liens symboliques auparavant.
dayneo
2

Vous pouvez simplement les concaténer.

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

cela semble étrange avec \ .. \ au milieu de votre chemin mais cela fonctionne. Pas besoin de faire quoi que ce soit de fou :)

Kristen
la source
Cela fonctionne juste. Peut être encore simplifié àecho %~dp0..\SomeFile.txt
cowlinator
1

Dans votre exemple, à partir de Bar \ test.bat, DIR / B / S .. \ somefile.txt renverrait le chemin complet.

George Sisco
la source
Ce n'est pas une mauvaise idée ... devrais-je boucler sur le résultat du DIR avec un FOR et simplement saisir le premier résultat?
Nathan Taylor
J'ai pensé au problème avec plusieurs fichiers. Vous n'avez pas précisé si les multiples étaient un problème, alors je suis allé avec. Quant à une boucle FOR, j'ai du mal à alimenter "../" dans une boucle for, mais ymmv. Si vous ne parvenez pas à exécuter la commande DIR, vous pouvez rediriger la sortie vers un fichier et le parcourir en boucle. Je ne dis pas que la manière «standard» d'extraire le chemin est mauvaise, c'est juste que je pensais que la mienne faisait plus de ce que vous avez demandé. Les deux solutions font «quelque chose» et elles ont toutes deux leurs propres problèmes.
George Sisco
Oh ... et, si vous parcourez DIR avec succès, vous pouvez écraser la redirection vers un fichier et obtenir le dernier résultat. Si vous inversez l'ordre d'une manière qui vous convient, vous ne pouvez obtenir que le premier résultat. Si vous devez parcourir le fichier en boucle, inverser l'ordre pour obtenir le premier résultat qui serait en dernière position peut également aider. La raison pour laquelle je suggère que c'est qu'il peut être plus facile d'obtenir et de jeter beaucoup de choses plutôt que de les traiter. Je ne sais pas si ma façon de penser a du sens pour vous. Il suffit de le présenter comme une idée.
George Sisco
Si vous avez besoin de normaliser un chemin vers un dossier, je vous suggère cette solution: stackoverflow.com/questions/48764076/…
uceumern
0

PowerShell est assez courant ces jours-ci, donc je l'utilise souvent comme moyen rapide d'appeler C # car il a des fonctions pour à peu près tout:

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

C'est un peu lent, mais la fonctionnalité gagnée est difficile à battre à moins de recourir à un langage de script réel.

stijn
la source
0

La solution de stijn fonctionne avec les sous-dossiers sous 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
la source
0

Fichiers Voir toutes les autres réponses

Répertoires

En ..étant votre chemin relatif, et en supposant que vous êtes actuellement dans D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (le chemin relatif)

renvoie le chemin absolu, par exemple

D:\Projects

Ingénieur
la source
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
Sergueï Sachkov
la source