CMD:当CALL引用批处理文件的名称时,%~d0失败

Chr*_*sJJ 5 windows-xp cmd batch-file

为什么%~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:\>
Run Code Online (Sandbox Code Playgroud)

编辑以下来自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:\>
Run Code Online (Sandbox Code Playgroud)

MC *_* ND 7

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

删除原始答案 - 我错了.

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

经过测试,似乎更多的是文件名处理方式的bug /功能以及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
Run Code Online (Sandbox Code Playgroud)

放在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
---------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

结果:一切都好.

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
---------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

结果:如果直接从cmd的主执行行获取,则无法获得正确的%~d0值.子程序调用与预期一样工作.

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

为什么?不知道.但跟踪CMD执行讨论procmon,在故障情况下,如果当CMD.EXE尝试检索该文件的信息,一个QueryDirectory API调用时对C:\Users\test.cmd其中的答案为NO SUCH FILE,但CMD忽略它,并继续执行,显示了错误的价值观.

所以,没有答案,对不起.但我不得不"记录"这个.房间里有些大师吗?

更新:更多信息在这里


dbe*_*ham 6

迷人的发现.

在DosTips的某个地方,有一个jeb帖子描述了主要脚本与CALLed子例程中的工作方式%0和变体%~f0:%0从子例程中提供子例程标签,但添加了一个%~f0与运行脚本路径一起工作的修饰符,即使SHIFT具有被使用过.

但是我不记得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
Run Code Online (Sandbox Code Playgroud)

以下是使用testas作为命令的结果,以及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>
Run Code Online (Sandbox Code Playgroud)

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

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>
Run Code Online (Sandbox Code Playgroud)

我从XP和Win 7获得相同的结果.

在子程序中,一切都按预期工作.

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

因此,在脚本中的任何位置获取执行脚本的路径信息的唯一可靠方法是使用子例程.如果当前的驱动器和/或目录已经改变,因为启动,或者如果已有的SHIFT值将是从主水平不正确%0.非常离奇.充其量,我会将其归类为设计缺陷.最糟糕的是,一个彻头彻尾的bug.


更新

其实,要解决你的代码最简单的方法就是使用PUSHD POPD和,但我不认为那是你真正需要的:-)

pushd R:
popd
Run Code Online (Sandbox Code Playgroud)

我曾经认为你可以%~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
Run Code Online (Sandbox Code Playgroud)

当我给脚本一个疯狂的名字时,这里有一些测试结果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>
Run Code Online (Sandbox Code Playgroud)

我还与名称测试test^it.bat,test!.bat以及test.bat和所有工作正常(未显示).


jeb*_*jeb 5

像dbenham:很有魅力!

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

%0在主批处理上下文中不会删除引号.
但他们都被调用子程序所剥夺.当使用两个以上的引号时,可以实现这一点,当%0展开时,每边只有一个引号将被删除.

ParamTest.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
Run Code Online (Sandbox Code Playgroud)

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

Main %0: """PARAM"test.BAT, D:\"""PARAM"test.BAT
Main %1: "paramTEST.bAt", D:\"paramTEST.bAt"
Func %0: :func, C:\temp\ParamTest.bat
Func %1: "paramTEST.bAt", D:\"paramTEST.bAt"

并且%0似乎保存了相关目录,因为您获得了不同的结果%~f0,%~f1甚至当内容看起来相同时.
但也许路径只是前缀%0.

输出: PARAMtest.BAT paramTEST.bAt

Main %0: PARAMtest.BAT, C:\temp\ParamTest.bat
Main %1: paramTEST.bAt, D:\paramTEST.bAt
Func %0: :func, C:\temp\ParamTest.bat
Func %1: paramTEST.bAt, D:\paramTEST.bAt