CMD: failure of %~d0 when CALL quotes the name of the batch file

后端 未结 4 2053
遥遥无期
遥遥无期 2021-01-03 02:43

Why the following failure of %~d0 to return the batch file\'s drive letter S: when CALL quotes the name of the batch file?

S:\\!DJ DAP>type test.b         


        
相关标签:
4条回答
  • 2021-01-03 03:07

    EDIT - npocmaka, you are right. Strange.

    Original answer removed - I was wrong.

    But problem is not the call command. The problem are the quotes and cmd.

    After testing, it seems more a bug/feature in how filenames are processed and how cmd are handling some errors in api calls.

    With the following batch file (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
    

    placed in d:\temp\testCMD and current directory in drive c: is C:\Users, the results on execution are:

    1.- Calling without quotes from cmd directory: 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
    ---------------------------------------------------------
    

    Result: everything ok.

    2.- Calling with quotes from cmd directory "test.cmd" (no, no need for call command)

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

    Result: Failure to get correct value of %~d0 ONLY if directly get from main execution line of cmd. The same with subroutine call works as expected.

    All scenarios tested without quotes work without failure. With quotes, if the calling line include the drive (ej: "d:.\test.cmd") all values are correctly retrieved. If not drive included in batch call, (ej: "test.cmd" with batch directory in path, or "\temp\testCMD\test.cmd" from root of D:), incorrect values retrieved, but only from main line of execution in batch file. Subroutines always get correct values.

    Why? No idea. But when tracing cmd execution with procmon, in failure cases, when cmd.exe try to retrieve the information for the file, a QueryDirectory API call is made for C:\Users\test.cmd which is answered with NO SUCH FILE, but cmd ignores it and continues execution, showing the wrong values.

    So, no answer, sorry. But i had to "document" this. Some guru in the room?

    update: More information here

    0 讨论(0)
  • 2021-01-03 03:08

    "%~dpi" also fails when you are listing files but the working directory is a different folder, or drive. It shows the working directory and not the path of the files.

    I think the solution here might be to get the %~d0 before changing drive.

    0 讨论(0)
  • 2021-01-03 03:11

    Like dbenham: Fascinating!

    I suppose it's a feature of cmd.exe.

    Quotes aren't stripped from %0 in the main batch context.
    But they are all stripped by a call to a subroutine. This can be realized when more than two quotes are used, only one quote from each side will be removed when the %0 is expanded.

    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
    

    Output for: """"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"
    

    And %0 seems to save the related directory, as you get different results for %~f0 and %~f1 even when the content seems to be equal.
    But perhaps the path is just prefixed to %0.

    Output for: 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
    
    0 讨论(0)
  • 2021-01-03 03:20

    Fascinating discovery.

    Somewhere on DosTips there is a jeb post describing how %0 and variants like %~f0 work from the main script vs. within a CALLed subroutine: %0 from within a subroutine gives the subroutine label, but adding a modifier like %~f0 works with the running scripts path, even if SHIFT has been used.

    But I don't remember jeb's post describing a difference between a quoted vs. unquoted %0 from the main routine (no subroutine).

    I extended MC ND's tests below. My script is 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
    

    Here is the result using test as the command, and test as the first argument:

    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>
    

    And here are the results using quoted values:

    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>
    

    I get identical results from XP and Win 7.

    Everything works as expected when within a subroutine.

    But I cannot explain the behavior from the main level. Before the SHIFT, the unquoted command works with the true path to the executing script. But the quoted command works with the string from the command line instead and fills in missing values using the current working drive and directory. Yet after the SHIFT, both the unquoted and quoted values behave the same, it simply works with the actual passed parameter and current working drive and directory.

    So the only reliable way to get the path info for the executing script at any point within a script is to use a subroutine. The values will be incorrect from the main level if the current drive and/or directory have changed since launch, or if there has been a SHIFT of %0. Very bizarre. At best, I would classify this as a design flaw. At worst, a downright bug.


    Update

    Actually, the easiest way to fix your code is to simply use PUSHD and POPD, but I don't think that is what you are really looking for :-)

    pushd R:
    popd
    

    I used to think you could solve the %~0 problem by capturing the value in an environment variable prior to changing your working directory. But that can fail if your script is called using enclosing quotes, but without the .bat extension. It can work if you are only looking for the drive, but other things like path, base name, extension, size, and timestamp can fail.

    It turns out the only way to positively get the correct value is to use a CALLed subroutine.

    Note that there is another potential problem that can crop up under obscure circumstances. Both ^ and ! can be used in file and folder names. Names with those values can be corrupted if you capture them while delayed expansion is enabled. Delayed expansion is normally disabled when a batch file starts, but it is possible that it could launch with delayed expansion enabled. You could explicitly disable delayed expansion before you capture the value(s), but there is another option using a function call.

    The script below defines a :currentScript function that can be used under any circumstances, and it is guaranteed to give the correct value. You pass in the name of a variable to receive the value, and optionally pass in a string of modifiers (without the tilde). The default option is F (full path, equivalent to DPNX)

    The :currentScript function is at the bottom. The rest of the script is a test harness to demonstrate and test the functionality. It contrasts the results using the function vs. using %0 directly.

    @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
    

    Here are some test results when I give the script a crazy name of test^it!.bat. I tested with both unquoted and quoted values. You can see that the :CurrentScript function always works, but a direct expansion of %~tzf0 often fails.

    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>
    

    I also tested with names of test^it.bat, test!.bat, and test.bat, and all worked properly (not shown).

    0 讨论(0)
提交回复
热议问题