Как преобразовать путь к короткому имени Windows в длинные имена в пакетном скрипте

Я пишу пакетный скрипт Windows, и у меня есть аргумент или переменная, содержащая путь, который использует короткие имена 8.3. Путь может представлять файл или папку.

Как преобразовать путь 8.3 в путь с длинным именем? Как минимум, я хотел бы иметь возможность просто распечатать полный длинный путь. Но в идеале я хотел бы безопасно получить путь с длинным именем в новую переменную.

например, если путь C:PROGRA~1AVASTS~1avastBROWSE~1.INI, Я хотел бы вернуться C:Program FilesAVAST SoftwareavastBrowserCleanup.ini.

как пакетный энтузиаст, я больше всего заинтересован в чистом пакетном решении, которое использует только собственные команды Windows. Но использование hybird других собственных скриптовых инструментов, таких как PowerShell и JScript, также приемлемо.

Примечание: я публикую свой собственный ответ на этот вопрос. Я поискал в интернете и был удивлен, найдя очень мало на эту тему. Я разработал несколько рабочих стратегий и подумал, что другие могут быть интересуюсь тем, что нашел.

1 ответов


сначала я продемонстрирую, как преобразовать аргумент пакетного файла %1 и распечатать результат на экране.

PowerShell

самое простое решение-использовать PowerShell. Я нашел следующий код в блог MSDN Сергея Бабкина

$long_path = (Get-Item -LiteralPath $path).FullName

поместить этот код в пакетный скрипт и распечатать результат тривиально:

@echo off
powershell "(Get-Item -LiteralPath '%~1').FullName"

однако я стараюсь избегать использования PowerShell в пакете для двух причины

  • PowerShell не является родным для XP
  • время запуска PowerShell значительно, поэтому он делает пакетный гибрид относительно медленным


CSCRIPT (JScript или VBS)

я нашел этот фрагмент VBS в компьютерный форум надежды который использует фиктивный ярлык для преобразования из короткой в длинную форму.

set oArgs = Wscript.Arguments
wscript.echo LongName(oArgs(0))
Function LongName(strFName)
Const ScFSO = "Scripting.FileSystemObject"
Const WScSh = "WScript.Shell"
   With WScript.CreateObject(WScSh).CreateShortcut("dummy.lnk")
     .TargetPath = CreateObject(ScFSO).GetFile(strFName)
     LongName = .TargetPath
   End With
End Function

я нашел аналогичный код в A Microsoft newsgroup архив старый форум vbscript.

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

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment

::----------- Batch Code-----------------
@echo off
cscript //E:JScript //nologo "%~f0" %1
exit /b

------------ JScript Code---------------*/
var shortcut = WScript.CreateObject("WScript.Shell").CreateShortcut("dummy.lnk");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var folder='';
try {
  shortcut.TargetPath = fso.GetFile(WScript.Arguments(0));
}
catch(e) {
  try {
    shortcut.TargetPath = fso.GetFolder(WScript.Arguments(0));
    folder='\'
  }
  catch(e) {
    WScript.StdErr.WriteLine(e.message);
    WScript.Quit(1);
  }
}
WScript.StdOut.WriteLine(shortcut.TargetPath+folder);


Чистый Пакет

удивительно, что мой веб-поиск не смог найти чистое пакетное решение. Так что я был предоставлен самому себе.

если вы знайте, что путь представляет собой файл, тогда просто преобразовать имя файла 8.3 в длинное имя с помощью dir /b "yourFilePath". Однако это не разрешает имена родительских папок.

ситуация еще хуже, если путь представляет собой папку. Невозможно перечислить определенную папку, используя только команду DIR - она всегда перечисляет содержимое папки вместо самого имени папки.

я попробовал несколько стратегий для обработки папки пути, и ни один из них не работал:

  • CD или PUSHD к пути, а затем посмотрите на приглашение-он сохраняет короткие имена папок
  • XCOPY с параметрами /L и /F-он также сохраняет короткие имена папок
  • аргумент или для модификатора переменной %~f1 или %%~fA - сохраняет короткие имена
  • FORFILES-не поддерживает короткие имена.

единственное решение, которое я смог придумать, - использовать DIR для итеративного преобразования каждой папки в пути, по одному за раз. Это требует, чтобы я использовал DIR /X /B /AD чтобы перечислить все папки в родительской папке, включая их имена 8.3, а затем использовать FINDSTR, чтобы найти правильное короткое имя папки. Я полагаюсь на то, что короткое имя файла всегда появляется в одном и том же месте после <DIR> текст. Как только я найду правильную строку, я могу использовать переменную substring или find/replace operations или FOR /F для анализа длинного имени папки. Я решил использовать для /Ф.

