Monday 17 June 2013

Handling Paths with Spaces & Parenthesis in Batch Files

Whoever it was that decided the 32-bit Program Files folder on 64-bit windows was called “Program Files (x86)” was clearly having a bad day. Not only does it still have spaces in, which has already caused many a developer grief, but it now contains parenthesis too. This second addition can be bad for batch file programmers because that’s what you use to create scopes with IF statements and FOR loops.

Error Handling

For example this is a pattern that I use in batch file programming to handle errors at each step [1]:-

tool.exe --do-something %folder%
if errorlevel 1 (
  echo ERROR: Oops processing ‘%folder%’ 
  exit /b %errorlevel%
)

I always like to enclose my string parameters in quotes so that you can easily see when the parameter is empty; which is a common problem that can be hard to spot at first. As a rule of thumb I’ve tended to use single quotes mostly because they end up being pasted into a SQL query and they look marginally less noisy than double quotes. So, what happens when the error handler is invoked and %folder% is something like the aforementioned 32-bit programs folder? Well, you’d hope to see this:-

ERROR: Oops processing 'C:\Program Files (x86)'

Of course what you actually get is this:-

' was unexpected at this time.

If you’re writing a deployment script, then you probably have another bit of a path tacked on the end, which if it’s also got spaces, such as C:\Program Files (x86)\Chris Oldwood\My Tool, then you’re going to see something more freaky like this:-

\Chris was unexpected at this time.

The error message “was unexpected at this time.” is now fairly well ingrained as meaning I screwed up something in the quoting (or un-quoting as we’ll see later) somewhere in an IF block. The solution of course is to use double quotes, around paths when printing them out:-

echo ERROR: Oops processing "%folder%"

As always there is no substitute for testing your error handling, even in a batch file! What might make this situation worse is that the script will likely have been written and initially tested on a 32-bit desktop and then it fails some time later when it’s finally used on a 64-bit machine. Hopefully as the take-up of 64-bit Windows increases for desktops the feedback loop will diminish and it’ll show up much sooner (assuming you do the testing).

Quoted Arguments

The Windows command interpreter does not behave like “normal” application runtimes when it comes to handling script arguments. In a C/C++ or C# application the arguments you get passed via main() have any outer quotes removed. When you need to pass an argument, such as a path that has spaces, you generally need to put double quotes around it to have the application treat it as a single argument. But with CMD.EXE what you’ll get instead is the argument with the quotes still surrounding it.

For example, if you create a batch file with the single line @ECHO %1 and invoke it with “Hello World” (with the quotes) what you’ll see is:-

"Hello World"

This causes a problem if you use the following common pattern to detect if an argument has been provided or not:-

if "%1" == "" (
  call :usage
  exit /b 1
)

The use of double quotes around the argument %1 to ensure you don’t get a syntax error when the string is empty will instead give you a slightly cryptic error message when fed with a path like “C:\Program Files (x86)”:-

Files was unexpected at this time.

One alternative I’ve seen a few people use is to replace the double quotes with a different character, such as parenthesis, e.g.

if (%1) == ()  (
  call :usage
  exit /b 1
)

Personally I’d be tempted to use single quotes as it looks a little less weird these days what with HTML/XML allowing both “” & ‘’ for empty strings.

Stripping Quotes

Of course I’d argue that doing that actually solves the wrong problem, which is that you might receive a path with or without surrounding quotes. If you intend to manipulate the path then you’ll want to remove the quotes first. After you’ve manipulated it you’ll want to put the quotes back on again before passing it on to another script or process. The interpreter already has support for doing this with the “~” variable modifier [2].

As an aside I dislike using %1, %2, etc directly in my scripts and prefer to copy them to a named variable as soon as possible. This is the ideal time to strip any quotes too, e.g.

@echo off
setlocal

set installFolder=%~1

if "%installFolder%" == "" (
  call :usage
  exit /b 1
)

As long as you use SETLOCAL before creating any variables they won’t pollute the current context when the script terminates. From then on you always use the variable which of course makes the script easier to understand.

if not exist "%installFolder%" (
  echo ERROR: Invalid root folder "%installFolder%"
  exit /b 1
)

set installFolder=%installFolder%\Chris Oldwood\My Tool

By stripping any quotes that are present in (path) arguments you then make it easier on yourself by having a consistent policy within the script - only append quotes when the path itself needs evaluating, not the variable. So for the example above in the existence check I needed to add the quotes, but in the manipulation below it I didn’t. Of course logging the path in the case of an error is the exception to the rule :-).

[1] For this pattern to nest correctly you need to enable delayed expansion of variables (see HELP SETLOCAL) and use the “!” instead of “%” when evaluating variables.

[2] If you type HELP FOR you’ll get a help page for the FOR command, the end of which contains the various modifiers.

4 comments:

  1. I sometimes wonder if "Program Files" and "Program Files (x86)" were deliberately chosen as a point for developers to "fail early" on.

    After all when long filenames were new it was easy for devs to forget to allow for them - but having a path that almost every app had to touch sort of forced them to.

    "Program Files (x86)" takes it further - both in the parentheses as well as the path for apps now *not* reliably being "Program Files"!

    ReplyDelete
  2. My problem of copying and moving of ling path file solved by suing long path tool. I Suggest everyone to try this.

    ReplyDelete
  3. hello friends,its awesome information.use long path tool.

    ReplyDelete