从短文件名获取完整路径和长文件名

13

我有一个不错的控制台文件管理器 (eXtreme by Senh Liu),它将短路径/文件名作为变量传递给 menu.bat 。

如何生成完整的文件夹名称 + 长文件名?

例如:

  • 输入变量=“P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL”
  • 目标变量=“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"


SET my_file=%~f2 这个怎么样? - Vlastimil Ovčáčík
亲爱的Vlastimil,很抱歉这个与%~2相同。我知道它会扩展文件名 - 如果您传递单个文件名。但是它无法处理“短路径\短文件名”。G - Gavin
2
如果只传递一个短文件名,它甚至无法工作。这是一个难题 - 很好的问题。 - dbenham
我非常谦虚地选择了dbenham的批处理函数作为我的首选答案。我在一个非常受控制的环境中工作,无法访问Power Shell。非常感谢大家。 - Gavin
10个回答

8
简单的解决方案:使用 PowerShell。
PS C:\> (Get-Item 'P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL').FullName
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal

你可以像这样在批处理文件中调用PowerShell:

你可以在批处理文件中嵌入一个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 SP2: PowerShell v2 可用
  • Windows Vista和Server 2008: 预装PowerShell v2(默认未安装),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%

不过,这确实需要一个额外的脚本文件。


+1,只要您可以访问PowerShell,这是一个非常优秀的解决方案。 - dbenham
同意,PowerShell 可供所有现代 Windows 使用,但有时你正在工作的计算机受到限制,无法安装它。如预期的那样,PowerShell 的解决方案非常简洁优雅。寻找批处理解决方案真是个大难题!请参见我的答案 - dbenham
我同意PowerShell可能由于管理限制、遗留系统或其他原因无法在所有主机上使用。但是,在这种情况下,我会使用VBScript代替。在我看来,用批处理完成这个任务不值得花费时间。尽管如此,我还是赞赏你的努力(+1)。 - Ansgar Wiechers
啊 - 非常好的VBS。Shell.Application对象是我找不到的缺失部分。该VBS可以转换为JScript,并轻松集成到单个JScript /批处理混合脚本中。 - dbenham

5
以下适用于任何有效路径,只要它不是UNC路径。路径可以是绝对路径或相对路径。它可以使用短文件名或长文件名(或混合使用)。路径可能指向文件夹或文件。
如果结果是一个文件夹,那么结果将以 \ 结尾;如果是文件,则不会在结尾添加 \ 。
:getLongPath 过程的第一个参数应为 inputPath 变量名称,第二个参数应为可选的返回变量名称。inputPath 变量应包含有效路径。如果未指定返回变量,则结果将在屏幕上呈现(带引号)。如果指定了返回变量,则结果将以变量形式返回(不带引号)。
如果正在返回变量,则仅当禁用延迟扩展时才应调用该过程。如果启用延迟扩展,则如果结果包含 ! 字符,则结果将损坏。
测试用例(仅限我的机器)在脚本顶部,实际过程在底部。
@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%"
)

=== 输出 ===

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之间的所有测试场景代码。然后,您可以简单地调用脚本,并将任何有效路径作为第一个参数传递。正确的长路径应作为结果打印。
我惊讶地发现,使用批处理难以解决这个问题。我认为使用JScript或VBS也不会更容易(实际上,Ansgar找到了一个不错的VBS解决方案)。但我喜欢Ansgar的简单PowerShell解决方案-要简单得多。
更新
我发现了一个比较特殊的情况,在该情况下,如果从FOR循环中调用上面的代码,并且路径恰好包含FOR变量,则上面的代码会失败。如果路径中有通配符,则它也无法正确报告错误,并且当路径包含感叹号“!”时启用延迟扩展时它也不能正常工作。
因此,我创建了下面的修改版本。我非常有信心它可以真正在所有情况下工作,除了UNC路径并且可能不能处理路径中的Unicode字符。我将其打包为易于调用的过程,并附带内置文档。它可以作为独立脚本保留,也可以并入更大的脚本中。
@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/batch脚本。它应该提供与上面纯批处理脚本相同的结果,但是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);

