5

为什么 %~d0 无法返回批处理文件的驱动器号 S: 当 CALL 引用批处理文件的名称时?

S:\!DJ DAP>type test.bat
R:
%~d0

S:\!DJ DAP>call test.bat

S:\!DJ DAP>R:

R:\>S:

S:\!DJ DAP>call "test.bat"

S:\!DJ DAP>R:

R:\>R:

R:\>

编辑以下来自 Jerry 和 MC 的回复:这是一个显示相同的非 CALL 示例:

R:\>s:

S:\!DJ DAP>type test.bat
R:
%~d0

S:\!DJ DAP>test.bat

S:\!DJ DAP>R:

R:\>S:

S:\!DJ DAP>"test.bat"

S:\!DJ DAP>R:

R:\>R:

R:\>
4

4 回答 4

7

编辑 - npocmaka,你是对的。奇怪的。

原始答案已删除-我错了。

但问题不在于call命令。问题是引号和cmd。

经过测试,文件名的处理方式以及 cmd 如何处理 api 调用中的一些错误似乎更像是一个错误/功能。

使用以下批处理文件(test.cmd)

@echo off
    setlocal enableextensions

    echo Calling subroutine from drive d:
    call :getInfo
    echo.

    c:
    echo Calling subroutine from drive c:
    call :getInfo
    echo.

    echo Getting data directly without subroutine

:getInfo
    echo ---------------------------------------------------------
    echo cd    : %cd%
    echo d0    : %~d0
    echo dp0   : %~dp0
    echo f0    : %~f0
    echo ---------------------------------------------------------
    echo.
    goto :EOF

放在 d:\temp\testCMD 和驱动器 c: 中的当前目录是 C:\Users,执行结果是:

1.-从 cmd 目录调用不带引号:test.cmd

Calling subroutine from drive d:
---------------------------------------------------------
cd    : D:\temp\testCMD
d0    : D:
dp0   : D:\temp\testCMD\
f0    : D:\temp\testCMD\test.cmd
---------------------------------------------------------


Calling subroutine from drive c:
---------------------------------------------------------
cd    : C:\Users
d0    : D:
dp0   : D:\temp\testCMD\
f0    : D:\temp\testCMD\test.cmd
---------------------------------------------------------


Getting data directly without subroutine
---------------------------------------------------------
cd    : C:\Users
d0    : D:
dp0   : D:\temp\testCMD\
f0    : D:\temp\testCMD\test.cmd
---------------------------------------------------------

结果:一切正常。

2.-使用cmd 目录中的引号调用"test.cmd"(不,不需要call命令)

Calling subroutine from drive d:
---------------------------------------------------------
cd    : D:\temp\testCMD
d0    : D:
dp0   : D:\temp\testCMD\
f0    : D:\temp\testCMD\test.cmd
---------------------------------------------------------


Calling subroutine from drive c:
---------------------------------------------------------
cd    : C:\Users
d0    : D:
dp0   : D:\temp\testCMD\
f0    : D:\temp\testCMD\test.cmd
---------------------------------------------------------


Getting data directly without subroutine
---------------------------------------------------------
cd    : C:\Users
d0    : C:
dp0   : C:\Users\
f0    : C:\Users\test.cmd
---------------------------------------------------------

结果:仅当直接从 cmd 的主执行行获取时,才能获得正确的 %~d0 值。与子程序调用相同,按预期工作。

没有引号测试的所有场景都可以正常工作。使用引号,如果调用行包含驱动器 (ej: "d:.\test.cmd"),则所有值都会被正确检索。如果批处理调用中不包含驱动器(ej:"test.cmd"在路径中使用批处理目录,或"\temp\testCMD\test.cmd"从 D: 的根目录),则检索到的值不正确,但仅来自批处理文件中的执行主线。子程序总是得到正确的值。

为什么?不知道。但是,当使用 procmon 跟踪 cmd 执行时,在失败的情况下,当 cmd.exe 尝试检索文件的信息时,会进行 QueryDirectory API 调用,并用 来C:\Users\test.cmd回答NO SUCH FILE但 cmd 会忽略它并继续执行,显示错误的值

所以,没有答案,对不起。但我必须“记录”这一点。房间里有大师吗?

更新:更多信息在这里

于 2013-11-05T07:19:38.413 回答
6

迷人的发现。

DosTips 上的某处有一篇 jeb 帖子描述了主脚本与 CALLed 子例程中的工作方式%0和变体:从子例程中给出子例程标签,但添加一个修饰符,如与运行脚本路径一起使用,即使 SHIFT 有被使用。%~f0%0%~f0

但我不记得 jeb 的帖子描述了%0主例程(无子例程)中引用与未引用之间的区别。

