Rozwiąż ścieżkę bezwzględną na podstawie ścieżki względnej i / lub nazwy pliku

155

Czy istnieje sposób w skrypcie wsadowym systemu Windows, aby zwrócić ścieżkę bezwzględną z wartości zawierającej nazwę pliku i / lub ścieżkę względną?

Dany:

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

Potrzebuję bezwzględnej ścieżki względem pliku wsadowego.

Przykład:

  • Plik „somefile.txt” znajduje się w „C: \ Foo \”
  • „test.bat” znajduje się w „C: \ Foo \ Bar”.
  • Użytkownik otwiera okno poleceń w „C: \ Foo” i wywołuje Bar\test.bat ..\somefile.txt
  • W pliku wsadowym będzie pochodzić „C: \ Foo \ somefile.txt” %1
Nathan Taylor
źródło
1
Względne ścieżki to nie koniec historii. Rozważ także dowiązania symboliczne NTFS : najprawdopodobniej będziesz również potrzebować analogu realpathdo solidnej normalizacji ścieżki.
ulidtko
Prawdopodobnie w ogóle nie potrzebujesz dokładnej ścieżki! Możesz po prostu dodać ścieżkę bazową: SET FilePath=%CD%\%1tak, aby mogła wyglądać jak C:\Foo\Bar\..\..\some\other\dir\file.txt. Programy wydają się rozumieć tak skomplikowaną ścieżkę.
Fr0sT
Wiele z tych odpowiedzi jest szalonych lub skomplikowanych lub po prostu błędnych - ale w rzeczywistości jest to całkiem łatwa rzecz do zrobienia zbiorczo, spójrz na moją odpowiedź poniżej .
BrainSlugs83

Odpowiedzi:

160

W plikach wsadowych, podobnie jak w standardowych programach C, argument 0 zawiera ścieżkę do aktualnie wykonywanego skryptu. Możesz użyć, %~dp0aby uzyskać tylko część ścieżki zerowego argumentu (który jest bieżącym skryptem) - ta ścieżka jest zawsze w pełni kwalifikowaną ścieżką.

Możesz również uzyskać w pełni kwalifikowaną ścieżkę pierwszego argumentu, używając %~f1, ale daje to ścieżkę zgodnie z bieżącym katalogiem roboczym, co oczywiście nie jest tym, czego chcesz.

Osobiście często używam %~dp0%~1idiomu w moim pliku wsadowym, który interpretuje pierwszy argument w stosunku do ścieżki wykonującej partii. Ma jednak wadę: nieszczęśliwie zawodzi, jeśli pierwszy argument jest w pełni kwalifikowany.

Jeśli potrzebujesz obsługiwać zarówno ścieżki względne, jak i bezwzględne, możesz skorzystać z rozwiązania Frédéric Ménez : tymczasowo zmień bieżący katalog roboczy.

Oto przykład, który zademonstruje każdą z tych technik:

@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

Jeśli zapiszesz to jako c: \ temp \ example.bat i uruchomisz go z c: \ Users \ Public jako

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

... zobaczysz następujący wynik:

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

dokumentację dotyczącą zestawu modyfikatorów dozwolonych w argumencie wsadowym można znaleźć tutaj: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call

Adrien Plisson
źródło
4
Możesz sobie z tym poradzić %0i %1podobnie: %~dpnx0dla pełnej ścieżki + nazwa samego pliku wsadowego, %~dpnx1dla pełnej ścieżki + nazwa pierwszego argumentu [jeśli w ogóle jest to nazwa pliku]. (Ale jak nazwałbyś plik na innym dysku, gdybyś i tak nie podał tej pełnej informacji o ścieżce w linii poleceń?)
Kurt Pfeifle
Ten przykład jest znacznie bardziej złożony niż odpowiedź @ frédéric-ménez
srossross
3
To kończy się niepowodzeniem w przypadku dowiązań symbolicznych NTFS.
ulidtko
@KurtPfeifle d:\foo\..\bar\xyz.txtmożna nadal znormalizować. Zalecam użycie tego podejścia z odpowiedzią @ axel-heider poniżej (używając podprogramu wsadowego - wtedy możesz to zrobić na dowolnej zmiennej, a nie tylko zmiennej numerowanej).
BrainSlugs83
@ulidtko czy możesz rozwinąć? Co zawodzi? - Czego się spodziewasz? - Czy spodziewasz się powrotu do oryginalnego folderu? (Bo gdyby tak się stało , że , nazwałbym to niepowodzenie - to rodzaj punktu dowiązania mający na pierwszym miejscu ...)
BrainSlugs83
151

Dziś rano spotkałem się z podobną potrzebą: jak przekonwertować ścieżkę względną na ścieżkę bezwzględną w skrypcie poleceń systemu Windows.

Oto co załatwiło sprawę:

@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
źródło
2
To jest NAPRAWDĘ przydatne. Czy są jakieś dobre zasoby do takich sztuczek z plikami wsadowymi?
Danny Parker
8
Nie myśl, że konieczne jest posiadanie „pushd”, po którym następuje „cd”. Możesz po prostu wykonać „pushd% REL_PATH%”. Spowoduje to zapisanie bieżącego katalogu i przejście do REL_PATH za jednym razem.
Eddie Sullivan
1
@DannyParker Przydatny zbiór technik wsadowych i przykładowych skryptów to robvanderwoude.com/batchfiles.php . Zobacz także stackoverflow.com/questions/245395/…
hfs
6
@EddieSullivan Jeśli przejście do katalogu nie powiedzie się (katalog nie istnieje, brak uprawnień ...), polecenie pushd również nie powiedzie się i faktycznie nie umieści wartości na stosie katalogów. Następne wywołanie (i wszystkie kolejne wywołania) popd spowoduje wyświetlenie niewłaściwej wartości. Używanie pushd .zapobiega temu problemowi.
SvenS
1
Zauważ, że to nie zadziała na ścieżkach, które są plikami lub na nieistniejących ścieżkach, gdzie gdzieś pośrodku znajduje się ... Nie było to dla mnie przeszkodą w pokazie, ale jest to trochę słabość rozwiązania wielokrotnego użytku.
Mihai Danila
97

Większość z tych odpowiedzi wydaje się szalona przez skomplikowane i bardzo błędne, oto moje - działa na każdej zmiennej środowiskowej, bez %CD%lub PUSHD/ POPDlub for /fnonsensem - po prostu zwykłe stare funkcje wsadowe. - Katalog i plik nie muszą nawet istnieć.

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

ECHO "%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~dpfn1
  EXIT /B
