Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Fascinating discovery.</p> <p>Somewhere on DosTips there is a jeb post describing how <code>%0</code> and variants like <code>%~f0</code> work from the main script vs. within a CALLed subroutine: <code>%0</code> from within a subroutine gives the subroutine label, but adding a modifier like <code>%~f0</code> works with the running scripts path, even if SHIFT has been used.</p> <p>But I don't remember jeb's post describing a difference between a quoted vs. unquoted <code>%0</code> from the main routine (no subroutine).</p> <p>I extended MC ND's tests below. My script is <code>c:\test\test.bat</code>.</p> <pre><code>@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" ( &lt;nul set /p "=From subroutine " ) else ( &lt;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 </code></pre> <p>Here is the result using <code>test</code> as the command, and <code>test</code> as the first argument:</p> <pre><code>C:\test&gt;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&gt; </code></pre> <p>And here are the results using quoted values:</p> <pre><code>C:\test&gt;"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&gt; </code></pre> <p>I get identical results from XP and Win 7.</p> <p>Everything works as expected when within a subroutine.</p> <p>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.</p> <p>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 <code>%0</code>. Very bizarre. At best, I would classify this as a design flaw. At worst, a downright bug.</p> <hr> <p><strong>Update</strong></p> <p>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 :-)</p> <pre><code>pushd R: popd </code></pre> <p>I used to think you could solve the <code>%~0</code> 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 <code>.bat</code> 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.</p> <p>It turns out the only way to positively get the correct value is to use a CALLed subroutine.</p> <p>Note that there is another potential problem that can crop up under obscure circumstances. Both <code>^</code> and <code>!</code> 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.</p> <p>The script below defines a <code>:currentScript</code> 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 <code>F</code> (full path, equivalent to <code>DPNX</code>)</p> <p>The <code>:currentScript</code> 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 <code>%0</code> directly.</p> <pre><code>@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 &amp; endlocal &amp; set "%~1=%rtn%" ! exit /b </code></pre> <p>Here are some test results when I give the script a crazy name of <code>test^it!.bat</code>. I tested with both unquoted and quoted values. You can see that the <code>:CurrentScript</code> function always works, but a direct expansion of <code>%~tzf0</code> often fails.</p> <pre><code>C:\test&gt;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&gt;"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&gt;"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&gt; </code></pre> <p>I also tested with names of <code>test^it.bat</code>, <code>test!.bat</code>, and <code>test.bat</code>, and all worked properly (not shown).</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload