80

I have a simple bat script that copies files from an known directory to a directory given by the user. How can I pass the path (it might contain spaces) to my script and use it with the xcopy command?


In my code i have the following

:READ_PWA_PATH
    if "%1" == "" ( 
        rem Set default path
        set PWA_PATH="C:\Program Files\PWA"
        rem
        echo You have not specified your PWA url.
        echo Default will be assumed: C:\Program Files\PWA. 
        choice /C:YN /M:"Do you wish to continue [Y] or cancel the script [N]?"
            IF ERRORLEVEL ==2 GOTO CANCEL
            IF ERRORLEVEL ==1 GOTO READ_WSS_SERVER_EXTENSIONS_PATH
        GOTO END
    ) else (
        set PWA_PATH=%1
    )

If I simply call the script I get the following error:

C:\Projects\Setup>install.cmd "C:\program files (x86)"

-----------------
SETUP SCRIPT
-----------------

files was unexpected at this time.
C:\Projects\Setup>
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
kjv
  • 11,047
  • 34
  • 101
  • 140

9 Answers9

146

Use "%~1". %~1 alone removes surrounding quotes. However since you can't know whether the input parameter %1 has quotes or not, you should ensure by "%~1" that they are added for sure. This is especially helpful when concatenating variables, e.g. convert.exe "%~1.input" "%~1.output"

DeanOC
  • 7,142
  • 6
  • 42
  • 56
zikaS
  • 1,461
  • 2
  • 9
  • 2
19

Interesting one. I love collecting quotes about quotes handling in cmd/command.

Your particular scripts gets fixed by using %1 instead of "%1" !!!

By adding an 'echo on' ( or getting rid of an echo off ), you could have easily found that out.

vrdhn
  • 4,024
  • 3
  • 31
  • 39
  • 12
    Despite this answer be marked as best by the question owner, it fixes one problem, adding other. If you just remove quotes from %1, your code may present errors when quotes are not provided (or when no parameters are given at all). The safest solution is to use %~1, as stated by @zikaS' answer. – Diego Queiroz Apr 02 '18 at 18:01
  • 2
    How to escape all arguments with %*? – mvorisek Jul 20 '19 at 18:20
17

I think the OP's problem was that he wants to do BOTH of the following:

  • Pass a parameter which may contain spaces
  • Test whether the parameter is missing

As several posters have mentioned, to pass a parameter containing spaces, you must surround the actual parameter value with double quotes.

To test whether a parameter is missing, the method I always learned was:

if "%1" == ""

However, if the actual parameter is quoted (as it must be if the value contains spaces), this becomes

if ""actual parameter value"" == ""

which causes the "unexpected" error. If you instead use

if %1 == ""

then the error no longer occurs for quoted values. But in that case, the test no longer works when the value is missing -- it becomes

if  == ""

To fix this, use any other characters (except ones with special meaning to DOS) instead of quotes in the test:

if [%1] == []
if .%1. == ..
if abc%1xyz == abcxyz
  • 9
    Alternatively, using **"%~1"** would remove exterior quotations and allow for a real testing, because you never know what the user will type, like **this** or **"this"**. – Jay Sep 11 '09 at 12:05
7

"%~1" will work most of the time. However there are a few things you need to note for this:

  1. It's not supported in Windows NT 4.0. You need Windows 2000 or later. (If you are coding a script compatible with old OS, beware.)
  2. It does not make parameters secure and sanitized. My observation is that there is no way to properly sanitize the command line arguments in CMD scripts.

To demonstrate second point, let me give an example:

REM example.cmd
ECHO %~1

Run with example.cmd dummy^&DIR . The ampersand is escaped here ( ^& ) to prevent shell from interpreting as a command delimiter, so that it becomes part of the argument passed to the script. The DIR is interpreted as a command inside the sub-shell running the script, where it shouldn't.

Quoting it might work some time, but still insecure:

REM example2.cmd
SETLOCAL EnableExtensions EnableDelayedExpansion
SET "arg1=%~1"
ECHO "%~1"
ECHO !arg1:"=!

example2.cmd foo^"^&DIR^&^"bar will break it. DIR command will be run two times, one right after SET and the other right after the first ECHO. You see that the "%~1" you think is quoted well gets unquoted by the argument itself.

So, no way to make parsing arguments secure.