BrainSlugs83
źródło
7
To rzeczywiście czyste, działające i proste rozwiązanie.
Razvan
3
Bardzo zwięzłe. Obecnie szeroko stosuję tę technikę.
Paulo França Lacerda
Dlaczego jest to% ~ dpfn1, a nie po prostu% ~ f1? Czy jest to jakieś obejście? % ~ f1 działa dobrze dla mnie przynajmniej na Windows 7 i Windows 10.
il - ya
1
@ il - ya wygląda na %~f1to, że jest po prostu skrótem %~dpfn1- więc możesz go %~f1zamiast tego użyć . 🙂 - w długim formacie ~oznacza usunięcie cudzysłowów, doznacza dysk, poznacza ścieżkę, nsama byłaby nazwą pliku bez rozszerzenia, ale fnoznacza nazwę pliku z rozszerzeniem. 1oznacza pierwszy argument. - (
Wydaje
@ BrainSlugs83 te zmienne są dostępne od XP (w cmd.exe), ale ich użycie nie zmieniło się od tego czasu:% ~ f1 - rozwija% 1 do w pełni kwalifikowanej nazwy ścieżki; % ~ d1 - rozwija% 1 tylko do litery dysku; % ~ p1 - rozwija% 1 tylko do ścieżki; % ~ n1 - rozwija% 1 tylko do nazwy pliku; % ~ x1 - rozwija% 1 tylko do rozszerzenia pliku. Nie ma specjalnego przypadku dla% ~ fn1, nie tworzy nazwy + rozszerzenia, przełącznik f ma po prostu pierwszeństwo przed d, p, n i x, gdy są łączone. Wydaje się, że nie ma dobrego powodu, aby dodawać d, p i n.
il - ya
35

Bez konieczności posiadania innego pliku wsadowego do przekazywania argumentów (i używania operatorów argumentów), możesz użyć FOR /F:

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

gdzie iin %%~fijest zmienną zdefiniowaną w /F %%i. na przykład. gdybyś to zmienił, /F %%ato ostatnia część byłaby %%~fa.

Aby zrobić to samo słuszne w wierszu poleceń (a nie w pliku wsadowym) zastąpić %%z %...

Peter Ritchie
źródło
Brak zmiany katalogu jest ważny, ponieważ sprawia, że ​​przepis jest odporny na fakt, że cdpolecenie niekoniecznie zmienia %CD%zmienną środowiskową (jeśli na przykład jesteś na dysku, D:a ścieżka docelowa jest włączona C:)
jez.
@jez Do tego możesz użyćcd /D D:\on.other.drive
Sebastian
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~finie powiedzie się, jeśli ..\relativePathzawiera spacje
Sebastian
1
Zadziałało, gdy usunąłem flagę / F i użyłem %% ~ dpfnI. Zauważ, że for /?sugeruje użycie wielkich liter w nazwach zmiennych, aby uniknąć nieporozumień z parametrami podstawiania
Sebastian
1
Usuwanie @SebastianGodelet /Fnie będzie działać na ścieżkach, które nie istnieją, i rozwiąże znaki wieloznaczne. Jeśli chcesz tego, świetnie, ale jeśli chcesz funkcjonalność /F, wystarczy użyć tokenssłowa kluczowego:FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI
SvenS
15

Ma to pomóc wypełnić luki w odpowiedzi Adriena Plissona (którą należy zaopiniować, gdy tylko ją zredaguje ;-):

możesz również uzyskać pełną ścieżkę swojego pierwszego argumentu używając% ~ f1, ale daje to ścieżkę zgodną z bieżącą ścieżką, co oczywiście nie jest tym, czego chcesz.

niestety nie wiem, jak połączyć te 2 razem ...

Można sobie z tym poradzić %0i %1podobnie:

  • %~dpnx0dla w pełni kwalifikowanego dysku + ścieżka + nazwa + rozszerzenie samego pliku wsadowego,
    %~f0również wystarczy;
  • %~dpnx1dla w pełni kwalifikowanego dysku + ścieżka + nazwa + rozszerzenie pierwszego argumentu [jeśli w ogóle jest to nazwa pliku],
    %~f1również wystarcza;

%~f1będzie działać niezależnie od tego, jak podałeś swój pierwszy argument: ze ścieżkami względnymi lub ścieżkami bezwzględnymi (jeśli nie określisz rozszerzenia pliku podczas nadawania nazwy %1, nie zostanie on dodany, nawet jeśli użyjesz %~dpnx1- jednak.

Ale jak, u licha, nazwałbyś plik na innym dysku , gdybyś nie podał tej pełnej informacji o ścieżce w linii poleceń?

Jednak %~p0, %~n0, %~nx0i %~x0może się przydać, powinien być zainteresowany w ścieżce (bez litera_dysku), nazwę pliku (bez rozszerzenia), pełną nazwę pliku z rozszerzeniem lub tylko rozszerzenie pliku jest. Ale zwróć uwagę, że while %~p1i %~n1będzie działać, aby znaleźć ścieżkę lub nazwę pierwszego argumentu, %~nx1i %~x1nie doda + pokaż rozszerzenia, chyba że użyłeś go już w linii poleceń.

Kurt Pfeifle
źródło
problem %~dpnx1polega na tym, że podaje w pełni kwalifikowaną ścieżkę argumentu względem bieżącego katalogu , podczas gdy OP chce ścieżki względnej do katalogu, w którym znajduje się plik wsadowy .
Adrien Plisson
@Adrien Plisson: %~dpnx1podaje w pełni kwalifikowaną ścieżkę pierwszego argumentu. Zupełnie nie względny , ani też nie w stosunku do obecnego reż. To djest dla dysku, to pjest dla ścieżki, njest dla sufiksu bez nazwy pliku, xjest dla sufiksu, 1jest dla pierwszego argumentu. - A pytanie Nathana brzmiało: „Czy istnieje sposób na zwrócenie bezwzględnej ścieżki z wartości zawierającej nazwę pliku i / lub ścieżkę względną?”
Kurt Pfeifle
Zarówno bezwzględne, jak i względne użycie i źródło ścieżek wraz z opisem . Mający zrozumienie!
it3xl
10

Możesz również użyć do tego funkcji wsadowych:

@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
źródło
9

Małe ulepszenie doskonałego rozwiązania BrainSlugs83 . Uogólnione, aby umożliwić nazwanie wyjściowej zmiennej środowiskowej w wywołaniu.

@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

Jeśli uruchomienie z C:\projectwyjścia to:

C:\project\doc\build

źródło
4

Nie widziałem wielu rozwiązań tego problemu. Niektóre rozwiązania wykorzystują przemierzanie katalogów za pomocą CD, a inne wykorzystują funkcje wsadowe. Osobiście preferowałem funkcje wsadowe, aw szczególności funkcję MakeAbsolute dostarczaną przez DosTips.

Ta funkcja ma pewne realne korzyści, przede wszystkim to, że nie zmienia bieżącego katalogu roboczego, a po drugie, oceniane ścieżki nie muszą nawet istnieć. Można znaleźć kilka pomocnych wskazówek, jak korzystać z funkcji tutaj też.

Oto przykładowy skrypt i jego wyniki:

@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

A wynik:

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

Mam nadzieję, że to pomoże ... Na pewno mi pomogło :) PS Jeszcze raz dziękuję DosTips! Rządzisz!

dayneo
źródło
dzięki @ulidtko. Nie próbowałem wcześniej z linkami symbolicznymi.
dayneo
2

Możesz je po prostu połączyć.

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

wygląda to dziwnie z \ .. \ na środku ścieżki, ale działa. Nie musisz robić nic szalonego :)

