Разрешить абсолютный путь из относительного пути и / или имени файла

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идиому в моем пакетном файле, которая интерпретирует первый аргумент относительно пути исполняемого пакета. Однако у него есть недостаток: он с треском проваливается, если первый аргумент полностью квалифицирован.

Если вам нужно поддерживать как относительные, так и абсолютные пути, вы можете воспользоваться решением Фредерика Менеза : временно изменить текущий рабочий каталог.

Вот пример, который продемонстрирует каждый из этих методов:

@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 как

c: \ Users \ Public> \ 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для полного пути + имя его первого аргумента [если это вообще имя файла]. (Но как же вы назвали бы файл на другом диске, если бы в любом случае не
указали
Этот пример гораздо сложнее, чем ответ @ 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 Полезная коллекция пакетных методов и примеров сценариев - 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=%~f1
  EXIT /B
BrainSlugs83
источник
7
Это действительно чистое, рабочее и простое решение.
Разван
3
Очень лаконично Сейчас я широко использую эту технику.
Пауло Франса Ласерда
1
Почему это% ~ dpfn1, а не просто% ~ f1? Это какой-то обходной путь? % ~ f1 прекрасно работает на Windows 7 и Windows 10, по крайней мере.
il - ya
1
@ il - ya выглядит %~f1просто коротко %~dpfn1- так что не стесняйтесь использовать %~f1вместо этого. 🙂 - в длинном формате ~означает удалить кавычки, dозначает диск, pозначает путь, nсамо по себе будет имя файла без расширения, но fnозначает имя файла с расширением. 1означает первый аргумент. - (Я полагаю, этот формат предшествует Windows 7.)
BrainSlugs83
1
Ах, ты победил меня в этом! Согласно комментарию в исходном коде nt4, файл nt4 \ private \ windows \ cmd \ clex.c (от 27.08.1997), функция MSCmdVar (), "//% ~ fi - расширяет% i до полного пути имя "Извините за приставание к вам. Я надеялся, что смогу разобраться в этом заблуждении. Я наткнулся на% ~ dpfn, просматривая довольно недавний код, выдернул пару своих волосков, пытаясь понять, что он должен делать (учтите, у меня их немного осталось), и начал исследовать, как он возник в жизни, как личная вендетта.
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
Себастьян
1
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, но это дает путь в соответствии с текущим путем, что, очевидно, не то, что вам нужно.

к сожалению, я не знаю, как смешать 2 вместе ...

Можно справиться %0и %1аналогично:

  • %~dpnx0для полностью определенного диска + путь + имя + расширение самого пакетного файла
    %~f0также достаточно;
  • %~dpnx1для полностью определенного диска + путь + имя + расширение его первого аргумента [если это вообще имя файла],
    %~f1также достаточно;

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

Но как на самом деле вы бы назвали файл на другом диске в любом случае, если бы не указали эту полную информацию о пути в командной строке?

Однако %~p0, %~n0, %~nx0и %~x0может пригодиться, вы должны быть заинтересованы в пути (без Буква_диска), имя файла (без расширения), полное имя файла с расширением или расширение имени файла только. Но учтите, что while %~p1и %~n1будет работать, чтобы выяснить путь или имя первого аргумента, %~nx1и %~x1не будет добавлять + показывать расширение, если вы уже не использовали его в командной строке.

Курт Пфайфл
источник
проблема в %~dpnx1том, что он дает полный путь к аргументу относительно текущего каталога , в то время как OP хочет путь относительно каталога, в котором находится пакетный файл .
Адриан Плиссон
1
@ 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! Ты жжешь!

dayneo
источник
спасибо @ulidtko. Я не пробовал против символических ссылок раньше.
Dayneo
2

Вы можете просто объединить их.

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

это выглядит странно с \ .. \ в середине вашего пути, но это работает. Не нужно ничего сумасшедшего делать :)

Кристен
источник
Это просто работает. Может быть дополнительно упрощено доecho %~dp0..\SomeFile.txt
cowlinator
1

В вашем примере из Bar \ test.bat DIR / B / S .. \ somefile.txt вернет полный путь.

Джордж Сиско
источник
Это неплохая идея ... я должен зациклить результат DIR с FOR и просто получить первый результат?
Натан Тейлор
Я думал о проблеме с несколькими файлами. Вы не указали, были ли кратны проблемы, поэтому я согласился. Что касается цикла 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
Сергей Сачков
источник