еще один камень преткновения, который я должен был определить, представляет ли исходный путь файл или папку. Часто используемый подход добавления обратной косой черты и использования IF EXIST "yourPath\" echo FOLDER неправильно сообщает файл как папку, если путь включает символическую ссылку или соединение, что является общим в сетевых средах компании.

я решил использовать IF EXIST "yourPath\*", найдено в https://stackoverflow.com/a/1466528/1012053.

но также возможно используйте переменную FOR %%~aF модификатор атрибутов для поиска d (каталог) атрибут, найденный в https://stackoverflow.com/a/3728742/1012053, и https://stackoverflow.com/a/8669636/1012053.

Итак, вот полностью рабочее чистое пакетное решение

@echo off
setlocal disableDelayedExpansion

:: Validate path
set "test=%~1"
if "%test:**=%" neq "%test%" goto :err
if "%test:?=%"  neq "%test%" goto :err
if not exist "%test%"  goto :err

:: Initialize
set "returnPath="
set "sourcePath=%~f1"

:: Resolve file name, if present
if not exist "%~1\*" (
  for /f "eol=: delims=" %%F in ('dir /b "%~1"') do set "returnPath=%%~nxF"
  set "sourcePath=%~f1\.."
)

:resolvePath :: one folder at a time
for %%F in ("%sourcePath%") do (
  if "%%~nxF" equ "" (
    for %%P in ("%%~fF%returnPath%") do echo %%~P
    exit /b 0
  )
  for %%P in ("%sourcePath%\..") do (
    for /f "delims=> tokens=2" %%A in (
      'dir /ad /x "%%~fP"^|findstr /c:">          %%~nxF "'
    ) do for /f "tokens=1*" %%B in ("%%A") do set "returnPath=%%C\%returnPath%"
  ) || set "returnPath=%%~nxF\%returnPath%"
  set "sourcePath=%%~dpF."
)
goto :resolvePath

:err
>&2 echo Path not found
exit /b 1

GOTO, используемый для итерации отдельных папок, замедлит работу, если есть много папок. Если бы я действительно хотел оптимизировать скорость, я мог бы использовать для /F для вызова другого пакетного процесса и разрешения каждой папки в бесконечном FOR /L %%N IN () DO... петли, и использовать EXIT вырваться из петли, как только я достигну корня. Но я не стал утруждать себя.


передача надежных утилит, которые могут возвращать результат в переменной

существует ряд крайних случаев, которые могут усложнить разработку надежного скрипта, учитывая, что ^, % и ! все законные символы в файле / папке имена.

  • Call doubles quoted ^ символы. Нет хорошего решения этой проблемы, кроме как передать значение по ссылке, используя переменную вместо строкового литерала. Это не проблема, если входной путь использует только короткие имена. Но это может быть проблемой, если путь используется смесь коротких и длинных имен.

  • передает % литералы в аргументах пакета могут быть сложными. Это может запутаться в том, кто многие раз (если вообще) он должен быть удвоен. Опять же, может быть проще передать значение по ссылке внутри переменной.

  • вызывающий может вызвать утилиту из цикла FOR. Если переменная или аргумент содержит %, затем расширение %var% или %1 в цикле в утилите может привести к непреднамеренному для переменной exansion, поскольку переменные for являются глобальными по области. Утилита не должна расширять аргументы в цикле FOR, а переменные могут быть только безопасно расширяется в цикле FOR, если используется отложенное расширение.

  • расширение для переменных, содержащих ! будет поврежден, если включено отложенное расширение.

  • вызывающая среда может иметь отложенное расширение включено или отключено. Передача значений, содержащих ! и ^ через ENDLOCAL барьером для замедленного среды расширение требует, что цитирует ! быть экранированы как ^!. Кроме того, цитируется ^ надо бежать как ^^, но только если строка содержит !. Конечно, эти символы не должны быть экранированы, если вызывающая среда отключила отложенное расширение.

я разработал надежные служебные формы как JScript, так и pure batch решений, которые учитывают все приведенные выше случаи edge.

утилиты по умолчанию ожидают путь в виде строкового литерала, но принимают имя переменной, содержащее путь, если /V используется опция.

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

полная документация встроена в утилиты и может быть доступна с помощью .

есть несколько неясные ограничения:

  • имя возвращаемой переменной не должно содержать ! или % символы
  • дополнительно /V имя входной переменной опции не должно содержать ! или % символы.
  • входной путь не должен содержать внутренних двойных кавычек. Это нормально, чтобы путь был заключен в один набор двойных кавычек, но внутри не должно быть никаких дополнительных кавычек.

я не проверял, утилиты работают с unicode в именах путей или если они работают с UNC-путями.


