Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows .bat file, %~$PATH:1 quotes issue

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

like image 545
Jimm Chen Avatar asked May 13 '13 09:05

Jimm Chen


People also ask

Why does my batch file only executes the first line?

The reason for this is that if you just start one bat file from another, only one of them will exit, while if using CALL, when the called bat file exits, the calling bat file will continue executing.

What does 1 mean in a batch file?

When used in a command line, script, or batch file, %1 is used to represent a variable or matched string. For example, in a Microsoft batch file, %1 can print what is entered after the batch file name.

What is %% f in batch script?

By default, /F breaks up the command output at each blank space, and any blank lines are skipped.


3 Answers

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"
like image 198
Robert Lujo Avatar answered Nov 03 '22 03:11

Robert Lujo


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
      )
    )
like image 39
jeb Avatar answered Nov 03 '22 03:11

jeb


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.

like image 24
Michael Burr Avatar answered Nov 03 '22 02:11

Michael Burr