如何在Windows批处理文件中去除ECHO输出字符串中的引号?

52

我正在创建一个Windows批处理文件,但必须ECHO一个庞大的复杂字符串,因此我不得不在两端放双引号。问题是引号也被一同ECHO到我要写入的文件中。如何ECHO这样的字符串并去掉引号?

更新:

我花了两天时间来解决这个问题,最终成功地把一些东西凑合在一起。Richard的答案能够去除引号,但即使我将ECHO放在子程序中并直接输出该字符串,Windows仍然会卡在字符串中的字符上。我将接受Richard的答案,因为它回答了所问的问题。

最终我使用了Greg的sed解决方案,但不得不进行修改,因为在sed / windows中存在错误/功能(没有文档也没有帮助)。使用sed有一些注意事项:必须使用双引号而不是单引号,不能直接转义字符串中的双引号,必须用^转义(例如^"),然后开始下一段字符串。此外,有人指出,如果将输入导管输送到sed,则字符串中有一个管道就会出现错误(由于在我的最终解决方案中找到了一种方法,不需要在字符串中使用所有引号,只需删除所有引号,我从未能够将最后一个引号独立删除。)感谢大家的帮助。

13个回答

59

call命令内置了此功能。引用call的帮助:

 Substitution of batch parameters (%n) has been enhanced.  You can
 now use the following optional syntax:

 %~1         - expands %1 removing any surrounding quotes (")

这是一个原始的例子:

@echo off
setlocal
set mystring="this is some quoted text"
echo mystring=%mystring%
call :dequote %mystring%
echo ret=%ret%
endlocal
goto :eof

:dequote
setlocal
rem The tilde in the next line is the really important bit.
set thestring=%~1
endlocal&set ret=%thestring%
goto :eof

输出:

C:\>dequote
mystring="this is some quoted text"
ret=this is some quoted text

我想要感谢 Tim Hill 在他的书"Windows NT Shell Scripting"中提到的“环境变量隧道”技术(endlocal&set ret=%thestring%)。这是我找到的唯一一本深入讲解批处理文件的书。


太好了!在提供参数时,是否有类似的简单方法可以去除引号?对于我们只控制调用方而不控制处理参数的脚本的情况呢? - Jens Schauder
把你的调用放在自己的函数中进行剥离,是否足够呢?就像上面所示。 - Richard A
有趣的是,这对我不起作用。即使我使用与Richard显示的完全相同的语法,并且尽管在“call /?”部分中看到相同的文本,“set thestring =%~1”仍会产生“命令语法不正确”的错误。 - Jim2B
问题出在我的文本中。它是XML格式,在其中包含“<”和“>”。因此Richard的解决方案确实可行,我只需要找到另一种操纵文本的方法。 - Jim2B
@Jim2B 有时候生命就是太短暂了。不管怎样,我很高兴你解决了它。 - Richard A
显示剩余2条评论

33

以下方法可用于在不使用引号的情况下打印字符串:

echo|set /p="<h1>Hello</h1>"

将此字符串推入文件中:

echo|set /p="<h1>Hello</h1>" > test.txt

将这个字符串推送到文件中并附加一个CR / LF:

echo|(set /p="<h1>Hello</h1>" & echo.) > test.txt`

检查:

type test.txt

不错的技巧。但是微软对cmd.exe批处理语言的不负责任、临时应变的方法使得这些黑客攻击成为必要。 - Michael Burr
请注意,在此形式中,“set”命令的用法无效并会引发“%ERRORLEVEL%”错误。为了避免这种情况,您需要提供一些虚拟变量名称:“echo | set /p DUMMY="<h1>Hello</h1>"”。 - Alex Che

12
你可以使用%var:x=y%的结构,将所有的x替换为y。看这个例子就能明白它的用途:
set I="Text in quotes"
rem next line replaces " with blanks
set J=%I:"=%
echo original %I%
rem next line replaces the string 'in' with the string 'without' 
echo stripped %J:in=without%

4
为了从一个集合变量中删除所有引号,你需要使用延迟变量扩展来安全地扩展变量并进行处理。使用百分号(即%VAR%%1)进行扩展本质上是不安全的(它们容易受到命令注入攻击,详情请参见此处)。
SETLOCAL EnableDelayedExpansion
SET VAR=A ^"quoted^" text.
REM This strips all quotes from VAR:
ECHO !VAR:^"=!
REM Really that's it.

要从文本文件或命令输出中删除引号,事情会变得复杂,因为使用延迟扩展时,文本文档中类似于!VAR!的字符串将在不应该扩展的情况下(在FOR /F中的%%i扩展内)扩展。这是另一个漏洞——信息泄露,其他地方没有记录。
为了安全地解析文档,需要在启用和禁用延迟扩展的环境之间切换。
REM Suppose we fetch the text from text.txt

SETLOCAL DisableDelayedExpansion
REM The FOR options here employs a trick to disable both "delims"
REM characters (i.e. field separators) and "eol" character (i.e. comment
REM character).
FOR /F delims^=^ eol^= %%L IN (text.txt) DO (

    REM This expansion is safe because cmd.exe expands %%L after quotes
    REM parsing as long as DelayedExpansion is Disabled. Even when %%L
    REM can contain quotes, carets and exclamation marks.
    SET "line=%%L"

    CALL :strip_quotes
    REM Print out the result. (We can't use !line! here without delayed
    REM expansion, so do so in a subroutine.)
    CALL :print_line
)
ENDLOCAL
GOTO :EOF

REM Reads !line! variable and strips quotes from it.
:strip_quotes
    SETLOCAL EnableDelayedExpansion
    SET line=!line:^"=!

    REM Make the variable out of SETLOCAL
    REM I'm expecting you know how this works:
    REM (You may use ampersand instead:
    REM `ENDLOCAL & SET "line=%line%"`
    REM I just present another way that works.)
    (
    ENDLOCAL
    SET "line=%line%"
    )
GOTO :EOF

:print_line
    SETLOCAL EnableDelayedExpansion
    ECHO !line!
    ENDLOCAL
GOTO :EOF

上述代码中的 delims^=^ eol^= 可能需要解释: 这实际上禁用了“delims”字符(即字段分隔符)和“eol”字符(即注释字符)。如果没有它,"delims" 将默认为制表符和空格,"eol" 将默认为分号。
  • eol= 标记总是读取等号后的下一个字符。要禁用它,该标记必须在选项字符串的末尾,以便没有任何字符可用于 "eol",从而有效地禁用它。如果选项字符串被引用,它可能会使用引号(")作为 "eol",因此我们不应该引用选项字符串。
  • delims= 选项,在选项字符串中不是最后一个选项时,将以空格终止。(要在 "delims" 中包含空格,它必须是 FOR /F 选项的最后一个选项。)因此,delims= 后跟一个空格,然后是另一个选项,会禁用 "delims"。

4

以下方法适用于我:

SET "SOMETHING=Complex (String) (of stuff!)"
echo !SOMETHING! >> file.txt

3
在首先添加了 SETLOCAL ENABLEDELAYEDEXPANSION 后,这个解决方案对我有效(在 Windows 10 cmd.exe 上)。 - Tzunghsing David Wong
1
我应该提到这是因为!VARIABLE!而不是%VARIABLE%,所以这是必需的。 - Mike Q

3

我知道这不是为了作者而存在的,但是如果你需要将一些没有引号的文本发送到文件中,下面的解决方案适用于我。您无需在echo命令中使用引号,只需用括号括起整个命令即可。

(
    echo first very long line
    echo second very long line with %lots% %of% %values%
) >"%filename%"

2
使用FOR命令来去掉周围的引号是我发现的最高效的方法。在紧凑的形式下(示例2),它是一行代码。
示例1:这是一个5行的(带注释)解决方案。
REM Set your string
SET STR=" <output file>    (Optional) If specified this is the name of your edited file"

REM Echo your string into the FOR loop
FOR /F "usebackq tokens=*" %%A IN (`ECHO %STR%`) DO (
    REM Use the "~" syntax modifier to strip the surrounding quotation marks
    ECHO %%~A
)

例子2: 一个实际应用的1行代码示例。

SET STR=" <output file>    (Optional) If specified this is the name of your edited file"

FOR /F "usebackq tokens=*" %%A IN (`ECHO %STR%`) DO @ECHO %%~A

我觉得有趣的是,内部回显会忽略重定向字符'<'和'>'。如果你执行ECHO asdfsd>asdfasd,你将把文件写出而不是标准输出。
希望这能帮到你 :)
编辑:
我考虑了一下,意识到有一种更简单(并且不那么hacky)的方法来实现相同的目的。使用增强的变量替换/扩展(请参见HELP SET),像这样:
SET STR=" <output file>    (Optional) If specified this is the name of your edited file"

ECHO %STR:~1,-1%

这将打印除第一个和最后一个字符(即引号)之外的所有内容。我建议还使用SETLOCAL ENABLEDELAYEDEXPANSION。如果您需要确定字符串中引号的位置,可以使用FINDSTR获取字符编号。


我认为这对于像“ This&That”这样的STR不起作用。 - PeterCo

2
这将把"C:\Program Files\somefile.txt"转换为C:\Program Files\somefile.txt,同时保留Height=5'6"Symbols="!@#等大小写格式。
:DeQuote

SET _DeQuoteVar=%1
CALL SET _DeQuoteString=%%!_DeQuoteVar!%%
IF [!_DeQuoteString:~0^,1!]==[^"] (
IF [!_DeQuoteString:~-1!]==[^"] (
SET _DeQuoteString=!_DeQuoteString:~1,-1!
) ELSE (GOTO :EOF)
) ELSE (GOTO :EOF)
SET !_DeQuoteVar!=!_DeQuoteString!
SET _DeQuoteVar=
SET _DeQuoteString=
GOTO :EOF

示例

SetLocal EnableDelayedExpansion
set _MyVariable = "C:\Program Files\ss64\"
CALL :dequote _MyVariable
echo %_MyVariable%

1
这可以通过使用setlocal和endlocal而不是取消引用两个本地变量来简化。不过,我更喜欢我的解决方案。 - Richard A

2
上述答案(以:DeQuote开头)假定延迟环境变量扩展设置为打开状态。从cmd /?中可以得知:
默认情况下未启用延迟环境变量扩展。您可以使用/V:ON或/V:OFF开关为CMD.EXE的特定调用启用或禁用延迟环境变量扩展。您可以通过使用REGEDT32.EXE在注册表中设置以下一个或两个REG_DWORD值,为机器和/或用户登录会话上所有CMD.EXE调用启用或禁用完成:
HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\DelayedExpansion

    and/or

HKEY_CURRENT_USER\Software\Microsoft\Command Processor\DelayedExpansion

将其设置为0x1或0x0。用户特定设置优先于机器设置。命令行开关优先于注册表设置。

如果启用了延迟环境变量扩展,则感叹号字符可以用于在执行时替换环境变量的值。


以上指的是JRL的答案。 - Richard A
对于单个批处理,您应该使用setlocal enabledelayedexpansion。无需在注册表中搞乱或告诉用户如何调用cmd。 - Joey
谢谢你的“setlocal”提示,我之前不知道这个。 - james

2
下面的批处理文件会启动一系列程序,并在每个程序后延迟一段时间。
问题是如何为每个程序传递带参数的命令行。这需要在程序参数周围加上引号,但在调用时会被删除。这展示了一些批处理文件处理技术。
在本地子例程:mystart中查看如何传递带引号的参数,并将引号删除。
@echo off

rem http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/if.mspx?mfr=true

rem Start programs with delay

rem  Wait n seconds
rem  n number retries to communicate with the IP address
rem  1000 milliseconds between the retries
rem  127.0.0.1 is the LocalHost
rem  start /b (silent)  /min (minimized) /belownormal (lower priority)
rem  /normal provides a no-op switch to hold the place of argument 1

rem  start  /normal "Opinions"  %SystemRoot%\explorer.exe /e,d:\agar\jobs\opinion
rem  ping 127.0.0.1 -n 8 -w 1000 > nul

rem   Remove quotes in Batch
rem     http://ss64.com/nt/syntax-dequote.html
rem   String manipulation in Batch
rem     http://www.dostips.com/DtTipsStringManipulation.php
rem   ^ line continuation
rem   
rem   set p="One Two"      p has the exact value  "One Two" including the quotes           
rem   set p=%p:~1,-1%      Removes the first and last characters
rem   set p=%p:"=%         Removes all double-quotes
rem   set p=%p:cat=mouse%  Replaces cat with mouse

rem  ping 127.0.0.1 -n 12 -w 1000 > nul
rem        1       2            3                                                         4

@echo on
call :mystart /b/min  "Opinions"   "%SystemRoot%\explorer.exe  /e,d:\agar\jobs\opinion"   8  
@echo on
call :mystart /b/min  "Notepad++"  D:\Prog_D\Notepad++\notepad++.exe  14
@echo on
call :mystart /normal "Firefox"    D:\Prog_D\Firefox\firefox.exe      20
@rem call :mystart /b/min "ProcessExplorer"  D:\Prog_D\AntiVirus\SysInternals\procexp.exe  8
@echo on
call :mystart /b/min/belownormal "Outlook" D:\Prog_D\MSOffice\OFFICE11\outlook.exe  2
@echo off
goto:eof

:mystart
@echo off
 rem  %3 is "program-path  arguments" with the quotes. We remove the quotes
 rem  %4 is seconds to wait after starting that program
 set p=%3
 set p=%p:"=%
 start  %1  %2  %p% 
 ping 127.0.0.1 -n %4 -w 1000 > nul
 goto:eof

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