Получить полный путь и длинное имя файла из короткого имени файла

У меня есть хороший менеджер файлов консоли (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 независимо от языка). Наконец, указанный префикс удаляется из полного длинного пути, и результат возвращается на консоль.