Получить полный путь и длинное имя файла из короткого имени файла
У меня есть хороший менеджер файлов консоли (eXtreme by Senh Liu), он передает короткие пути/имена файлов в качестве переменных в menu.bat.
Как я могу сгенерировать полное имя папки + длинное имя файла?
Пример:
- input variable = "P:\MYPROG ~ 1\SHELLS\ZBACKUP\REFSTO ~ 1.BAL"
- target variable = "P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
Я пробовал следующее:
-
SET my_file=%~2
:
echo %my_file%
производит: "P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
-
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~fF
:
echo %my_file%
производит: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
-
FOR /F "tokens=* USEBACKQ" %%F IN (`dir /B %2`) DO SET my_file=%%~dpnxF
:
echo %my_file%
производит: "P:\MYPROG~1\SHELLS\zBackup\RefsToMyData.bal"
Ответы
Ответ 1
Следующее должно работать с любым допустимым путем, если это не путь UNC. Путь может быть абсолютным или относительным. Он может использовать короткие имена файлов или длинные имена (или смесь). Путь может относиться к папке или файлу.
Результат заканчивается на \
, если это папка, а не \
в конце, если это файл.
Подпрограмма :getLongPath
ожидает имя переменной inputPath как 1-й аргумент и необязательное имя возвращаемой переменной в качестве второго аргумента. Переменная inputPath должна содержать допустимый путь. Если возвращаемая переменная не указана, результат будет ECHOed на экране (заключен в кавычки). Если указана возвращаемая переменная, результат возвращается в переменной (без кавычек).
Подпрограмма должна вызываться только тогда, когда замедленное расширение отключается, если вы возвращаете переменную. Если вызвано с задержкой расширения включен, то результат будет поврежден, если он содержит символ !
.
Тесты (только для моей машины) находятся в верхней части script, фактическая процедура внизу.
@echo off
setlocal
for %%F in (
"D:\test\AB2761~1\AZCFE4~1.TXT"
"AB2761~1\AZCFE4~1.TXT"
"D:\test\AB2761~1\ZZCE57~1\"
"D:\test\a b\a z.txt"
"D:\test\a b\z z"
"."
"\"
"x%%&BAN~1\test"
"x%% & bang!\test"
) do (
echo(
echo resolving %%F
set "shortPath=%%~F"
call :getLongPath shortPath longPath
set longPath
)
echo(
echo(
set "shortPath=D:\test\AB2761~1\AZCFE4~1.TXT"
set shortPath
echo Calling :getLongPath with with no return variable
call :getLongPath shortPath
exit /b
:getLongPath path [rtnVar]
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "eol=: delims=" %%: in ('dir /b /ad') do (
if /i "%%~snx:" equ "%folder%" (
set "rtn=%%:\%rtn%"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%
( endlocal
if "%~2" equ "" (echo "%rtn%") else set "%~2=%rtn%"
)
=== OUTPUT ===
resolving "D:\test\AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "AB2761~1\AZCFE4~1.TXT"
longPath=D:\test\a b\a z.txt
resolving "D:\test\AB2761~1\ZZCE57~1\"
longPath=D:\test\a b\z z\
resolving "D:\test\a b\a z.txt"
longPath=D:\test\a b\a z.txt
resolving "D:\test\a b\z z"
longPath=D:\test\a b\z z\
resolving "."
longPath=D:\test\
resolving "\"
longPath=D:\
resolving "x%&BAN~1\test"
longPath=D:\test\x% & bang!\test\
resolving "x% & bang!\test"
longPath=D:\test\x% & bang!\test\
shortPath=D:\test\AB2761~1\AZCFE4~1.TXT
Calling :getLongPath with with no return variable
"D:\test\a b\a z.txt"
Если вы хотите запустить вышеуказанный код, я предлагаю вам полностью удалить весь код сценария тестирования между @echo off
и :getLongPath
. Затем вы можете просто вызвать script, передав любой допустимый путь в качестве первого аргумента. В результате должен быть напечатан правильный длинный путь.
Я был поражен тем, как трудно было это решить, используя партию. Я не думаю, что это намного проще с JScript или VBS (на самом деле Ansgar нашел хорошее решение VBS). Но мне нравится Ansgar простое решение PowerShell - намного проще.
Обновление
Я нашел неясный случай, когда вышеуказанный код выходит из строя, если вызван из цикла FOR, и в пути есть переменная FOR внутри него. Он также неправильно сообщает путь с дикими картами как ошибку, и он не работает с задержкой расширения, если путь содержит !
.
Итак, я создал модифицированную версию ниже. Я уверен, что он действительно должен работать во всех ситуациях, за исключением UNC-путей и, возможно, не с юникодом в пути. Я упаковал его в качестве простой процедуры вызова, в комплекте со встроенной документацией. Его можно оставить автономным script или включить в более крупный script.
@echo off
:getLongPath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
setlocal enableDelayedExpansion
for /f "eol=: delims=" %%F in ("!%~1!") do (
endlocal
set "sourcePath=%%~sF"
set "sourcePath2=%%F"
set "sourceFile=%%~nxF"
)
if not exist "%sourcePath%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "sourcePath3=%sourcePath2:**=%"
set "sourcePath3=%sourcePath3:?=%"
if "%sourcePath3%" neq "%sourcePath2%" (
>&2 echo ERROR: Invalid path
exit /b 1
)
set "rtn="
2>nul cd "%sourcePath%" || (
cd "%sourcePath%\.."
for /f "eol=: delims=" %%F in ('dir /b /a-d "%sourceFile%"') do set "rtn=%%F"
)
:resolveFolders
for %%F in ("%cd%") do (
cd ..
set "folder=%%~nxF"
)
if defined folder for /f "delims=: tokens=1,2" %%A in ("%folder%:%rtn%") do for /f "eol=: delims=" %%F in ('dir /b /ad') do (
if /i "%%~snxF" equ "%%A" (
set "rtn=%%F\%%B"
goto :resolveFolders
)
)
set "rtn=%cd%%rtn%"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
Я также адаптировал Ansgar VBS в гибридный JScript/пакет script. Он должен обеспечить идентичный результат, как чистая партия script выше, но JScript намного проще следовать.
@if (@X)==(@Y) @end /* harmless hybrid line that begins a JScrpt comment
@echo off
:getLongpath
:::
:::getLongPath PathVar [RtnVar]
:::getLongPath /?
:::
::: Resolves the path contained in PathVar into the full long path.
::: If the path represents a folder then it will end with \
:::
::: The result is returned in variable RtnVar.
::: The result is echoed to the screen if RtnVar is not specified.
:::
::: Prints this documentation if the first argument is /?
::************ Batch portion ***********
if "%~1" equ "" (
>&2 echo ERROR: Insufficient arguments. Use getLongPath /? to get help.
exit /b 1
)
if "%~1" equ "/?" (
for /f "delims=" %%A in ('findstr "^:::" "%~f0"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
echo(!ln:~3!
endlocal
)
exit /b 0
)
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "rtn="
for /f "delims=" %%A in ('cscript //E:JScript //nologo "%~f0" %*') do set "rtn=%%A"
if not defined rtn exit /b 1
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
if not defined notDelayed (set "!=!==!") else set "!="
for %%A in ("%rtn%") do (
endlocal
endlocal
if "%~2" equ "" (echo %%~A%!%) else set "%~2=%%~A"!
)
exit /b 0
************ JScript portion ***********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var app=WScript.CreateObject("Shell.Application");
var inPath=env(WScript.Arguments.Item(0));
var folder="";
var f;
if (fso.FileExists(inPath)) {
f=fso.GetFile(inPath);
}
else if (fso.FolderExists(inPath)) {
folder="\\"
f=fso.GetFolder(inPath);
if (f.IsRootFolder) {
WScript.StdOut.WriteLine(f.Path);
WScript.Quit(0);
}
}
else {
WScript.StdErr.WriteLine('ERROR: Invalid path');
WScript.Quit(1);
}
WScript.StdOut.WriteLine( app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path + folder);
Ответ 2
Простое решение: используйте PowerShell.
PS C:\> (Get-Item 'P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL').FullName
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
Вы можете включить вызов PowerShell в пакетном файле следующим образом:
@echo off
setlocal
for /f "usebackq delims=" %%f in (
`powershell.exe -Command "(Get-Item '%~1').FullName"`
) do @set "var=%%~f"
echo %var%
Вывод:
C:\> test.cmd P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal
PowerShell доступен для всех поддерживаемых версий Windows:
- Windows XP SP3 и Server 2003 с пакетом обновления 2 (SP2): PowerShell v2 доступно
- Windows Vista и Server 2008: поставляются с PowerShell v1 (не установлены по умолчанию), PowerShell v2 доступно
- Windows 7 и Server 2008 R2: предустановленная версия PowerShell v2, PowerShell v3 доступно (батареи не включены)
- Windows 8 и Server 2012: предустановленная версия PowerShell v3
Если PowerShell по какой-либо причине нельзя использовать (например, административные ограничения), я бы вместо этого использовал VBScript:
name = WScript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(name) Then
Set f = fso.GetFile(name)
ElseIf fso.FolderExists(name) Then
Set f = fso.GetFolder(name)
If f.IsRootFolder Then
WScript.Echo f.Path
WScript.Quit 0
End If
Else
'path doesn't exist
WScript.Quit 1
End If
Set app = CreateObject("Shell.Application")
WScript.Echo app.NameSpace(f.ParentFolder.Path).ParseName(f.Name).Path
VBScript, подобный приведенному выше, может использоваться в пакетном файле следующим образом:
@echo off & setlocal
for /f "delims=" %%f in ('cscript //NoLogo script.vbs "%~1"') do @set "var=%%~f"
echo %var%
Однако для этого требуется дополнительный файл script.
Ответ 3
Это возвращает полный длинный путь, но зависит от:
A) в дереве не так много файлов (из-за времени)
B) в дереве есть только одно из целевого (длинного) имени файла.
@echo off
for /f "delims=" %%a in (' dir /b "%~1" ') do set "file=%%a"
for /f "delims=~" %%a in ("%~dp1") do cd /d "%%a*"
for /f "delims=" %%a in ('dir /b /s /a-d "%file%" ') do set "var=%%a"
echo "%var%"
Когда вызывается с помощью mybat "d:\MYPROG~1\SHELLS\zBackup\REFSTO~1.BAL"
он ответил так:
"d:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal"
Ответ 4
И одно неожиданно простое решение:
echo lcd %some_path%|ftp
EDITED, чтобы показать пример: это не 100%
d:\>echo lcd C:\Files\Download\MYMUSI~1\iTunes\ALBUMA~1 |ftp
Local directory now C:\Files\Download\MYMUSI~1\iTunes\Album Artwork.
Ответ 5
@echo off
setlocal
rem this need to be a short name to avoid collisions with dir command bellow
cd C:\BALBAL~1\BLBALB~1\
set "curr_dir=%cd%"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do @dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo --%full_path%--
cd %curr_dir%
endlocal
Путь жестко запрограммирован в начале, но вы можете изменить его или параметризовать его. Поскольку вы можете легко получить полное имя файла, это всего лишь решение для каталогов.
ИЗМЕНИТЬ
теперь работает для файла и каталога и может передаваться параметр:
@echo off
rem ---------------------------------------------
rem ---------------------- TESTS ----------------
rem ----------------------------------------------
md "c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes" >nul 2>&1
md "c:\test\1 b1\1\" >nul 2>&1
for %%t in ("c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes") do set t_dir=%%~st
for %%t in ("c:\test\1 b1\1\") do set t_dir2=%%~st
echo a>"%t_dir2%a"
echo a>"%t_dir2%a a.txt"
echo testing "%t_dir%\\"
call :get_full_name "%t_dir%\\"
echo(
echo testing "%t_dir2%a"
call :get_full_name "%t_dir2%a"
echo(
echo testing "%t_dir2%a a.txt" with return variable
call :get_full_name "%t_dir2%a a.txt" test_var
echo return variable : -- %test_var% --
goto :eof
rem -------------------------------------
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
set "curr_dir=%cd%"
for /f "delims=" %%n in ('dir /b /n "%~dps1\%~snx1"') do set "name=%%n"
cd "%~dps1"
set "full_path="
:repeat
for /f "delims=" %%f in ('for %%d in ^(.^) do @dir /a:d /n /b "..\*%%~snd"') do (
set "full_path=%%~f\%full_path%"
)
cd ..
if ":\" NEQ "%cd:~-2%" (
goto :repeat
) else (
set "full_path=%cd%%full_path%"
)
echo %full_path%%name%
cd %curr_dir%
endlocal & if "%~2" NEQ "" set "%~2=%full_path%%name%"
и тестовый выход:
testing "c:\test\BLABLA~1\BLABLA~1\NONONO~1\YESYES~1\\"
c:\test\blablablablabl\bla bla bla\no no no no no no\yes yes yes\
testing "c:\test\1B1~1\1\a"
c:\test\1 b1\1\a
testing "c:\test\1B1~1\1\a a.txt" with return variable
c:\test\1 b1\1\a a.txt
return variable : -- c:\test\1 b1\1\a a.txt --
Ответ 6
Это уродливое пакетное задание, и мой код не очень приятный, но сила брута :-)
@echo off &SETLOCAL
SET "short=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL"
SET "shorty=%short:\= %"
FOR %%a IN (%short%) DO SET "shortname=%%~nxa"
FOR %%a IN (%shorty%) DO (
IF DEFINED flag (
CALL :doit "%%~a"
) ELSE (
SET "longpath=%%~a"
SET flag=true
SET "first=\"
)
)
ECHO "%longpath%"
goto:eof
:doit
SET "last=%~1"
IF "%last%" neq "%shortname%" (SET "isDir=/ad") ELSE SET "isDir=/a-d"
FOR /f "delims=" %%b IN ('dir %isdir% %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X0=%%b"
FOR /f "delims=" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "X1=%%b"
REM for European time format
IF "%X0: =%"=="%X1: =%" (SET /a token=3) ELSE SET /a token=4
REM for "AM/PM" time format
IF "%X0: =%"=="%X1: =%" (SET /a token=4) ELSE SET /a token=5
FOR /f "tokens=%token%*" %%b IN ('dir %isdir% /x %longpath%%first%^|findstr /ri "\<%last%\>"') DO SET "longname=%%~c"
SET "longpath=%longpath%\%longname%"
SET "first="
goto:eof
Пожалуйста, установите формат времени в функции doit
(удалите в соответствующем формате).
Возможно, это может быть неудачно с особыми символами в именах путей или файлов, например !%=&^
.
Ответ 7
И одна попытка с WMIC
и Win32_Directory
. Вероятно медленнее, чем при использовании cd
и dir
, но текущий каталог не изменяется:
@echo off
:get_full_name [%1 - short path to a file or directory ; %2 - if set stores the result in variable with that name]
setlocal
if not exist "%~1" ( echo file/dir does not exist & exit /b 2 )
for /f "delims=" %%n in ('dir /b /n "%~dps1\*%~snx1"') do set "name=%%n"
set "short_path=%~dps1"
set "short_path=%short_path:~0,-1%"
set "drive=%short_path:~0,2%"
set "full_name="
:repeat
set "short_path=%short_path:\=\\%"
set "short_path=%short_path:'=\'%"
FOR /F "usebackq skip=2 delims=" %%P in (`WMIC path win32_directory where name^='%short_path%' get Path^,FileName /Format:Textvaluelist.xsl`) do for /f "delims=" %%C in ("%%P") do (
set "_%%C"
)
set "_Path=%_Path:~0,-1%"
set full_name=%_FileName%\%full_name%
if "%_Path%" NEQ "" (
set "short_path=%drive%%_Path%"
goto :repeat
) else (
set full_name=%drive%\%_FileName%\%full_name%
)
echo %full_name%%name%
endlocal if "%~2" NEQ "" set "%~2=%full_path%%name%"
Не тяжело испытано еще....
Ответ 8
Ниже приведен пакет script на основе npocmaka, используя ftp
(вместе с его подкомандой lcd
). Там вы можете видеть, что только последний элемент пути расширяется до длинного имени. Моя идея теперь применить подкоманду lcd
для каждого элемента пути индивидуально, поэтому мы получим полные имена всех элементов в конечном результате.
Этот script работает только для каталогов. Он не работает для файлов, и он не работает для UNC-путей.
Итак, идем:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ARGS=%*"
set FTP_CMD=lcd
set "TEMP_FILE=%TEMP%\%~n0_%RANDOM%.tmp"
setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
endlocal
set "ARG=%%~fA" & set "SEP=\"
setlocal EnableDelayedExpansion
> "%TEMP_FILE%" (
for %%D in ("!ARG:\=" "!") do (
endlocal
if not "%%~D"=="" (
set "ITEM=%%~D"
setlocal EnableDelayedExpansion
echo(%FTP_CMD% "!ITEM!!SEP!"
endlocal
set "SEP="
)
setlocal EnableDelayedExpansion
)
)
set "PREFIX="
for /F "delims=" %%L in ('^< "%TEMP_FILE%" ftp') do (
endlocal
if not defined PREFIX set "PREFIX=%%L"
set "LONG_PATH=%%L"
setlocal EnableDelayedExpansion
)
set "PREFIX=!PREFIX::\.=!" & set "PREFIX=!PREFIX:~,-1!"
for /F "delims=" %%E in ("!PREFIX!") do (
set "LONG_PATH=!LONG_PATH:*%%E=!"
set "LONG_PATH=!LONG_PATH:~,-1!"
)
echo(!LONG_PATH!
)
endlocal
del /Q "%TEMP_FILE%"
endlocal
exit /B
В принципе существует цикл for %%D
, который выполняет итерацию по всем элементам данного пути (после того, как он был расширен до полного пути с помощью внешнего цикла for %%A
). Каждый элемент заключен в ""
и ему предшествует lcd
(подкоманда команды ftp
для изменения локального рабочего каталога). Для первого элемента пути, который представляет собой диск, привязка \
добавляется для ссылки на его корневой каталог. Каждая из этих построенных строк пути записывается во временный файл.
Затем временный файл перенаправляется в команду ftp
, поэтому он изменяет свой элемент пути к локальному рабочему каталогу по элементу пути. Выход ftp
захватывается контуром for /F %%L
. Фактически последняя строка вывода представляет интерес только потому, что она содержит полный длинный путь. Тем не менее, первая строка также сохраняется, где используется корневой каталог соответствующего диска. Это просто необходимо, чтобы легко извлечь префикс выходных строк, чтобы удалить его из выходной строки, содержащей полный путь (команда ftp
выводит что-то вроде Local directory now D:\.
на английские системы, но я хочу, чтобы script независимо от языка). Наконец, указанный префикс удаляется из полного длинного пути, и результат возвращается на консоль.