我在下面扩展了 MC ND 的测试。我的脚本是c:\test\test.bat.

@echo off
setlocal
echo(
echo Upon entry:
echo ---------------------------------------------------------
echo   %%shift%% : %shift%
echo      %%cd%% : %cd%
echo        %%0 : %0
echo        %%1 : %1
echo      %%~d0 : %~d0
echo      %%~p0 : %~p0
echo      %%~n0 : %~n0
echo      %%~x0 : %~x0
echo      %%~f0 : %~f0
call echo call %%%%~f0 : %%~f0
echo ---------------------------------------------------------

set "shift=FALSE"
d:
echo(
echo Current directory set to D:\

:top
call :getInfo

:getInfo
echo(
if "%0" equ ":getInfo" (
  <nul set /p "=From subroutine "
) else (
  <nul set /p "=From main "
)
if "%shift%" equ "TRUE" (echo after SHIFT) else (echo before SHIFT)
echo ---------------------------------------------------------
echo   %%shift%% : %shift%
echo      %%cd%% : %cd%
echo        %%0 : %0
echo        %%1 : %1
echo      %%~d0 : %~d0
echo      %%~p0 : %~p0
echo      %%~n0 : %~n0
echo      %%~x0 : %~x0
echo      %%~f0 : %~f0
call echo call %%%%~f0 : %%~f0
echo ---------------------------------------------------------
if "%0" equ ":getInfo" exit /b
if "%shift%" equ "TRUE" exit /b
shift
set "shift=TRUE"
goto top

这是使用test命令和test第一个参数的结果:

C:\test>test test

Upon entry:
---------------------------------------------------------
  %shift% :
     %cd% : C:\test
       %0 : test
       %1 : test
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

Current directory set to D:\

From subroutine before SHIFT
---------------------------------------------------------
  %shift% : FALSE
     %cd% : D:\
       %0 : :getInfo
       %1 :
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

From main before SHIFT
---------------------------------------------------------
  %shift% : FALSE
     %cd% : D:\
       %0 : test
       %1 : test
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

From subroutine after SHIFT
---------------------------------------------------------
  %shift% : TRUE
     %cd% : D:\
       %0 : :getInfo
       %1 :
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

From main after SHIFT
---------------------------------------------------------
  %shift% : TRUE
     %cd% : D:\
       %0 : test
       %1 :
     %~d0 : D:
     %~p0 : \
     %~n0 : test
     %~x0 :
     %~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------

C:\test>

以下是使用引用值的结果:

C:\test>"test" "test"

Upon entry:
---------------------------------------------------------
  %shift% :
     %cd% : C:\test
       %0 : "test"
       %1 : "test"
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 :
     %~f0 : C:\test\test
call %~f0 : C:\test\test
---------------------------------------------------------

Current directory set to D:\

From subroutine before SHIFT
---------------------------------------------------------
  %shift% : FALSE
     %cd% : D:\
       %0 : :getInfo
       %1 :
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

From main before SHIFT
---------------------------------------------------------
  %shift% : FALSE
     %cd% : D:\
       %0 : "test"
       %1 : "test"
     %~d0 : D:
     %~p0 : \
     %~n0 : test
     %~x0 :
     %~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------

From subroutine after SHIFT
---------------------------------------------------------
  %shift% : TRUE
     %cd% : D:\
       %0 : :getInfo
       %1 :
     %~d0 : C:
     %~p0 : \test\
     %~n0 : test
     %~x0 : .bat
     %~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------

From main after SHIFT
---------------------------------------------------------
  %shift% : TRUE
     %cd% : D:\
       %0 : "test"
       %1 :
     %~d0 : D:
     %~p0 : \
     %~n0 : test
     %~x0 :
     %~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------

C:\test>

我从 XP 和 Win 7 得到相同的结果。

在子例程中,一切都按预期工作。

但我无法从主要层面解释这种行为。在 SHIFT 之前,未加引号的命令使用执行脚本的真实路径。但是引用的命令使用命令行中的字符串代替,并使用当前工作驱动器和目录填充缺失值。然而,在 SHIFT 之后,未引用和引用的值的行为相同,它只是与实际传递的参数和当前工作驱动器和目录一起工作。

因此,在脚本中的任何位置获取执行脚本的路径信息的唯一可靠方法是使用子例程。如果当前驱动器和/或目录自启动以来已更改,或者如果有%0. 很奇怪。充其量,我会将其归类为设计缺陷。在最坏的情况下,一个彻头彻尾的错误。


更新

实际上,修复代码的最简单方法是简单地使用 PUSHD 和 POPD,但我认为这不是您真正想要的 :-)

pushd R:
popd

我曾经认为您可以%~0通过在更改工作目录之前捕获环境变量中的值来解决问题。但是,如果使用封闭引号调用脚本但没有.bat扩展名,则可能会失败。如果您只查找驱动器,它可以工作,但路径、基本名称、扩展名、大小和时间戳等其他内容可能会失败。

事实证明,获得正确值的唯一方法是使用 CALLed 子例程。

请注意,在不明确的情况下可能会出现另一个潜在问题。两者^!都可用于文件和文件夹名称。如果在启用延迟扩展的情况下捕获具有这些值的名称,则它们可能会损坏。批处理文件启动时通常会禁用延迟扩展,但它可能会在启用延迟扩展的情况下启动。您可以在捕获值之前显式禁用延迟扩展,但是还有另一个使用函数调用的选项。

下面的脚本定义了一个:currentScript可以在任何情况下使用的函数,并且保证给出正确的值。您传入一个变量的名称来接收该值,并可选择传入一个修饰符字符串(不带波浪号)。默认选项是F(完整路径,相当于DPNX

功能在:currentScript底部。脚本的其余部分是用于演示和测试功能的测试工具。它对比了使用函数与%0直接使用的结果。

@echo off
setlocal disableDelayedExpansion
set arg0=%0
if "%arg0:~0,1%%arg0:~0,1%" equ """" (set "arg0=Quoted") else set "arg0=Unquoted"

call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"

setlocal enableDelayedExpansion
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"

endlocal
d:
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"

setlocal enableDelayedExpansion
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"

exit /b

:header
set "rtn="
setlocal
echo(
echo(
if "!" equ "" (set "delayed=ON") else set "delayed=OFF"
if "%cd%\" equ "%~dp0" (set "cwd=Original") else set "cwd=Modified"
echo %arg0%: %cwd% working directory, Delayed expansion = %delayed%
echo ---------------------------------------------------------------------------
exit /b


:currentScript  rtnVar  [options]
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "options=%~2"
if not defined options set "options=f"
call set "rtn=%%~%options%0"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
endlocal & endlocal & set "%~1=%rtn%" !
exit /b

当我给脚本起一个疯狂的名字时,这里有一些测试结果test^it!.bat。我用不带引号和带引号的值进行了测试。可以看到:CurrentScript函数总是有效,但是直接展开%~tzf0经常会失败。

C:\test>TEST^^IT!.BAT


Unquoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Unquoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Unquoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Unquoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"

C:\test>"TEST^IT!.BAT"


Quoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Quoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "D:\TEST^IT!.BAT"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "D:\TESTIT.BAT"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"

C:\test>"TEST^IT!"


Quoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "C:\test\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "C:\test\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "D:\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "D:\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"

C:\test>

我还使用 、 和 的名称进行了测试,test^it.bat并且都正常工作(未显示)。test!.battest.bat

于 2013-11-05T13:42:16.293 回答
5

像德本汉姆:迷人!

我想这是 cmd.exe 的一个功能。

%0在主批处理上下文中不会删除引号。
但是它们都被一个子程序的调用剥离了。这可以在使用两个以上的引号时实现,当展开时,每侧只会删除一个引号%0

参数测试.bat

@echo off
cls
setlocal
d:
echo Main %%0: %~0, %~f0
echo Main %%1: %~1, %~f1
call :func %1
exit /b

:func
echo Func %%0: %~0, %~f0
echo Func %%1: %~1, %~f1
exit /b

输出:""""PARAM"test.BAT" ""paramTEST.bAt""

主 %0: """PARAM"test.BAT, D:\"""PARAM"test.BAT
主 %1:“paramTEST.bAt”,D:\“paramTEST.bAt”
函数 %0: :func, C:\temp\ParamTest.bat
函数 %1: "paramTEST.bAt", D:\"paramTEST.bAt"

并且%0似乎保存了相关目录,因为即使内容似乎相同%~f0,您也会得到不同的结果。 但也许路径只是以.%~f1
%0

输出:PARAMtest.BAT paramTEST.bAt

主要 %0: PARAMtest.BAT, C:\temp\ParamTest.bat
主要 %1: paramTEST.bAt, D:\paramTEST.bAt
函数 %0: :func, C:\temp\ParamTest.bat
功能 %1: paramTEST.bAt, D:\paramTEST.bAt
于 2013-11-05T19:45:44.203 回答
1

"%~dpi"当您列出文件但工作目录是不同的文件夹或驱动器时也会失败。它显示工作目录而不是文件的路径。

我认为这里的解决方案可能是%~d0在更换驱动器之前获得。

于 2013-11-05T12:29:06.217 回答