我正在编写Windows批处理脚本,我有一个参数或变量,其中包含使用短8.3名称的路径.路径可能表示文件或文件夹.
如何将8.3路径转换为长名称路径?至少我希望能够简单地打印出完整的长名称路径.但理想情况下,我想安全地将长名称路径转换为新变量.
例如,给定路径C:\PROGRA~1\AVASTS~1\avast\BROWSE~1.INI
,我想返回C:\Program Files\AVAST Software\avast\BrowserCleanup.ini
.
作为批处理爱好者,我最感兴趣的是只使用本机Windows命令的纯批处理解决方案.但是,混合使用PowerShell和JScript等其他本机脚本工具也是可以接受的.
注意: 我发布了自己的答案.我在网上搜索,并惊讶地发现这个主题很少.我开发了多种工作策略,并认为其他人可能会对我发现的内容感兴趣.
首先,我将演示如何转换批处理文件参数%1
并将结果打印到屏幕.
电源外壳
最简单的解决方案是使用PowerShell.我在Sergey Babkin的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
我在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到路径然后查看提示 - 它保留短文件夹名称
带有/ L和/ F选项的XCOPY - 它还保留短文件夹名称
参数或FOR变量修饰符%~f1
或%%~fA
- 保留短名称
FORFILES - 似乎不支持短名称.
我能想到的唯一解决方案是使用DIR迭代转换路径中的每个文件夹,一次一个.这需要我DIR /X /B /AD
用来列出父文件夹中的所有文件夹,包括它们的8.3名称,然后使用FINDSTR找到正确的短文件夹名称.我依赖的事实是短文件名总是出现在
文本后面的完全相同的位置.找到正确的行后,我可以使用变量子字符串或查找/替换操作,或者使用FOR/F来解析长文件夹名称.我选择使用FOR/F.
我遇到的另一个绊脚石是确定原始路径是代表文件还是文件夹.IF EXIST "yourPath\" echo FOLDER
如果路径涉及符号链接或联结,则常用的方法是附加反斜杠并使用不正确的方式将文件报告为文件夹,这在公司网络环境中很常见.
我选择使用IF EXIST "yourPath\*"
,发现于/sf/ask/17360801/.
但也可以使用FOR变量%%~aF
属性修饰符来查找d
(目录)属性,可在/sf/ask/17360801/8742/1012053和/sf/ask/17360801/找到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
在我到达根目录时使用它来突破循环.但我没有打扰.
Devoloping强大的实用程序,可以返回变量中的结果
有许多的优势情况下考虑到可以复杂强大的脚本开发的是^
,%
和!
在文件/文件夹名字都是合法字符.
CALL加倍引用的^
字符.除了使用变量而不是字符串文字通过引用传递值之外,这个问题没有很好的解决方案.如果输入路径仅使用短名称,则这不是问题.但如果路径使用短名称和长名称的混合,则可能是一个问题.
%
在批处理参数中传递文字可能很棘手.对于谁多次(如果有的话)应该加倍,可能会让人感到困惑.同样,在变量中通过引用传递值可能更容易.
CALLer可以在FOR循环内调用该实用程序.如果变量或参数包含%
,则实用程序中循环的扩展%var%
或%1
循环内部可能会导致无意的FOR变量扩展,因为FOR变量的范围是全局的.该实用程序不能在FOR循环中扩展参数,并且如果使用延迟扩展,则只能在FOR循环内安全地扩展变量.
!
如果启用了延迟扩展,则包含FOR变量的扩展将被破坏.
CALLing环境可能启用或禁用了延迟扩展.将包含ENDLOCAL屏障!
和^
跨越ENDLOCAL屏障的值传递到延迟扩展环境需要将引用!
转义为^!
.此外,引用^
必须转义为^^
,但仅限于行包含!
.当然,如果CALLing环境已禁用延迟扩展,则不应转义这些字符.
我已经开发了JScript和纯批处理解决方案的强大实用形式,它们考虑了上述所有边缘情况.
默认情况下,实用程序将路径视为字符串文字,但如果使用该/V
选项,则接受包含路径的变量名称.
默认情况下,utilties只是将结果打印到stdout.但是如果将返回变量的名称作为额外参数传递,则结果可以在变量中返回.无论是否在CALLing环境中启用或禁用延迟扩展,都可以保证返回正确的值.
完整文档嵌入在实用程序中,可以使用该/?
选项进行访问.
有一些模糊的限制:
返回变量名称不得包含!
或%
字符
同样,/V
选项输入变量名称不得包含!
或%
字符.
输入路径不得包含内部双引号.路径可以用一组双引号括起来,但不应该有任何额外的引号.
我还没有测试实用程序是否在路径名中使用unicode,或者它们是否与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