Kristen
źródło
To po prostu działa. Można jeszcze bardziej uprościćecho %~dp0..\SomeFile.txt
cowlinator
1

W naszym przykładzie z Bar \ test.bat DIR / B / S .. \ somefile.txt zwróci pełną ścieżkę.

George Sisco
źródło
To nie jest zły pomysł ... czy powinienem powtórzyć wynik DIR z FOR i po prostu pobrać pierwszy wynik?
Nathan Taylor
Myślałem o problemie z wieloma plikami. Nie sprecyzowałeś, czy wielokrotności są problemem, więc poszedłem z tym. Jeśli chodzi o pętlę FOR, mam problem z dostarczeniem „../” do pętli for, ale ymmv. Jeśli nie możesz uzyskać tego za pomocą polecenia DIR, możesz przekierować wyjście do pliku i zapętlić go. Nie mówię, że „standardowy” sposób wydobywania ścieżki jest zły, po prostu myślałem, że mój zrobił więcej z tego, o co prosiłeś. Oba rozwiązania „coś” robią i oba mają swój własny zestaw problemów.
George Sisco
Aha… i jeśli pomyślnie przejdziesz przez DIR, możesz nadpisać przekierowanie do pliku i uzyskać ostatni wynik. Jeśli odwrócisz kolejność w sposób, który jest dla Ciebie akceptowalny, możesz uzyskać tylko pierwszy wynik. Jeśli musisz przeglądać plik w pętli, pomocne może być również odwrócenie kolejności, aby uzyskać pierwszy wynik, który byłby na ostatniej pozycji. Powodem, który sugeruję, jest to, że może być łatwiej zdobyć i wyrzucić wiele rzeczy, niż faktycznie sobie z nimi radzić. Nie wiem, czy mój sposób myślenia ma dla ciebie sens. Po prostu przedstawię to jako pomysł.
George Sisco,
Jeśli potrzebujesz znormalizować ścieżkę do folderu, proponuję takie rozwiązanie: stackoverflow.com/questions/48764076/ ...
uceumern
0

PowerShell jest obecnie dość powszechny, więc często używam go jako szybkiego sposobu wywoływania C #, ponieważ ma on funkcje dla prawie wszystkiego:

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

Jest trochę powolny, ale uzyskana funkcjonalność jest trudna do pokonania, chyba że bez uciekania się do rzeczywistego języka skryptowego.

stijn
źródło
0

rozwiązanie stijn działa z podfolderami pod 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
źródło
0

Pliki Zobacz wszystkie inne odpowiedzi

Katalogi

Z ..bycia swoją ścieżką względną, a zakładając, że jesteś obecnie w D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (ścieżka względna)

zwraca ścieżkę bezwzględną np

D:\Projects

Inżynier
źródło
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
Siergiej Saczkow
źródło