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

时间:2013-09-06 09:55:34

标签: batch-file

我有一个很好的控制台文件管理器(Senh Liu的eXtreme),它将短路径/文件名作为变量传递给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"

10 个答案:

答案 0 :(得分:5)

简单的解决方案:使用PowerShell。

PS C:\> (Get-Item 'P:\MYPROG~1\SHELLS\ZBACKUP\REFSTO~1.BAL').FullName
P:\MyPrograms\SHELLS\zBackup\RefsToMyData.bal

您可以将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 available
  • Windows Vista和Server 2008:附带PowerShell v1(默认情况下未安装),PowerShell v2 available
  • Windows 7和Server 2008 R2:PowerShell v2预装,PowerShell v3 availablebatteries not included
  • 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 :(得分:5)

以下内容适用于任何有效路径,只要 为UNC路径即可。路径可以是绝对的或相对的。它可能使用短文件名或长名称(或混合名称)。路径可以指文件夹或文件。

如果结果是文件夹,则结果将以\结尾,如果文件是文件,则结果不会为\

:getLongPath例程需要一个inputPath变量名作为第一个参数,一个可选的返回变量名作为第二个参数。 inputPath变量应包含有效路径。如果未指定返回变量,则结果将被ECHOed到屏幕(用引号括起来)。如果指定了返回变量,则在变量中返回结果(不带引号)。

只有在返回变量时禁用延迟扩展时才应调用例程。如果在启用延迟扩展的情况下调用,则如果结果包含!字符,则结果将被破坏。

测试用例(仅适用于我的机器)位于脚本的顶部,实际例程位于底部。

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

=== OUTPUT ===

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 /批处理脚本。它应该提供与上面的纯批处理脚本相同的结果,但是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);

答案 2 :(得分:2)

这将返回完整的长路径名,但取决于:
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"

答案 3 :(得分: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. 

答案 4 :(得分: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 --

答案 5 :(得分: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功能中设置您的时间格式(删除适用的格式)。
如果路径或文件名中包含特殊字符,可能会失败,如!%=&^

答案 6 :(得分: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%"

尚未经过严格测试....

答案 7 :(得分:1)

以下是基于answer 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循环扩展到其完整路径之后)。每个元素都包含在""中,并以lcd开头(ftp命令的子命令用于更改本地工作目录)。对于构成驱动器的第一个路径元素,附加尾随\以引用其根目录。每个构建的路径字符串都写入临时文件。

接下来,临时文件被重定向到ftp命令,因此它按路径元素更改其本地工作目录路径元素。 ftp循环捕获for /F %%L的输出。实际上输出的最后一行是有意义的,因为它包含完整的长路径。但是,也会存储第一行,其中使用适用驱动器的根目录。这只需要轻松提取输出行的前缀,以便从包含完整路径的输出行中删除它(ftp命令在英语系统上输出类似Local directory now D:\.的东西,但我想要脚本与语言无关)。最后,从完整的长路径中删除所述前缀,并在控制台上返回结果。

答案 8 :(得分: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

答案 9 :(得分:0)

好吧,这是我前一段时间开始的脚本,它依赖于以下事实:使用dir /Bwildcard返回长文件名或目录名。这是一种递归方法,它遍历作为命令行参数给出的路径的目录层次结构并解析每个元素。请注意,由于使用了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