(EDIT: EnableDelayedExpansion doesn't work in Windows NT 4 either. Thanks to the info here: http://www.robvanderwoude.com/local.php)

Explorer09
  • 569
  • 5
  • 9
  • There exists [a way to receive even the strangest argument](http://stackoverflow.com/q/4200316/463115). It's completly different to a simple `set arg=%1`, it uses `REM` ! – jeb Mar 19 '15 at 09:11
  • The `REM` trick is cool, but it still cannot be bullet-proof in all cases. This is by design of the Command Interpreter (it sacrifices security for convenience). (And yes this problem drove me nuts, because Unix Bourne shell is already bullet-proof by design!) I think the only bullet-proof way is to introduce another `SETLOCAL` option that delays expansion of command-line arguments. That way we can use something like: `SETLOCAL EnableDelayedExpansionArgs & ECHO.arg1=!1` – Explorer09 Mar 24 '15 at 03:24
  • Do you can show one or two samples that fails with the REM trick? – jeb Mar 24 '15 at 10:03
  • @jeb , As stated in the answer of your linked question, it cannot accept newlines as part of the argument. I've experimented with this, and found out that it relies on `REM` command blocking parsing of the shell's meta-characters (`&|()<>`), and the fact that CMD shell does not allow newlines as part of arguments. So YES it's bullet-proof, but **only when the script is called from within CMD shell**. If you call the batch script from outside the shell (e.g. through `system(3)` function in C library or `exec(2)` system call), it's no longer bullet-proof. – Explorer09 Mar 25 '15 at 14:38
  • @jeb , This is how you counter it: with Windows PowerShell (yes it accepts newlines as part of the argument) `myBatch.bat "` `foo"` If it uses `SET /P` to read what is REM-echoed-to-a-file, the above goes infinite loop, as the first line of %1 is empty line. – Explorer09 Mar 25 '15 at 15:07
  • One more thing. I don't believe it is intentional to have shell variables expanded inside comments - the variables shouldn't be expanded at all. This is one of the reason I _personally_ don't like the `REM` trick here. – Explorer09 Mar 29 '15 at 03:56
5

If you have a path with spaces you must surround it with quotation marks (").

Not sure if that's exactly what you're asking though?

2

If your path contains space then try using %~s1. This will remove the space and appends ~1 to your path and more importantly it refers to the absolute path of your file. Try using it.

Pop
  • 12,135
  • 5
  • 55
  • 68
Prabhu RK
  • 71
  • 1
  • 1
  • 11
2
@echo off
setlocal enableextensions enabledelayedexpansion

if %1=="" (     
        rem Set default path
        set PWA_PATH="C:\Program Files\PWA"
        rem
        echo You have not specified your PWA url.
        echo Default will be assumed: C:\Program Files\PWA.     
        choice /C:YN /M:"Do you wish to continue [Y] or cancel the script [N]?"
                IF ERRORLEVEL ==2 GOTO CANCEL
                IF ERRORLEVEL ==1 GOTO READ_WSS_SERVER_EXTENSIONS_PATH
        GOTO END
    ) else (
        set PWA_PATH=%1
        @echo !PWA_PATH! vs. %1
        goto end
    )
:READ_WSS_SERVER_EXTENSIONS_PATH
echo ok
goto end
:CANCEL
echo cancelled
:end
echo. final %PWA_PATH% vs. %1

As VardhanDotNet mentions, %1 is enough.

"%1%" would add quotes around quotes: ""c:\Program Files\xxx"" which means:

  • 'empty string' (""),
  • followed by 'c:\Program',
  • followed by the "unexpected here" 'Files\xxx',
  • followed by an empty string ("")

Note however that if you need to use PWA_PATH within your IF clause, you need to refer if as !PWA_PATH! (hence the enabledelayedexpansion as the beginning of the script)

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

Suppose you want to backup a database by executing a batch file from within a C# code. Here is a fully working solution that deals with blank spaces inside the path. This works in Windows. I have not tested it with mono though.

C# code:

        public bool BackupDatabase()
    {
        bool res = true;
        string file = "db.bat";
        if (!File.Exists(file)) return false;

        BackupPaths.ForEach(path =>
        {
            Directory.CreateDirectory(path);

            string filePath = Path.Combine(path, string.Format("{0}_{1}.bak", Util.ConvertDateTimeToFileName(false), DatabaseName));
            Process process = new Process();
            process.StartInfo.FileName = file;
            process.StartInfo.Arguments = string.Format(" {0} {1} \\\"{2}\\\""
                , DBServerName
                , DatabaseName
                , filePath);

            process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;

            try
            {
                process.Start();
                process.WaitForExit();
            }
            catch (Exception ee)
            {
                Logger.Log(ee);
                res = false;
            }
        });
        return res;
    }

and here is the batch file:

@echo OFF
set DB_ServerName=%1
set Name_of_Database=%2
set PathToBackupLocation=%3

echo Server Name = '%DB_ServerName%'
echo Name of Database = '%Name_of_Database%'
echo Path To Backup Location = '%PathToBackupLocation%'

osql -S %DB_ServerName% -E -Q "BACKUP DATABASE %Name_of_Database% TO DISK=%PathToBackupLocation%"
BHP
  • 443
  • 4
  • 13
0

In a special case scenario, where you want to always default to the current directory always, OR, default to the current directory only if a parameter was not passed in, you can use %CD%.

As found per this answer on Server Fault:

Windows maintains the current directory in the psuedo-environment variable %CD%.

Check out that post for a great example and how your can easily test it directly via the command line.

In my case, I an an app (myapp.exe) that I needed to call with a command-line flag --dir=, like:
myapp --dir=<path-to-desired-directory>.

Since I always wanted the flag to always reference whichever directory I called the app from, I could make a batch file (mybat.bat) to automatically capture my current path, and open the app with it.

mybat . worked when mybat.bat had the following contents:
myapp --dir=%1

Which was great, but being able to leave off the . was even better.
mybat works when I changed the contents to:
myapp --dir=%CD%

SherylHohman
  • 16,580
  • 17
  • 88
  • 94