1
如果路径包含“%”,则应将%s变为四倍(在我的尝试中也是如此 :-) ) - npocmaka
@npocmaka - 很好的发现。我想我有一个解决方案,但这意味着需要重构例程以通过引用(变量名)而不是字符串常量传递路径。 - dbenham
@npocmaka - 我已经解决了 "%" 问题,并添加了包括 "%", "!", 和 "&" 的测试用例。 - dbenham
这次找不到打破它的方法 :) - npocmaka
@npocmaka - 谢谢。使用 : 作为 FOR 循环变量的微妙细节可以避免名称中的 % 可能引起的问题。我选择了 :,因为该字符永远不会出现在文件或文件夹名称中。 - dbenham
@npocmaka - 我实际上发现了一个很少见的情况,代码失败了。我已经添加了一个新的“防弹”版本。 - dbenham

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"

2

还有一种出乎意料的简单解决方案:

 echo lcd %some_path%|ftp

编辑后的示例:它并不是100%

d:\>echo lcd C:\Files\Download\MYMUSI~1\iTunes\ALBUMA~1 |ftp  
Local directory now C:\Files\Download\MYMUSI~1\iTunes\Album Artwork. 

2
还不太完美,npocmaka,但很有趣。请看我在你的答案中添加的示例。 - foxidrive
糟糕,看起来很有前途。 - npocmaka
1
是的。有趣的不同方法。 - jeb
这种方法的问题在于,只有目录路径的最后一个元素会被扩展为长名称;然而,我找到了一个解决方案(大约2.5年后 :-)) 来克服这个问题--请参见我的答案... - aschipfl

1
@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 --

1
我不确定这里出了什么问题,但它对我来说无法正常工作。看看我的答案-它使用了类似的策略,但对我有效 :) - dbenham
我已经修复了一个小错误,如果路径中的最后一项包含空格,则可能会出现该错误...但是在我的电脑上它可以正常工作。你是否收到了错误信息? - npocmaka
1
如果我提供以\结尾的文件夹路径,则会错误地将文件添加到末尾。如果我提供带有文件名中空格的文件路径,则会显示“找不到文件”,然后列出父路径的长名称(不包括文件)。 - dbenham

1

这是一个丑陋的批处理作业,我的代码不好看,但是很暴力 :-)

@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函数中设置您的时间格式(删除适用的格式)。
这可能会因路径或文件名中的特殊字符(如!%=&^)而失败。

1

尝试使用 WMICWin32_Directory 进行操作,可能比使用 cddir 更慢,但当前目录不会改变:

@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%"

尚未进行严格测试......

1
这是一个基于答案 by npocmaka的批处理脚本,使用ftp命令(以及其子命令lcd)。您可以看到,只有路径的最后一个元素被展开为长名称。我的想法是为路径的每个元素单独应用lcd子命令,这样我们将在最终输出中获得所有元素的完整名称。
此脚本仅适用于目录。它不适用于文件,也不适用于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 循环将其扩展为完整路径后)。每个元素都用 "" 包围,并以 lcdftp 命令的子命令,用于更改本地工作目录)为前缀。对于构成驱动器的第一个路径元素,会附加一个尾随的 \ 以引用其根目录。这些构建的路径字符串中的每一个都被写入临时文件。

接下来,临时文件被重定向到ftp命令,因此它会逐个更改其本地工作目录路径元素。 for /F %% L 循环捕获ftp的输出。实际上,只有输出的最后一行是感兴趣的,因为它包含完整的长路径。但是,第一行也被存储,其中使用了适用驱动器的根目录。这只是为了轻松提取输出行的前缀,以便从包含完整路径的输出行中移除它(ftp命令在英语系统上会输出类似于Local directory now D:\.的内容,但我希望该脚本独立于语言)。最后,所述前缀从完整的长路径中移除,并将结果返回给控制台。
这里有一种改进的方法,可以处理文件路径,通过子例程:LAST_ITEM单独处理最后一个路径元素,在这种情况下不依赖于ftp,而是依赖于for循环在给定通配符时扩展最后一个路径元素为长路径:
@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=\" & set "ITEM="
    if exist "%%~fA" (
        if exist "%%~fA\" (set "FLAG=") else set "FLAG=#"
        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="
                ) else set "ITEM="
                setlocal EnableDelayedExpansion
            )
        )
        set "PREFIX="
        for /F "delims=" %%L in ('^< "%TEMP_FILE%" 2^> nul 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!"
        )
        if not "!LONG_PATH:~-2!"==":\" set "LONG_PATH=!LONG_PATH!\"
        for /F "tokens=1,2 delims=|" %%S in ("!LONG_PATH!|!ITEM!") do (
            endlocal
            set "LONG_PATH=%%S" & set "ITEM=%%T"
            if defined FLAG call :LAST_ITEM ITEM LONG_PATH
            setlocal EnableDelayedExpansion
        )
        if defined FLAG (echo(!LONG_PATH!!ITEM!) else echo(!LONG_PATH!
    ) else setlocal EnableDelayedExpansion
)
endlocal

del /Q "%TEMP_FILE%"

endlocal
exit /B


:LAST_ITEM  var_last_item  var_long_path
setlocal EnableDelayedExpansion
for %%I in ("!%~2!!%~1!*") do (
    endlocal
    set "LONG=%%~nxI" & set "SHORT=%%~snxI"
    setlocal EnableDelayedExpansion
    if /I "!LONG!"=="!%~1!" (set "%~1=!LONG!"
    ) else if /I "!SHORT!"=="!%~1!" set "%~1=!LONG!"
)
for /F "delims=" %%T in ("!%~1!") do (
    endlocal
    set "%~1=%%T"
    setlocal EnableDelayedExpansion
)
endlocal
exit /B

0

好的,这是我一段时间前开始编写的脚本,它依赖于 dir /B 返回长文件或目录名称的事实,当使用 wildcard 时。这是一种递归方法,它遍历给定为命令行参数的路径的目录层次结构并解析每个元素。请注意,由于使用了 call,它在包含 % 和/或 ^ 的路径上存在问题:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

set ARGS=%*
set "COLL="

setlocal EnableDelayedExpansion
for %%A in (!ARGS!) do (
    endlocal
    set "ARG=%%~fA"
    if exist "%%~fA" (
        call :PROC_ITEM COLL "%%~fA" || set "COLL="
    )
    setlocal EnableDelayedExpansion
)
if defined COLL (echo(!COLL!) else exit /B 1
endlocal

endlocal
exit /B


:PROC_ITEM  rtn_built_path  val_source_path
setlocal DisableDelayedExpansion
set "FND="
if "%~pnx2"=="\" (
    set "COLL=%~d2"
) else (
    cd /D "%~dp2." & rem (this must be set before `for /F` in order for `%%~snxJ` to refer to it!)
    for /F "delims= eol=|" %%J in ('dir /B /A "%~f2?"') do (
        if /I "%%J"=="%~nx2" (
            set "FND=\%%J" & rem (this assignment should be executed for long names)
        ) else (
            if /I "%%~snxJ"=="%~nx2" set "FND=\%%J" & rem (and this for short ones)
        )
    )
    if defined FND (
        call :PROC_ITEM COLL "%~dp2."
    ) else (
        exit /B 1 & rem (this intercept exceptions and should usually not happen)
    )
)
endlocal & set "%~1=%COLL%%FND%"
exit /B

0

我的解决方案:

set shortname=P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL
for /F %f in ('dir /b /s %shortname%') do where /R %~dpf %~nf%~xf

如果你在批处理文件中使用它:

for /F %%f in ('dir /b /s %shortname%') do where /R %%~dpf %%~nf%%~xf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接