Сначала я покажу, как преобразовать аргумент пакетного файла %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 на форуме Computer Hope, который использует фиктивный ярлык для преобразовать короткую форму в полную.
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
Я нашел аналогичный код в архив группы новостей Microsoft и старый форум 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 — он также сохраняет короткие имена папок.
- Аргумент или модификатор переменной FOR
%~f1
или %%~fA
- сохраняет короткие имена
- FORFILES - не поддерживает короткие имена.
Единственное решение, которое мне удалось найти, — это использовать DIR для многократного преобразования каждой папки в пределах пути, по одной за раз. Это требует, чтобы я использовал DIR /X /B /AD
для перечисления всех папок в родительской папке, включая их имена 8.3, а затем использовал FINDSTR для поиска правильного короткого имени папки. Я полагаюсь на тот факт, что короткое имя файла всегда появляется в одном и том же месте после текста <DIR>
. Как только я нахожу правильную строку, я могу использовать переменную подстроку или операции поиска/замены или FOR /F для анализа длинного имени папки. Я решил использовать 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, используемый для перебора отдельных папок, замедлит работу, если папок много. Если бы я действительно хотел оптимизировать скорость, я мог бы использовать FOR /F для запуска другого пакетного процесса и разрешать каждую папку в бесконечном цикле FOR /L %%N IN () DO...
и использовать EXIT
для выхода из цикла, как только я достигну корня. Но я не стал заморачиваться.
Разработка надежных утилит, которые могут возвращать результат в переменной
Существует ряд крайних случаев, которые могут усложнить разработку надежного скрипта, учитывая, что ^
, %
и !
являются допустимыми символами в именах файлов/папок.
CALL удваивает ^
символ в кавычках. У этой проблемы нет хорошего решения, кроме передачи значения по ссылке с использованием переменной вместо строкового литерала. Это не проблема, если во входном пути используются только короткие имена. Но это может быть проблемой, если путь использует сочетание коротких и длинных имен.
Передача %
литералов в пакетных аргументах может быть сложной. Может возникнуть путаница в отношении того, сколько раз (если вообще) его следует удвоить. Опять же, может быть проще передать значение по ссылке внутри переменной.
CALLer может вызвать утилиту из цикла FOR. Если переменная или аргумент содержат %
, то расширение %var%
или %1
в цикле утилиты может привести к непреднамеренному расширению переменной FOR, поскольку переменные FOR являются глобальными по области действия. Утилита не должна раскрывать аргументы в цикле FOR, а переменные можно безопасно раскрывать в цикле FOR, только если используется отложенное раскрытие.
Расширение переменных FOR, содержащих !
, будет повреждено, если включено отложенное расширение.
В среде CALLing может быть включено или отключено отложенное расширение. Передача значений, содержащих !
и ^
, через барьер ENDLOCAL в среду с отложенным расширением требует, чтобы !
в кавычках экранировалось как ^!
. Кроме того, ^
в кавычках необходимо экранировать как ^^
, но только если строка содержит !
. Конечно, эти символы не следует экранировать, если в среде CALLing отключено отложенное расширение.
Я разработал надежные служебные формы как для JScript, так и для чистых пакетных решений, которые учитывают все крайние случаи, описанные выше.
Утилиты по умолчанию принимают путь в виде строкового литерала, но принимают имя переменной, содержащее путь, если используется опция /V
.
По умолчанию утилиты просто выводят результат на стандартный вывод. Но результат может быть возвращен в переменной, если вы передадите имя возвращаемой переменной в качестве дополнительного аргумента. Правильное значение гарантированно будет возвращено, независимо от того, включено или отключено отложенное расширение в вашей среде CALLing.
Полная документация встроена в утилиты и доступна с помощью опции /?
.
Есть несколько непонятных ограничений:
- Имя возвращаемой переменной не должно содержать символов
!
или %
.
- Аналогично, имя входной переменной опции
/V
не должно содержать символов !
или %
.
- Входной путь не должен содержать внутренних двойных кавычек. Путь может быть заключен в один набор двойных кавычек, но внутри не должно быть никаких дополнительных кавычек.
Я не проверял, работают ли утилиты с юникодом в именах путей или с путями UNC.
jLongPath.bat — гибридный JScript/пакетный
@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.bat — чистая партия
:::
:::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
person
dbenham
schedule
26.12.2015