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.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:\>
EDIT following responses from Jerry and MC: Here's a non-CALL example showing the same:
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:\>
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)
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
Result: everything ok.
2.- Calling with quotes from cmd directory
"test.cmd"
(no, no need forcall
command)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 withNO 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
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
Output for:
""""PARAM"test.BAT" ""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
"%~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.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
.Here is the result using
test
as the command, andtest
as the first argument:And here are the results using quoted values:
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 :-)
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 isF
(full path, equivalent toDPNX
)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.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.I also tested with names of
test^it.bat
,test!.bat
, andtest.bat
, and all worked properly (not shown).