6

I have a which.bat on Windows 7,

@echo off
REM This bat searches a file in PATH list to see whether a file can be found.
REM If found, it shows the file's full path.
REM     which.bat gcc.exe
REM shows
REM     gcc.exe is found: D:\GMU\MinGW2\bin\gcc.exe
REM 
REM Note: Filename extension is significant in the search. E.g. If you run
REM     which.bat gcc
REM gcc.exe will not be matched.

IF "%1" == "" goto END

IF "%~$PATH:1" == "" (
      echo %1 is not found in any directories from PATH env-var.
    ) ELSE (
      echo %1 is found: %~$PATH:1
    )

:END

This bat works well until I find a strange behavior today.

There is a file O:\temp\pfiles (x86)\mystuff.txt, and PATH has content:

PATH=O:\temp\pfiles (x86);D:\CmdUtils

Running which mystuff.txt, I got the VERY STRANGE output:

\mystuff.txt was unexpected at this time.

enter image description here

After some poking around, I find that the (x86) in directory name causes the problem. To workaround, I have to add quotes to the echo, like this:

echo %1 is found: "%~$PATH:1"

The downside of such tweak is obvious: The quotes are printed to screen which is not always desired in the programmer's opinion.

Can anyone help explain this strange behavior?

I find this problem because in my real env, I have some paths like C:\Program Files (x86)\Common Files\NetSarang in PATH, which exhibit exactly the same symptom.

enter image description here

Toni Toni Chopper
  • 1,833
  • 2
  • 20
  • 29
Jimm Chen
  • 3,411
  • 3
  • 35
  • 59
  • Thank you for letting me know. ``where.exe`` is great. I have been using which.bat since Windows XP where there is no ``where.exe`` yet. – Jimm Chen May 13 '13 at 09:16
  • good info... but if you are searching for an 'executable' and provide no extension then does not work... use 'where.exe' then... or 'which' if ou are in linux... or just install Cygwin and use 'which'... works great !!! – ZEE May 25 '17 at 12:30

3 Answers3

8

MS Dos is pretty simple shell implementation, and as I have figured out that interpretation of one DOS command line goes in 2 phases:

  1. evaluation of variables in current line
  2. interpretation of evaluated command line

In this case your command line:

IF "%~$PATH:1" == "" (
      echo %1 is not found in any directories from PATH env-var.
    ) ELSE (
      echo %1 is found: %~$PATH:1
    )

would be interpreted as:

IF "O:\temp\pfiles (x86)\mystuff.txt" == "" (
      echo mystuff is not found in any directories from PATH env-var.
    ) ELSE (
      echo mystuff.txt is found: O:\temp\pfiles (x86)\mystuff.txt
    )

Now we can notice the problem in (x86), i.e. interpreter sees this somehow like this - first ) closes else statement:

) ELSE (
      echo mystuff.txt is found: O:\temp\pfiles (x86
)\mystuff.txt
)

Solution: put "" around all potentially problematic variables.

I usually put quotes around the whole echo command content, for example:

echo "%1 is found: %~$PATH:1"
Robert Lujo
  • 15,383
  • 5
  • 56
  • 73
4

As the problem is clear now (from Michael Burr and Robert Lujo), I try to show a solution.

You need quotes, but you don't want to display them.

With delayed expansion the closing parenthesis is harmless

setlocal EnableDelayedExpansion
IF "%~$PATH:1" == "" (
      echo %1 is not found in any directories from PATH env-var.
    ) ELSE (
      set "found=%~$PATH:1"      
      echo %1 is found: !found!
    )

Or just with a disappearing quote

IF "%~$PATH:1" == "" (
      echo %1 is not found in any directories from PATH env-var.
    ) ELSE (
      for %%^" in ("") do (
        echo %1 is found: %%~"%~$PATH:1
      )
    )
jeb
  • 78,592
  • 17
  • 171
  • 225
2

I can guess at an explanation (though not a helpful one): cmd.exe's parser isn't very clever - it gets confused by the parens in %~$PATH:1 - when it expands the variable and sees the ) character, it assumes that it's the closig paren for the ) ELSE ( line. (I think it doesn't do anything with the ( character in the expansion because those are only significant at the start of a command).

You can work around the problem by making sure that the expansing which can contain a ')' is not inside a (...) command grouping, or that it's quoted (as you found). Since you don't want the quotes, the other workaround might look like:

@echo off
REM This bat searches a file in PATH list to see whether a file can be found.
REM If found, it shows the file's full path.
REM     which.bat gcc.exe
REM shows
REM     gcc.exe is found: D:\GMU\MinGW2\bin\gcc.exe
REM 
REM Note: Filename extension is significant in the search. E.g. If you run
REM     which.bat gcc
REM gcc.exe will not be matched.

IF "%1" == "" goto END

IF "%~$PATH:1" == "" (
      echo %1 is not found in any directories from PATH env-var.
    ) ELSE (
      call :printfound %1
    )

goto END

:printfound
echo %1 is found: %~$PATH:1
goto :eof

:END

It's ugly, but that's the kind of thing you have to do with cmd.exe scripting.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760