Розв’яжіть абсолютний шлях від відносного шляху та / або імені файлу


155

Чи є спосіб у пакетному скрипті Windows повернути абсолютний шлях зі значення, що містить ім'я файлу та / або відносний шлях?

Подано:

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

Мені потрібен абсолютний шлях відносно пакетного файлу.

Приклад:

  • "somefile.txt" знаходиться в "C: \ Foo \"
  • "test.bat" розташований у "C: \ Foo \ Bar".
  • Користувач відкриває вікно команд у "C: \ Foo" і дзвонить Bar\test.bat ..\somefile.txt
  • У пакетному файлі "C: \ Foo \ somefile.txt" буде похідно %1

1
Відносні шляхи - це не кінець історії. Розглянемо також NTFS-посилання : швидше за все, вам також знадобиться аналог realpathдля надійної нормалізації шляху.
ulidtko

Напевно, точний шлях вам взагалі не потрібен! Можна просто додати базовий шлях: SET FilePath=%CD%\%1так, щоб це було схоже C:\Foo\Bar\..\..\some\other\dir\file.txt. Програми, здається, розуміють такий складний шлях.
Fr0sT

Багато цих відповідей божевільні за складними або просто простими баггі, але це насправді досить просто зробити в партії, погляньте на мою відповідь нижче .
BrainSlugs83

Відповіді:


160

У пакетних файлах, як у стандартних програмах на C, аргумент 0 містить шлях до поточного виконуваного сценарію. Ви можете використовувати %~dp0лише частину шляху 0-го аргументу (який є поточним сценарієм) - цей шлях завжди є повністю кваліфікованим шляхом.

Ви також можете отримати повністю кваліфікований шлях вашого першого аргументу, використовуючи %~f1, але це дає шлях відповідно до поточного робочого каталогу, який, очевидно, не є тим, що ви хочете.

Особисто я часто використовую %~dp0%~1ідіому у своєму пакетному файлі, який інтерпретує перший аргумент відносно шляху виконуваної партії. Однак у нього є недолік: він збиває з ладу, якщо перший аргумент є повністю кваліфікованим.

Якщо вам потрібно підтримувати як відносні, так і абсолютні шляхи, ви можете скористатися рішенням Frédéric Ménez : тимчасово змінити поточний робочий каталог.

Ось приклад, який демонструє кожну з цих методик:

@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

Якщо ви збережете це як c: \ temp \ example.bat і запустіть його з c: \ Users \ Public as

c: \ Користувачі \ Загальні> \ temp \ example.bat .. \ windows

... ви будете спостерігати такий результат:

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

документацію для набору модифікаторів, дозволених у пакетному аргументі, можна знайти тут: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/call


4
Ви можете обробляти %0і %1також: %~dpnx0для повного шляху + імені самого командного файлу, %~dpnx1для повного шляху + імені свого першого аргументу [якщо це ім'я файл взагалі]. (Але як же ви назвали файл на іншому диску, якщо ви все одно не дасте повну інформацію про командний рядок?)
Kurt Pfeifle

Цей приклад набагато складніший, ніж відповідь @ frédéric-ménez
srossross

3
Це не вдається на символьних посиланнях NTFS.
улідтко

@KurtPfeifle d:\foo\..\bar\xyz.txtще можна нормалізувати. Я рекомендую використовувати цей підхід з відповіддю @ axel-heider нижче (використовуючи пакетну підпрограму - тоді ви можете це зробити на будь-якій змінній, а не лише на нумерованій змінній).
BrainSlugs83

@ulidtko ви можете докладно? Що не вдається? - Що ви очікуєте, що це станеться? - Ви очікуєте переходу на оригінальну папку? (Тому що якщо це так , що я б назвав цей відмова - це свого роду точки , що мають символьні посилання в першу чергу ...)
BrainSlugs83

151

Я зіткнувся з подібною потребою сьогодні вранці: як перетворити відносний шлях в абсолютний шлях всередині командного сценарію Windows.

Наступне зробив трюк:

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

2
Це дійсно корисно. Чи є якісь хороші ресурси для прийомів пакетних файлів, таких як цей?
Денні Паркер

8
Не думайте, що потрібно мати "pushd", а потім "CD". Ви можете просто зробити "pushd% REL_PATH%". Це збереже поточний каталог і перейде на REL_PATH за один раз.
Едді Салліван

1
@DannyParker Корисною колекцією методів Batch та прикладних скриптів є robvanderwoude.com/batchfiles.php . Дивіться також stackoverflow.com/questions/245395/…
hfs

6
@EddieSullivan Якщо зміна на каталог не вдається (каталог не існує, немає дозволу ...), команда pushd також виходить з ладу і фактично не натисне значення на стек каталогів. Наступний дзвінок (і всі послідовні дзвінки) для popd відображатиме неправильне значення. Використання pushd .запобігає цій проблемі.
SvenS

1
Зауважте, що це не працюватиме на шляхах, які є файлами, або на неіснуючих шляхах, де є десь посередині. Для мене це не було пробкою, але це дещо слабке рішення для багаторазового використання.
Михай Даніла

97

Більшість цих відповідей здаються божевільними над складними та супер-баггі. Ось моя - вона працює на будь-якій змінній середовища, немає %CD%або PUSHD/ POPDабо for /fнісенітниці - просто звичайні старі функції партії. - Каталог і файл навіть не повинні існувати.

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

ECHO "%BLAH%"

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

:NORMALIZEPATH
  SET RETVAL=%~dpfn1
  EXIT /B

7
Це дійсно чисте, робоче та прямолінійне рішення.
Разван

3
Дуже лаконічно. Зараз я широко використовую цю техніку.
Paulo França Lacerda

Чому це% ~ dpfn1, а не просто% ~ f1? Це якесь вирішення? % ~ f1 добре працює для мене на Windows 7 і Windows 10 принаймні.
il - ya

1
@ il - ya, схоже %~f1, просто короткий %~dpfn1- так що не соромтеся використовувати це %~f1. 🙂 - у довгому форматі ~означає видалити лапки, dозначає диск, pозначає шлях, nпоодинці буде ім'я файлу без розширення, але fnозначає ім'я файлу з розширенням. 1означає перший аргумент. - (Я вважаю, що цей формат передує Windows 7.)
BrainSlugs83

@ BrainSlugs83 ці змінні доступні з XP (у cmd.exe), але їх використання з тих пір не змінилося:% ~ f1 - розширює% 1 на повністю кваліфіковану назву шляху; % ~ d1 - розширює% 1 лише на букву диска; % ~ p1 - розширює лише% 1 на шлях; % ~ n1 - розширює% 1 лише до імені файлу; % ~ x1 - розширює% 1 лише до розширення файлу. Для% ~ fn1 немає особливого випадку, він не створює ім'я + розширення, перемикач f просто має перевагу над d, p, n та x, коли вони поєднуються. Здається, немає вагомих причин для додавання d, p і n.
il - ya

35

Без необхідності мати ще один пакетний файл для передачі аргументів (та використання операторів аргументів), ви можете використовувати FOR /F:

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

де iin %%~fi- змінна, визначена в /F %%i. напр. якби ви змінили це, /F %%aто остання частина була б %%~fa.

Щоб зробити те саме, що в командному рядку (а не в пакетному файлі) замініть %%на %...


Не змінювати каталог важливо, оскільки це робить рецепт надійним для того, що cdкоманда не обов'язково змінює %CD%змінну середовища (якщо, наприклад, ви перебуваєте на диску, D:а ваш цільовий шлях увімкнено C:)
яз

@jez Для цього можна скористатисяcd /D D:\on.other.drive
Себастьян

FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fiне вдасться, якщо ..\relativePathмістить пробіли
Себастьян

1
Це спрацювало, коли я видалив прапор / F і використав %% ~ dpfnI. Зауважте, що for /?пропонується використовувати великі літери для змінних імен, щоб уникнути плутанини з параметрами заміни
Себастьян

1
Видалення @SebastianGodelet /Fне буде працювати на шляхах, які не існують, і воно вирішить символи підстановки. Якщо ви цього хочете, чудово, але якщо ви хочете функціональність /F, то вам просто потрібно використовувати tokensключове слово:FOR /F "tokens=*" %%I IN ("..\relative Path\*") DO echo absolute path: %%~fI
SvenS

15

Це допоможе заповнити прогалини у відповіді Адріена Пліссона (яку слід анулювати, як тільки він її редагує ;-):

ви також можете отримати повністю кваліфікований шлях вашого першого аргументу, використовуючи% ~ f1, але це дає шлях відповідно до поточного шляху, який, очевидно, не є тим, що ви хочете.

на жаль, я не знаю, як змішати два разом ...

Можна впоратися %0і %1так само:

  • %~dpnx0для повноцінного диска + шлях + ім'я + розширення самого пакетного файлу
    %~f0також достатньо;
  • %~dpnx1для повноцінного диск + шлях + ім'я + розширення першого аргументу [якщо це взагалі ім'я файлу],
    %~f1також достатньо;

%~f1буде працювати незалежно від того, як ви вказали свій перший аргумент: з відносними шляхами або з абсолютними шляхами (якщо ви не вкажете розширення файлу при іменуванні %1, воно не буде додано, навіть якщо ви використовуєте %~dpnx1- проте.

Але як же ви все-таки назвали файл на іншому диску, якщо ви в першу чергу не дасте повну інформацію про цей шлях у командному рядку?

Однак %~p0, %~n0, %~nx0і %~x0може стати в нагоді, ви повинні бути зацікавлені в дорозі (без буква_диска), ім'я файлу (без розширення), повне ім'я файлу з розширенням або розширення імені файлу тільки. Але зауважте, поки %~p1і %~n1буде працювати над тим, щоб з'ясувати шлях або ім'я першого аргументу, %~nx1і %~x1не додаватиме + показувати розширення, якщо ви вже не використовували його в командному рядку.


Проблема %~dpnx1полягає в тому, що він дає повністю кваліфікований шлях аргументу щодо поточного каталогу , тоді як ОП хоче шлях відносно до каталогу, де знаходиться пакетний файл .
Адріан Пліссон

@Adrien Plisson: %~dpnx1дає повністю кваліфікований шлях 1-го аргументу. Зовсім не відносно і не відносно поточного режиму. Це dдля диска, pшлях для шляху, nдля суфікса ім'я файлів, xсуфікс, 1для першого аргументу. - І питання Натана було: "Чи є спосіб повернути абсолютний шлях зі значення, що містить ім'я файлу та / або відносний шлях?"
Курт Пфайфл

Як абсолютне, так і відносне використання шляхів та джерело з описом . Вдячний!
it3xl

10

Ви також можете використовувати для цього пакетні функції:

@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

9

Невелике вдосконалення для відмінного рішення BrainSlugs83 . Узагальнено, щоб дозволити називати змінну вихідного середовища у виклику.

@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

Якщо запуск з C:\projectвиводу:

C:\project\doc\build

4

Я не бачив багатьох рішень цієї проблеми. Деякі рішення використовують обхід каталогів за допомогою CD, а інші використовують пакетні функції. Мої особисті переваги були для пакетних функцій і, зокрема, функції MakeAbsolute , передбаченої DosTips.

Функція має деякі реальні переваги, перш за все, це те, що вона не змінює поточну робочу директорію, по-друге, що оцінювані контури навіть не повинні існувати. Ви можете знайти деякі корисні поради про те , як використовувати функцію тут теж.

Ось приклад сценарію та його результатів:

@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

І вихід:

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

Я сподіваюся, що це допомагає ... Це точно допомогло мені :) PS Дякую ще раз DosTips! Ти рок!


дякую @ulidtko. Я раніше не пробував проти посилань.
dayneo

2

Ви можете просто їх об'єднати.

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

це виглядає дивно з \ .. \ посеред вашого шляху, але це працює. Не треба робити нічого божевільного :)


Це просто працює. Можна додатково спростити доecho %~dp0..\SomeFile.txt
каулінатор

1

У вашому прикладі з Bar \ test.bat DIR / B / S .. \ somefile.txt поверне повний шлях.


Це не погана ідея ... чи слід перебирати результат DIR з FOR і просто брати перший результат?
Натан Тейлор

Я подумав про проблему з кількома файлами. Ви не вказували, чи є проблема множинними, тому я пішов із цим. Що стосується циклу FOR, у мене виникають проблеми з подачею циклу "../" на цикл, але ymmv. Якщо ви не можете перейти до команди DIR, ви можете перенаправити вихід у файл і пропустити цикл через це. Я не кажу, що "стандартний" спосіб видобутку шляху поганий, це просто те, що я думав, що моя зробила більше того, що ви просили. Обидва рішення роблять "щось", і в обох є свій набір питань.
Джордж Сіско

Ох, і якщо ви успішно пропрацюєте DIR, ви можете перезаписати переадресацію у файл і отримати останній результат. Якщо ви скасуєте замовлення у прийнятному для вас способі, ви можете отримати лише перший результат. Якщо вам доведеться пройти цикл через файл, також може допомогти зміна порядку для отримання першого результату, який був би в останньому положенні. Я вважаю, що це може бути простіше дістати і відкинути багато речей, а не реально з ними боротися. Я не знаю, чи має мій спосіб мислення для вас сенс. Просто виклавши його там як ідею.
Джордж Сіско

Якщо вам необхідно нормалізувати шлях до папки, я пропоную це рішення: stackoverflow.com/questions/48764076 / ...
uceumern

0

PowerShell досить поширений в наші дні, тому я часто використовую його як швидкий спосіб викликати C #, оскільки в ньому є функції для майже всього:

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

Це трохи повільно, але набутий функціонал важко перемогти, якщо не вдатися до фактичної мови сценаріїв.


0

Рішення stijn працює з папками під 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%

0

Файли Дивіться всі інші відповіді

Довідники

Що ..є вашим відносним шляхом та припускаючи, що ви зараз перебуваєте D:\Projects\EditorProject:

cd .. & cd & cd EditorProject (відносний шлях)

повертає абсолютний шлях, наприклад

D:\Projects


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.