如何在批处理脚本中将 Windows 短名称路径转换为长名称
How can I convert a Windows short name path into long names within a batch script
我正在编写一个 Windows 批处理脚本,并且我有一个参数或变量包含一个使用短 8.3 名称的路径。该路径可能表示一个文件或文件夹。
如何将 8.3 路径转换为长名称路径?至少我希望能够简单地打印出完整的长名称路径。但理想情况下,我想安全地将长名称路径放入新变量中。
例如,给定路径 C:\PROGRA~1\AVASTS~1\avast\BROWSE~1.INI
,我想 return C:\Program Files\AVAST Software\avast\BrowserCleanup.ini
.
作为批处理爱好者,我最感兴趣的是仅使用本机 Windows 命令的纯批处理解决方案。但混合使用其他本机脚本工具(如 PowerShell 和 JScript)也是可以接受的。
注意: 我发布了我自己对这个问题的回答。我在网上搜索了一下,惊讶地发现很少有关于这个主题的资料。我制定了多种工作策略,并认为其他人可能会对我的发现感兴趣。
首先,我将演示如何转换批处理文件参数 %1
并将结果打印到屏幕。
PowerShell
最简单的解决方案是使用 PowerShell。我在 MSDN blog by Sergey Babkin
上找到了以下代码
$long_path = (Get-Item -LiteralPath $path).FullName
将该代码放入批处理脚本并打印结果很简单:
@echo off
powershell "(Get-Item -LiteralPath '%~1').FullName"
但是,出于两个原因,我尽量避免在批处理中使用 PowerShell
- PowerShell 不是 XP 原生的
- PowerShell 的启动时间很长,因此批处理混合相对较慢
CSCRIPT(JScript 或 VBS)
我在 Computer Hope forum 找到了这个 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 newsgroup archive and an old vbscript forum 找到了类似的代码。
该代码仅支持文件路径,在批处理中嵌入 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);
纯批
令人惊讶的是,我的网络搜索未能找到纯批处理解决方案。所以我一个人。
如果你知道路径代表一个文件,那么使用dir /b "yourFilePath"
将8.3文件名转换成长文件名就是一件简单的事情。但是,这不会解析父文件夹的名称。
如果路径表示文件夹,情况更糟。无法仅使用 DIR 命令列出特定文件夹 - 它总是列出文件夹的内容而不是文件夹名称本身。
我尝试了多种策略来处理文件夹路径,其中 none 行得通:
- CD 或 PUSHD 到路径然后看提示-它保留短文件夹名称
- 带有 /L 和 /F 选项的 XCOPY - 它还保留短文件夹名称
- 参数或 FOR 变量修饰符
%~f1
或 %%~fA
- 保留短名称
- FORFILES - 似乎不支持短名称。
我能想出的唯一解决方案是使用 DIR 迭代转换路径中的每个文件夹,一次一个。这需要我使用 DIR /X /B /AD
列出父文件夹中的所有文件夹,包括它们的 8.3 名称,然后使用 FINDSTR 定位正确的文件夹短名称。我依赖于这样一个事实,即短文件名总是出现在 <DIR>
文本之后的完全相同的位置。找到正确的行后,我可以使用变量子字符串或 find/replace 操作,或使用 FOR /F 来解析长文件夹名称。我选择使用 FOR /F.
我遇到的另一个绊脚石是确定原始路径是代表文件还是文件夹。如果路径涉及符号 link 或连接点,则常用的附加反斜杠和使用 IF EXIST "yourPath\" echo FOLDER
的方法会错误地将文件报告为文件夹,这在公司网络环境中很常见。
我选择使用 IF EXIST "yourPath\*"
,位于 。
但也可以使用 FOR 变量 %%~aF
属性修饰符来查找 d
(目录)属性,在 , and .
处找到
这是一个完全可用的纯批处理解决方案
@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
时跳出循环到达根源。但是我没有打扰。
开发可以return变量结果的强大实用程序
考虑到 ^
、%
和 !
都是 file/folder 中的合法字符,有许多边缘情况可能会使稳健脚本的开发复杂化名字。
CALL 双引号 ^
字符。这个问题没有好的解决方案,除了使用变量而不是字符串文字通过引用传递值。如果输入路径仅使用短名称,则这不是问题。但如果路径混合使用短名称和长名称,则可能会出现问题。
在批处理参数中传递 %
文字可能很棘手。对于应该加倍的次数(如果有的话),可能会让人感到困惑。同样,在变量中通过引用传递值可能更容易。
CALLer 可以从 FOR 循环中调用实用程序。如果变量或参数包含 %
,则在实用程序的循环内扩展 %var%
或 %1
可能会导致无意的 FOR 变量扩展,因为 FOR 变量在范围内是全局的。该实用程序不得在 FOR 循环内扩展参数,并且如果使用延迟扩展,则只能在 FOR 循环内安全地扩展变量。
如果启用延迟扩展,包含 !
的 FOR 变量的扩展将被破坏。
呼叫环境可能已启用或禁用延迟扩展。将包含 !
和 ^
的值跨越 ENDLOCAL 屏障传递到延迟扩展环境需要将引用的 !
转义为 ^!
。此外,引号 ^
必须转义为 ^^
,但前提是该行包含 !
。当然,如果 CALLing 环境已禁用延迟扩展,则不应转义这些字符。
我开发了 JScript 和纯批处理解决方案的强大实用程序形式,它们考虑到了上述所有边缘情况。
默认情况下,实用程序期望路径为字符串文字,但如果使用 /V
选项,则接受包含路径的变量名。
默认情况下,实用程序只是将结果打印到标准输出。但是,如果您将 return 变量的名称作为额外参数传递,则结果可以 returned 在变量中。无论您的 CALLing 环境中是否启用了延迟扩展,都保证 returned 的值是正确的。
完整的文档嵌入在实用程序中,可以使用 /?
选项访问。
有一些模糊的限制:
- return变量名不能包含
!
或%
个字符
- 同样
/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
面对同样的问题,我在纯批处理中找到了更简单的解决方案。
它使用 ATTRIB 命令,显然你没有尝试过。
attrib.exe 输出长文件名,即使在获取短文件名作为参数时也是如此。
但不幸的是,它也不会扩展路径中的短名称。
就像在您的解决方案中一样,这需要在整个路径名上循环以获得完整的长路径名。
另一个困难来自于当路径名不存在时,attrib.exe 在 stdout 上输出以路径名结尾的错误消息(就像正常输出一样),而不是 return 错误级别。这可以通过过滤 attrib.exe 输出以删除驱动器名称前包含 - 的行来解决。
:GetLongPathname 扩展例程的测试程序:
@echo off
:# Convert a short or long pathname to a full long pathname
:GetLongPathname %1=PATHNAME %2=Output variable name
setlocal EnableDelayedExpansion
set "FULL_SHORT=%~fs1" &:# Make sure it really is short all the way through
set "FULL_SHORT=%FULL_SHORT:~3%" &:# Remove the drive and initial \
set "FULL_LONG=%~d1" &:# Begin with just the drive
if defined FULL_SHORT for %%x in ("!FULL_SHORT:\=" "!") do ( :# Loop on all short components
set "ATTRIB_OUTPUT=" &:# If the file does not exist, filter-out attrib.exe error message on stdout, with its - before the drive.
for /f "delims=" %%l in ('attrib "!FULL_LONG!\%%~x" 2^>NUL ^| findstr /v /c:" - %~d1"') do set "ATTRIB_OUTPUT=%%l"
if defined ATTRIB_OUTPUT ( :# Extract the long name from the attrib.exe output
for %%f in ("!ATTRIB_OUTPUT:*\=\!") do set "LONG_NAME=%%~nxf"
) else ( :# Use the short name (which does not exist)
set "LONG_NAME=%%~x"
)
set "FULL_LONG=!FULL_LONG!\!LONG_NAME!"
) else set "FULL_LONG=%~d1\"
endlocal & if not "%~2"=="" (set "%~2=%FULL_LONG%") else echo %FULL_LONG%
exit /b
示例输出:
C:\JFL\Temp>test "C:\Progra~1\Window~1"
C:\Program Files\Windows Defender
C:\JFL\Temp>test "\Progra~1\Not There\Not There Either"
C:\Program Files\Not There\Not There Either
C:\JFL\Temp>
PS。我希望 Microsoft 在 FOR 命令中添加一个 %~l 修饰符,与 %~s 修饰符完全相反。这本来可以避免这种杂技。在一个理想的世界...
我正在编写一个 Windows 批处理脚本,并且我有一个参数或变量包含一个使用短 8.3 名称的路径。该路径可能表示一个文件或文件夹。
如何将 8.3 路径转换为长名称路径?至少我希望能够简单地打印出完整的长名称路径。但理想情况下,我想安全地将长名称路径放入新变量中。
例如,给定路径 C:\PROGRA~1\AVASTS~1\avast\BROWSE~1.INI
,我想 return C:\Program Files\AVAST Software\avast\BrowserCleanup.ini
.
作为批处理爱好者,我最感兴趣的是仅使用本机 Windows 命令的纯批处理解决方案。但混合使用其他本机脚本工具(如 PowerShell 和 JScript)也是可以接受的。
注意: 我发布了我自己对这个问题的回答。我在网上搜索了一下,惊讶地发现很少有关于这个主题的资料。我制定了多种工作策略,并认为其他人可能会对我的发现感兴趣。
首先,我将演示如何转换批处理文件参数 %1
并将结果打印到屏幕。
PowerShell
最简单的解决方案是使用 PowerShell。我在 MSDN blog by Sergey Babkin
上找到了以下代码$long_path = (Get-Item -LiteralPath $path).FullName
将该代码放入批处理脚本并打印结果很简单:
@echo off
powershell "(Get-Item -LiteralPath '%~1').FullName"
但是,出于两个原因,我尽量避免在批处理中使用 PowerShell
- PowerShell 不是 XP 原生的
- PowerShell 的启动时间很长,因此批处理混合相对较慢
CSCRIPT(JScript 或 VBS)
我在 Computer Hope forum 找到了这个 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 newsgroup archive and an old vbscript forum 找到了类似的代码。
该代码仅支持文件路径,在批处理中嵌入 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);
纯批
令人惊讶的是,我的网络搜索未能找到纯批处理解决方案。所以我一个人。
如果你知道路径代表一个文件,那么使用dir /b "yourFilePath"
将8.3文件名转换成长文件名就是一件简单的事情。但是,这不会解析父文件夹的名称。
如果路径表示文件夹,情况更糟。无法仅使用 DIR 命令列出特定文件夹 - 它总是列出文件夹的内容而不是文件夹名称本身。
我尝试了多种策略来处理文件夹路径,其中 none 行得通:
- CD 或 PUSHD 到路径然后看提示-它保留短文件夹名称
- 带有 /L 和 /F 选项的 XCOPY - 它还保留短文件夹名称
- 参数或 FOR 变量修饰符
%~f1
或%%~fA
- 保留短名称 - FORFILES - 似乎不支持短名称。
我能想出的唯一解决方案是使用 DIR 迭代转换路径中的每个文件夹,一次一个。这需要我使用 DIR /X /B /AD
列出父文件夹中的所有文件夹,包括它们的 8.3 名称,然后使用 FINDSTR 定位正确的文件夹短名称。我依赖于这样一个事实,即短文件名总是出现在 <DIR>
文本之后的完全相同的位置。找到正确的行后,我可以使用变量子字符串或 find/replace 操作,或使用 FOR /F 来解析长文件夹名称。我选择使用 FOR /F.
我遇到的另一个绊脚石是确定原始路径是代表文件还是文件夹。如果路径涉及符号 link 或连接点,则常用的附加反斜杠和使用 IF EXIST "yourPath\" echo FOLDER
的方法会错误地将文件报告为文件夹,这在公司网络环境中很常见。
我选择使用 IF EXIST "yourPath\*"
,位于 。
但也可以使用 FOR 变量 %%~aF
属性修饰符来查找 d
(目录)属性,在 , and .
这是一个完全可用的纯批处理解决方案
@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
时跳出循环到达根源。但是我没有打扰。
开发可以return变量结果的强大实用程序
考虑到 ^
、%
和 !
都是 file/folder 中的合法字符,有许多边缘情况可能会使稳健脚本的开发复杂化名字。
CALL 双引号
^
字符。这个问题没有好的解决方案,除了使用变量而不是字符串文字通过引用传递值。如果输入路径仅使用短名称,则这不是问题。但如果路径混合使用短名称和长名称,则可能会出现问题。在批处理参数中传递
%
文字可能很棘手。对于应该加倍的次数(如果有的话),可能会让人感到困惑。同样,在变量中通过引用传递值可能更容易。CALLer 可以从 FOR 循环中调用实用程序。如果变量或参数包含
%
,则在实用程序的循环内扩展%var%
或%1
可能会导致无意的 FOR 变量扩展,因为 FOR 变量在范围内是全局的。该实用程序不得在 FOR 循环内扩展参数,并且如果使用延迟扩展,则只能在 FOR 循环内安全地扩展变量。如果启用延迟扩展,包含
!
的 FOR 变量的扩展将被破坏。呼叫环境可能已启用或禁用延迟扩展。将包含
!
和^
的值跨越 ENDLOCAL 屏障传递到延迟扩展环境需要将引用的!
转义为^!
。此外,引号^
必须转义为^^
,但前提是该行包含!
。当然,如果 CALLing 环境已禁用延迟扩展,则不应转义这些字符。
我开发了 JScript 和纯批处理解决方案的强大实用程序形式,它们考虑到了上述所有边缘情况。
默认情况下,实用程序期望路径为字符串文字,但如果使用 /V
选项,则接受包含路径的变量名。
默认情况下,实用程序只是将结果打印到标准输出。但是,如果您将 return 变量的名称作为额外参数传递,则结果可以 returned 在变量中。无论您的 CALLing 环境中是否启用了延迟扩展,都保证 returned 的值是正确的。
完整的文档嵌入在实用程序中,可以使用 /?
选项访问。
有一些模糊的限制:
- return变量名不能包含
!
或%
个字符 - 同样
/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
面对同样的问题,我在纯批处理中找到了更简单的解决方案。
它使用 ATTRIB 命令,显然你没有尝试过。
attrib.exe 输出长文件名,即使在获取短文件名作为参数时也是如此。
但不幸的是,它也不会扩展路径中的短名称。
就像在您的解决方案中一样,这需要在整个路径名上循环以获得完整的长路径名。
另一个困难来自于当路径名不存在时,attrib.exe 在 stdout 上输出以路径名结尾的错误消息(就像正常输出一样),而不是 return 错误级别。这可以通过过滤 attrib.exe 输出以删除驱动器名称前包含 - 的行来解决。
:GetLongPathname 扩展例程的测试程序:
@echo off
:# Convert a short or long pathname to a full long pathname
:GetLongPathname %1=PATHNAME %2=Output variable name
setlocal EnableDelayedExpansion
set "FULL_SHORT=%~fs1" &:# Make sure it really is short all the way through
set "FULL_SHORT=%FULL_SHORT:~3%" &:# Remove the drive and initial \
set "FULL_LONG=%~d1" &:# Begin with just the drive
if defined FULL_SHORT for %%x in ("!FULL_SHORT:\=" "!") do ( :# Loop on all short components
set "ATTRIB_OUTPUT=" &:# If the file does not exist, filter-out attrib.exe error message on stdout, with its - before the drive.
for /f "delims=" %%l in ('attrib "!FULL_LONG!\%%~x" 2^>NUL ^| findstr /v /c:" - %~d1"') do set "ATTRIB_OUTPUT=%%l"
if defined ATTRIB_OUTPUT ( :# Extract the long name from the attrib.exe output
for %%f in ("!ATTRIB_OUTPUT:*\=\!") do set "LONG_NAME=%%~nxf"
) else ( :# Use the short name (which does not exist)
set "LONG_NAME=%%~x"
)
set "FULL_LONG=!FULL_LONG!\!LONG_NAME!"
) else set "FULL_LONG=%~d1\"
endlocal & if not "%~2"=="" (set "%~2=%FULL_LONG%") else echo %FULL_LONG%
exit /b
示例输出:
C:\JFL\Temp>test "C:\Progra~1\Window~1"
C:\Program Files\Windows Defender
C:\JFL\Temp>test "\Progra~1\Not There\Not There Either"
C:\Program Files\Not There\Not There Either
C:\JFL\Temp>
PS。我希望 Microsoft 在 FOR 命令中添加一个 %~l 修饰符,与 %~s 修饰符完全相反。这本来可以避免这种杂技。在一个理想的世界...