jLongPath.летучая мышь!--46--> - гибридный JScript / batch

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
:::
:::jLongPath  [/V]  SrcPath  [RtnVar]
:::jLongPath  /?
:::
:::  Determine the absolute long-name path of source path SrcPath
:::  and return the result in variable RtnVar.
:::
:::  If RtnVar is not specified, then print the result to stderr.
:::
:::  If option /V is specified, then SrcPath is a variable that
:::  contains the source path.
:::
:::  If the first argument is /?, then print this help to stdout.
:::
:::  The returned ERROLEVEL is 0 upon success, 1 if failure.
:::
:::  jLongPath.bat version 1.0 was written by Dave Benham
:::

::----------- Batch Code-----------------
@echo off
setlocal disableDelayedExpansion
if /i "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
if /i "%~1" equ "/V" shift /1
(
  for /f "delims=* tokens=1,2" %%A in (
    'cscript //E:JScript //nologo "%~f0" %*'
  ) do if "%~2" equ "" (echo %%A) else (
    endlocal
    if "!!" equ "" (set "%~2=%%B" !) else set "%~2=%%A"
  )
) || exit /b 1
exit /b 0

------------ JScript Code---------------*/
try {
  var shortcut = WScript.CreateObject("WScript.Shell").CreateShortcut("dummy.lnk"),
      fso = new ActiveXObject("Scripting.FileSystemObject"),
      path=WScript.Arguments(0),
      folder='';
  if (path.toUpperCase()=='/V') {
    var env=WScript.CreateObject("WScript.Shell").Environment("Process");
    path=env(WScript.Arguments(1));
  }
  try {
    shortcut.TargetPath = fso.GetFile(path);
  }
  catch(e) {
    shortcut.TargetPath = fso.GetFolder(path);
    folder='\'
  }
  var rtn = shortcut.TargetPath+folder+'*';
  WScript.StdOut.WriteLine( rtn + rtn.replace(/\^/g,'^^').replace(/!/g,'^!') );
}
catch(e) {
  WScript.StdErr.WriteLine(
    (e.number==-2146828283) ? 'Path not found' :
    (e.number==-2146828279) ? 'Missing path argument - Use jLongPath /? for help.' :
    e.message
  );
}


longPath.летучая мышь!--46--> - чисто пакетный

:::
:::longPath  [/V]  SrcPath  [RtnVar]
:::longPath  /?
:::
:::  Determine the absolute long-name path of source path SrcPath
:::  and return the result in variable RtnVar.
:::
:::  If RtnVar is not specified, then print the result to stderr.
:::
:::  If option /V is specified, then SrcPath is a variable that
:::  contains the source path.
:::
:::  If the first argument is /?, then prints this help to stdout.
:::
:::  The returned ERROLEVEL is 0 upon success, 1 if failure.
:::
:::  longPath.bat version 1.0 was written by Dave Benham
:::
@echo off
setlocal disableDelayedExpansion

:: Load arguments
if "%~1" equ "" goto :noPath
if "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
if /i "%~1" equ "/V" (
  setlocal enableDelayedExpansion
  if "%~2" equ "" goto :noPath
  if not defined %~2!! goto :notFound
  for /f "eol=: delims=" %%F in ("!%~2!") do (
    endlocal
    set "sourcePath=%%~fF"
    set "test=%%F"
  )
  shift /1
) else (
  set "sourcePath=%~f1"
  set "test=%~1"
)

:: Validate path
if "%test:**=%" neq "%test%" goto :notFound
if "%test:?=%"  neq "%test%" goto :notFound
if not exist "%test%" goto :notFound

:: Resolve file name, if present
set "returnPath="
if not exist "%sourcePath%\*" (
  for /f "eol=: delims=" %%F in ('dir /b "%sourcePath%"') do set "returnPath=%%~nxF"
  set "sourcePath=%sourcePath%\.."
)

:resolvePath :: one folder at a time
for /f "delims=* tokens=1,2" %%R in (^""%returnPath%"*"%sourcePath%"^") do (
  if "%%~nxS" equ "" for %%P in ("%%~fS%%~R") do (
    if "%~2" equ "" (
      echo %%~P
      exit /b 0
    )
    set "returnPath=%%~P"
    goto :return
  )
  for %%P in ("%%~S\..") do (
    for /f "delims=> tokens=2" %%A in (
      'dir /ad /x "%%~fP"^|findstr /c:">          %%~nxS "'
    ) do for /f "tokens=1*" %%B in ("%%A") do set "returnPath=%%C\%%~R"
  ) || set "returnPath=%%~nxS\%%~R"
  set "sourcePath=%%~dpS."
)
goto :resolvePath

:return
set "delayedPath=%returnPath:^=^^%"
set "delayedPath=%delayedPath:!=^!%"
for /f "delims=* tokens=1,2" %%A in ("%delayedPath%*%returnPath%") do (
  endlocal
  if "!!" equ "" (set "%~2=%%A" !) else set "%~2=%%B"
  exit /b 0
)

:noPath
>&2 echo Missing path argument - Use longPath /? for help.
exit /b 1

:notFound
>&2 echo Path not found
exit /b 1