在批处理函数的参数中使用特殊字符的奇怪行为?

时间:2012-09-29 18:58:30

标签: windows batch-file cmd

假设您运行test.bat "blabla,blabla,^>blabla", "blaby"

test.bat实现:

@SETLOCAL
@ECHO OFF
SET list=%~1
ECHO "LIST: %list%"
ECHO "ARG 1: %~1"
ECHO "ARG 2: %~2"
@ENDLOCAL   
@GOTO :EOF

输出符合预期:

"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

但是如果你在批处理文件中使test.bat成为一个函数怎么办呢?

@SETLOCAL
CALL :TEST "blabla,blabla,^>blabla", "blaby"
@ENDLOCAL
@GOTO :EOF

:TEST
@SETLOCAL
@ECHO OFF
SET list=%~1
ECHO "LIST: %list%"
ECHO "ARG 1: %~1"
ECHO "ARG 2: %~2"
@ENDLOCAL   
@GOTO :EOF

运行后输出为:

"LIST: blabla,blabla,^"
"ARG 1: blabla,blabla,^^>blabla"
"ARG 2: blaby"

咦?

  1. blabla在哪里进入列表?
  2. ARG 1有^^?为什么?
  3. 有人可以解释特殊字符在函数参数中的行为方式与命令行参数不同吗?

2 个答案:

答案 0 :(得分:9)

只需使用以下命令即可获得与第一批脚本相同的结果:

call test.bat "blabla,blabla,^>blabla", "blaby"

您的问题源于批处理如何解析CALL语句的不幸方面。在How does the Windows Command Interpreter (CMD.EXE) parse scripts?的第6阶段对此进行了描述。

哎哟 - 我以为我理解之前插入的插入符号,但显然不是。为了回应jeb的评论,我对以下讨论进行了大量编辑。

CMD.EXE的设计者希望像call echo ^^这样的语句提供与echo ^^相同的结果。在处理特殊字符的阶段2中,两个语句都将^^减少为^。但是CALL声明必须第二次经历第1阶段和第2阶段。因此,在幕后,当CMD.EXE识别阶段6中的CALL语句时,它将剩余的插入符号加倍回^^,然后第二轮第2阶段将其缩减回^。这两个语句都将一个插入符号回显到屏幕上。

不幸的是,CMD.EXE会盲目地将所有插入符号加倍,即使它们被引用也是如此。但引用的插入符号不被视为逃避,它是一个文字。插入符号不再被消耗。非常不幸。

在解析器的第6阶段运行call test.bat "blabla,blabla,^>blabla", "blaby"变为call test.bat "blabla,blabla,^^>blabla" "blaby"

这很容易解释为什么ARG 1在你的输出中看起来像。

至于blabla去哪儿了?,这有点棘手。

当您的脚本执行SET list=%~1时,系统会删除引号,^^会被视为转义符号,缩减为^>不再转义。因此,SET语句的输出被重定向到“blabla”文件。当然SET没有输出,所以你的硬盘上应该有一个零长度的“blabla”文件。

编辑 - 如何使用“延迟扩展”正确传递所需的参数

在他的回答中,davor试图扭转在所调用的例程中插入符号倍增的效果。但这并不可靠,因为你无法确定插入符号可能翻倍的次数。如果让呼叫者调整呼叫以进行补偿,则会更好。这很棘手 - 你必须使用什么叫做“后期扩张”的jeb

在批处理脚本中,您可以定义包含所需参数字符串的变量,然后通过使用另一个%转义%来延迟扩展,直到插入符号加倍。您需要将语句中每个CALL的百分比加倍。

@echo off
setlocal
set arg1="blabla,blabla,^>blabla"
call :TEST %%arg1%% "blaby"
echo(
call call :TEST %%%%arg1%%%% "blaby"
::unquoted test
exit /b

:TEST
setlocal
set list=%~1
echo "LIST: %list%"
echo "ARG 1: %~1"
echo "ARG 2: %~2"
exit /b

以上产生了预期的结果:

"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

从命令行运行时,扩展规则不同。从命令行逃避%是不可能的。相反,您必须在百分比内添加一个插入符号,以防止扩展阶段识别第一遍的名称,然后在阶段2中剥离插入符号时,第二次扩展传递正确地扩展变量。

以下使用davor的原始TEST.BAT

C:\test>test.bat "blabla,blabla,^>blabla" "blaby"
"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

C:\test>set arg1="blabla,blabla,^>blabla"

C:\test>test.bat %arg1% "blaby"
"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

C:\test>call test.bat %^arg1% "blaby"
"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

C:\test>set arg2=%^arg1%

C:\test>call call test.bat %^arg2% "blaby"
"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

转义的替代方法 - 通过引用传递值!

总而言之,逃避规则非常复杂。这就是高级批处理脚本通常通过引用而不是文字传递字符串值的原因。将所需的字符串放在变量中,然后将变量的名称作为参数传递。延迟扩展用于获取确切的字符串,而不必担心因特殊字符或CALL插入符号加倍或剥离百分比而导致的损坏。

这是一个简单的test.bat,它演示了概念

@echo off
setlocal enableDelayedExpansion
set "var1=!%~1!"
echo var1=!var1!

call :test var1
exit /b

:test
set "var2=!%~1!"
echo var2=!var2!

以下是它如何运作的演示。

C:\test>set complicatedString="This & that ^" ^& the other thing ^^ is 100% difficult to escape

C:\test>set complicatedString
complicatedString="This & that ^" & the other thing ^ is 100% difficult to escape

C:\test>test.bat complicatedString
var1="This & that ^" & the other thing ^ is 100% difficult to escape
var2="This & that ^" & the other thing ^ is 100% difficult to escape

C:\test>call test.bat complicatedString
var1="This & that ^" & the other thing ^ is 100% difficult to escape
var2="This & that ^" & the other thing ^ is 100% difficult to escape

C:\test>call call test.bat complicatedString
var1="This & that ^" & the other thing ^ is 100% difficult to escape
var2="This & that ^" & the other thing ^ is 100% difficult to escape

答案 1 :(得分:0)

经过一些测试和dbenham的答案后,人们需要预测双插入符并用单插入符替换它:

@SETLOCAL
CALL :TEST "blabla,blabla,^>blabla", "blaby"
@ENDLOCAL
@GOTO :EOF

:TEST
@SETLOCAL
@ECHO OFF
SET "list=%~1" & REM CHANGED
SET "list=%list:^^=^%" & REM ADDED
ECHO "LIST: %list%"
ECHO "ARG 1: %~1"
ECHO "ARG 2: %~2"
@ENDLOCAL   
@GOTO :EOF

输出是:

"LIST: blabla,blabla,^>blabla"
"ARG 1: blabla,blabla,^^>blabla"
"ARG 2: blaby"

还要注意奇怪的事情: 在SET "list=%list:^^=^%"中,^^之间的%%被视为两个字符,而非被转义的^