Skip to content

Instantly share code, notes, and snippets.

@darcyparker
Created March 28, 2013 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darcyparker/5267063 to your computer and use it in GitHub Desktop.
Save darcyparker/5267063 to your computer and use it in GitHub Desktop.
Tools written in a scripting language like node.js, ruby, python, etc... often wrap the script in a batch file that uses %~dp0 to identify the root folder of the command being called. In most scenarios %~dp0 gives the expected result. But sometimes they give the wrong result and cause the tool/script to fail. For example, some applications (such…
@echo off
REM Using %~dp0 is common in batch files to identify the root folder of the batch file
REM But sometimes %~dp0 does not work as expected.
REM
REM This batch file illustrates where %~dp0 fails to return desired root folder where this
REM batch file is located.
REM
REM To setup the demonstration:
REM - save this batch file to a location such as d:\bin\GetThisCommandsDir
REM - Go to a different folder to execute the test cases
REM
REM Test Cases:
REM First example, where %~dp0 works:
REM d:\>GetThisCommandsDir
REM Command and Arguments: GetThisCommandsDir
REM Where is this command using %~dp0: d:\bin_win32\
REM Where is this command using :whereiscommand subroutine/method: d:\bin_win32\GetThisCommandsDir.bat
REM
REM Second example, where %~dp0 breaks:
REM d:\>"GetThisCommandsDir"
REM Command and Arguments: "GetThisCommandsDir"
REM Where is this command using %~dp0: d:\
REM Where is this command using :whereiscommand subroutine/method: d:\bin_win32\GetThisCommandsDir.bat
REM
REM Notice the difference in the way the command is specified in each test case.
REM - The first case does not wrap the command in double quotes
REM (which is the way most people would execute the command.)
REM - The second case wraps the command name in arguments and fails to give the correct location
REM using %~dp0
REM
REM Is the second test case that important?
REM - A command's name rarely (never in my experience) has spaces in it, so why would anyone
REM ever take the time to wrap it in double quotes if it is in the current dir or %PATH%
REM - The only real scenario someone would wrap the command in double quotes is when they need
REM to type out the full path because it is not in a folder specified by the %PATH%
REM
REM So, when would a command's name be wrapped in double quotes?
REM - It's a common practice for other programs to execute batch files and pass arguments
REM - These other programs need to 'escape' and/or wrap arguments being passed to the command
REM - For example, consider an argument with spaces in it. In windows (cmd.exe), the argument
REM needs to be wrapped in double quotes so that cmd.exe can tell it is a single argument and
REM not multiple arguments.
REM - The problem is that sometimes these programs do not construct the command line that uses the
REM batch file correctly.
REM - Many programs escape all arguments including the 0th argument (the command name itself) even
REM if it is not necessary.
REM - In unix-y shells like bash, this is not a problem. But as this program shows, it matters
REM on Windows with cmd.exe
REM
REM Example cases where I have seen this problem:
REM Generally programs that use batch files that wrap scripts for other scripting languages
REM - example many (not all) tools written in node.js, ruby, python, perl, etc...
REM - For example tools written for node.js are typically installed with npm, and a batch file
REM wrapper is used to execute the script in node.js.
REM - These batch files created by npm depend on %~dp0 !!!
REM - So other programs that call these tools need to verify they are not wrapping the 0th argument
REM in double quotes when using windows cmd.exe
REM - If these other programs cannot be updated, then the batch file needs to be updated to be
REM more robust. This script proposes a workaround.
REM
setlocal
set DEBUGLOG=GetThisCommandsDir.log
echo Command and Arguments: %0 %*
echo Command and Arguments: %0 %*>> %DEBUGLOG%
echo Where is this command using %%~dp0: %~dp0
echo Where is this command using %%~dp0: %~dp0>> %DEBUGLOG%
call :whereiscommand %~n0
echo Where is this command using :whereiscommand subroutine/method: %_whereiscommand%
echo Where is this command using :whereiscommand subroutine/method: %_whereiscommand%>> %DEBUGLOG%
goto :end
REM whereiscommand Finds a command in path environment variable
REM First param is command name to search for
:whereiscommand
set _whereiscommand=
for /F "usebackq delims==" %%z IN (`where %1`) DO call :__whereiscommand %%z
goto :eof
REM __whereiscommand is meant to be an Internal command that only sets %_whereiscommand% if it is not set already
REM This way the first found in `where %1` takes precedence
REM Note: a nested if statement with env var evaluations inside a for block doesn't work,
REM because variables in block are expanded before the loop is executed.
REM Workaround is to call a subroutine so changes to environment variables are seen for each iteration
:__whereiscommand
if /I "x%_whereiscommand%" EQU "x" (
set _whereiscommand=%1
)
goto :eof
endlocal
:end
@darcyparker
Copy link
Author

Mentioned in preservim/tagbar#133

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment