将带有空格的路径作为参数传递给批处理文件

80

我有一个简单的批处理脚本,它将文件从已知目录复制到由用户指定的目录。我如何将路径(可能包含空格)传递给我的脚本,并在xcopy命令中使用它?


我的代码如下:

: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
    )
如果我直接调用这个脚本,我会收到以下错误:
C:\Projects\Setup>install.cmd "C:\program files (x86)"

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

files was unexpected at this time.
C:\Projects\Setup>
9个回答

147

使用"%~1"%~1单独移除周围的引号。但是,由于您无法知道输入参数%1是否有引号,因此您应该通过"%~1"确保它们被添加。当连接变量时,这尤其有用,例如:convert.exe "%~1.input" "%~1.output"


实际上,在批处理文件中使用 "%~1"。但我猜这就是你的意思。 - Paul Groke
1
官方文档:https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/call - lovestackh343

20

有趣的话题。我喜欢收集有关在cmd/command中处理引用的引用的语录。

使用%1而不是"%1"可以修复您特定的脚本!!!

通过添加“echo on”(或者摆脱“echo off”),您可以很容易地找到这一点。


13
尽管这个答案被问题提出者标记为最佳答案,它解决了一个问题,却引入了另一个问题。如果您只是从%1中删除引号,当没有提供引号时(或根本没有给出参数时),您的代码可能会出现错误。最安全的解决方案是使用%~1,正如@zikaS的回答所述。 - Diego Queiroz
2
如何使用 %* 转义所有参数? - mvorisek

17

我认为OP的问题是他想要同时完成以下两个目标:

  • 传递可能包含空格的参数
  • 测试参数是否缺失

正如几位帖子中提到的那样,要传递包含空格的参数,必须用双引号括起实际的参数值。

要测试参数是否缺失,我一直学到的方法是:

if "%1" == ""

然而,如果实际参数被引用(如果值包含空格,则必须这样做),则变成

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

这会导致“意外”错误。如果您改用

if %1 == ""

如果使用引号将值括起来,则不会再出现错误。但在这种情况下,当值缺失时,测试将不再起作用 - 它变成了

if  == ""

为解决此问题,请在测试中使用任何其他字符(除了DOS具有特殊含义的字符)代替引号:

if [%1] == []
if .%1. == ..
if abc%1xyz == abcxyz

9
或者,使用 "%~1" 可以去除外部引号并允许进行真正的测试,因为你永远不知道用户会输入什么,比如 this 或 **"this"**。 - Jay

7

"%~1" 在大多数情况下都可以使用。但是有一些需要注意的事项:

  1. 它不支持 Windows NT 4.0,你需要使用 Windows 2000 或更新版本的操作系统。(如果你正在编写与旧操作系统兼容的脚本,请小心)
  2. 它无法使参数安全和经过处理。我的观察是,在 CMD 脚本中,没有适当的方式来消毒命令行参数。

为了证明第二点,让我举个例子:

REM example.cmd
ECHO %~1

使用example.cmd dummy^&DIR运行。这里转义了&符号( ^& ),以防止shell将其解释为命令定界符,使其成为传递给脚本的参数的一部分。DIR被解释为在运行脚本的子shell内部的一个命令,而这是不应该的。

用引号可能有时会起作用,但仍然不安全:

REM example2.cmd
SETLOCAL EnableExtensions EnableDelayedExpansion
SET "arg1=%~1"
ECHO "%~1"
ECHO !arg1:"=!
example2.cmd foo^"^&DIR^&^"bar会导致错误。DIR命令会在SET之后和第一个ECHO之后各运行一次。你会发现你认为已经加上引号的"%~1"被参数本身取消了引号。因此,无法使解析参数变得安全。(编辑:EnableDelayedExpansion在Windows NT 4中也无效。感谢这里的信息:http://www.robvanderwoude.com/local.php

存在一种方式可以接收最奇怪的参数。这与简单的“set arg=%1”完全不同,它使用了“REM”! - jeb
“REM”技巧很酷,但在所有情况下仍无法做到百分百防护。这是命令解释器的设计(它为方便而牺牲了安全性)。 (是的,这个问题让我疯狂,因为Unix Bourne shell已经通过设计实现了防弹!)我认为唯一防弹的方法是引入另一个“SETLOCAL”选项,延迟命令行参数的扩展。这样我们就可以使用类似于:“SETLOCAL EnableDelayedExpansionArgs & ECHO.arg1=!1”的东西。 - Explorer09
你能展示一两个在REM技巧下失败的样本吗? - jeb
@jeb,正如您链接问题的答案所述,它不能接受换行符作为参数的一部分。我已经进行了实验,并发现它依赖于REM命令阻止解析shell的元字符(&|()<>),以及CMD shell不允许换行符作为参数的一部分。 因此,是的,它是防弹的,但只有在从CMD shell内调用脚本时才是如此。如果您从外部调用批处理脚本(例如通过C库中的system(3)函数或exec(2)系统调用),它就不再是防弹的了。 - Explorer09
@jeb,这是如何解决它的方法:使用Windows PowerShell(是的,它接受换行符作为参数的一部分) myBatch.bat " <Enter> foo" <Enter><Enter> 如果它使用SET / P来读取REM-回显到文件中的内容,则上述代码会进入无限循环,因为%1的第一行是空行。 - Explorer09
还有一件事。我不相信在注释中展开shell变量是有意的 - 这些变量根本不应该被展开。这就是为什么我_个人_不喜欢这里的“REM”技巧的原因之一。 - Explorer09

5
如果您的路径中有空格,则必须用引号(")将其括起来。
不过我不确定这是否完全符合您的要求?

2

如果您的路径包含空格,请尝试使用%~s1。这将删除空格并在您的路径后附加~1,更重要的是它引用文件的绝对路径。建议尝试使用。


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

VardhanDotNet所提到的,%1就足够了。 "%1%"会在引号中添加引号:""c:\Program Files\xxx"",这意味着:
  • '空字符串'(""),
  • 后面跟着 'c:\Program',
  • 后面跟着 "此处不应出现" 的 'Files\xxx',
  • 最后是一个空字符串(""
请注意,如果您需要在IF子句中使用PWA_PATH,则需要将其称为!PWA_PATH!(因此脚本开头需要启用enabledelayedexpansion)。

0
假设您想通过在C#代码内执行批处理文件来备份数据库。这里是一个完全可行的解决方案,可以处理路径中的空格。 此方法适用于Windows系统。但我尚未在mono上进行测试。
C#代码:
        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;
    }

这是批处理文件的内容:
@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%"

0
在一个特殊情况下,如果你想要始终默认为当前目录,或者仅在没有传递参数时才默认为当前目录,你可以使用%CD%
正如在Server Fault上的答案中所发现的:

Windows将当前目录保存在伪环境变量%CD%中。

请查看该帖子以获取一个很好的示例,并了解如何通过命令行直接轻松测试它。
在我的情况下,我有一个应用程序(myapp.exe),我需要使用命令行标志--dir=来调用它,例如:
myapp --dir=<path-to-desired-directory>
由于我总是希望该标志始终引用我从中调用应用程序的任何目录,因此我可以创建一个批处理文件(mybat.bat)来自动捕获我的当前路径,并打开该应用程序。
当我的bat.bat具有以下内容时,mybat .就可以工作:
myapp --dir=%1

这很不错,但是能够省略 . 更好。
当我将内容更改为以下内容时,mybat 就可以工作了:
myapp --dir=%CD%


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接