批处理 - 如何正确分配任意百分数数字(%1)或百分号星号(%*)参数,这些参数可能包含引号?

5

在处理先前的问题时,我遇到了一个类似的问题,最初是为了解决它而努力。

实际上,我花了这个月的时间尝试创建一个通用的.bat帮助库,以尽可能处理任何名称的拖放文件。我想透明地为调用者执行此操作,并与任何现有脚本向后兼容,因此文件名作为%1、%2传递,并且我对此没有问题。

@MCND的解决方案是读取%CMDCMDLINE%然后进行复杂的解析。所以我可以像这样简单地写set "a=%CMDCMDLINE:"=?%"——就会将完整的字符串写入变量A中,但是其中所有的引号立即被替换为问号?,导致我可以轻松进一步处理未引用的行。

但它只适用于.bat的拖放,因为这样我无法读取从命令提示符传递的参数。我必须使用%*

我的当前问题是:如果参数可能包含引号字符,甚至是不成对的引号,该怎么读取它呢?

考虑以下BAT文件:(我们称之为“C:\test.bat”)

@echo off
echo START
setlocal enabledelayedexpansion

set a=%1
echo a=[!a!]

set b="%1"
echo b=[!b!]

set "c=%1"
echo c=[!c!]

set d=%~1
echo d=[!d!]

set e="%~1"
echo e=[!e!]

set "f=%~1"
echo f=[!f!]

set g=%*
echo g=[!g!]

set h="%*"
echo h=[!h!]

set "i=%*"
echo i=[!i!]

endlocal
echo DONE
exit /b

基本上,它是我能想象到的一切,用于读取传入批处理("call")百分比参数:在未引用的set x=y、引用的set "x=y"和显式引用的set x="y"变体中,形式为%1%~1%*。延迟扩展仅用于安全地回显值(这里我不关心参数中的检查标记!,以简化问题)。
我的最终目标是创建一个永远不会抛出错误的脚本:因此,每一个可能的错误都应该在我的代码执行之前(在CMD提示符本身)或之后(当调用者尝试访问具有损坏名称的文件时)出现。
因此,在此示例中,在START和DONE之间理想情况下不应出现任何错误,但它们将出现在相应的"echo"之前的"set"指令中。 尝试 #1 命令:test 输出:
START
a=[]
b=[""]
c=[]
d=[]
e=[""]
f=[]
g=[]
h=[""]
i=[]
DONE

很好,在零参数下没有错误。让我们加一些参数:

尝试 #2

命令:test 123 "56"

输出:

START
a=[123]
b=["123"]
c=[123]
d=[123]
e=["123"]
f=[123]
g=[123 "56"]
h=["123 "56""]
i=[123 "56"]
DONE

看起来对于“普通”的字符串,任何方法都可以无错误地工作。

尝试 #3

命令:test ^&-

输出:

START
'-' is not recognized as an internal or external command,
operable program or batch file.
a=[]
b=["&-"]
c=[&-]
'-' is not recognized as an internal or external command,
operable program or batch file.
d=[]
e=["&-"]
f=[&-]
'-' is not recognized as an internal or external command,
operable program or batch file.
g=[]
h=["&-"]
i=[&-]
DONE

如您所见,案例A、D和G失败了(=%1=%~1=%*)。

尝试 #4

命令:test "&-"

输出:

START
a=["&-"]
'-""' is not recognized as an internal or external command,
operable program or batch file.
b=[""]
'-""' is not recognized as an internal or external command,
operable program or batch file.
c=[]
'-""' is not recognized as an internal or external command,
operable program or batch file.
d=[]
e=["&-"]
f=[&-]
g=["&-"]
'-""' is not recognized as an internal or external command,
operable program or batch file.
h=[""]
'-""' is not recognized as an internal or external command,
operable program or batch file.
i=[]
DONE

现在B、C、D、H和I案例失败了(="%1""=%1"=%~1="%*""=%*")。 尝试 #5 命令:test ""^&-" 输出:
START
'-"' is not recognized as an internal or external command,
operable program or batch file.
a=[""]
b=["""&-""]
c=[""&-"]
d=["&-]
'-"' is not recognized as an internal or external command,
operable program or batch file.
e=[""]
'-"' is not recognized as an internal or external command,
operable program or batch file.
f=[]
'-"' is not recognized as an internal or external command,
operable program or batch file.
g=[""]
h=["""&-""]
i=[""&-"]
DONE

在E和F两个案例中失败了(="%~1""=%~1"),与A和G(=%1%*)一起。

因此,“set”变量的任何变体都不能可靠地在所有尝试中破坏我的代码!

我是否漏掉了一些其他方法来读取来自不受信任的源的当前参数?

我能否在普通变量%…%中获得%…?我能否直接在%…中替换引号"?我能否将%*放在某些特殊上下文中,例如&符号&可能不会导致执行更改?是否有另一种获取参数的方式(再次调用CMD,使用临时文件-任何内容)?

即使在这些情况下(未配对的引号,未转义的特殊字符等)破坏原始字符串,我也没关系,但我需要摆脱解析错误(导致不受控制的代码执行)!


尝试第五次:尝试将 test ""^&-" 更改为 test ""^&-^"... - aschipfl
1个回答

5

你无法使用普通的百分比扩展来解决这个问题!
证明:
样例参数为"&"&
要调用带有此参数的批处理文件,您必须转义第二个 "&" 符号。

test.bat "&"^&

使用常规命令无法处理此问题,因为您始终会得到一个未引用的“&”符号。

set arg=%1
set "arg=%1"
set arg=%~1
set "arg=%~1"

每行代码都会因为错误信息而失败。
是否有任何命令不会因为 %1%* 而失败呢?是的,REM 可以正确处理它们。
这样做不会产生错误。
REM %1

有趣,但是使用REM语句如何使内容受益呢?
首先你必须启用ECHO ON以查看它是否真正起作用。

echo on
REM %1

很好,现在您可以看到调试输出了。

c:\temp>REM "&"&

现在您只需要将调试输出重定向到文件中。
有许多方法是不起作用的。

@echo on
> arg.txt REM %1
( REM %1 ) > arg.txt
call :func > arg.txt

..
:func 
REM %1

调用重定向本身可以正常工作,但在函数内部你不能再访问%1了。
但是有一种解决方案可以通过使用FOR循环来获取输出。

(
    @echo on
    for %%a in (42) do (
        rem %1
    ) 
) > arg.txt

将参数存储在文件中是否比存储在%1中更好?
是的,因为文件可以独立于内容以安全的方式进行读取。

仍然存在一些问题,例如参数中的/?^%%a
但这些都可以解决。

@echo off
setlocal DisableDelayedExpansion
set "prompt=X"
setlocal DisableExtensions
(
    @echo on
    for %%a in (4) do (
        rem #%1#
    ) 
) > XY.txt
@echo off
echo x
endlocal
for /F "delims=" %%a in (xy.txt) DO (
  set "param=%%a"
)
setlocal EnableDelayedExpansion
set param=!param:~7,-4!
echo param='!param!'

更多关于该主题的信息请参见
SO:如何接收最奇怪的命令行参数?
SO:接收多行参数

对于拖放,您需要更复杂的解决方案 SO:为多个文件拖放批处理文件?

编辑:解决方案%*
必须禁用扩展名,否则会修改像%a--%~a这样的参数。
但是如果没有扩展名,则%*扩展不再起作用。
因此,在扩展名扩展%*之前启用扩展名,但在FOR显示内容之前将禁用扩展名。

@echo off
setlocal EnableExtensions DisableDelayedexpansion
set "prompt=-"
(
    setlocal DisableExtensions    
    @echo on
    for %%a in (%%a) do (
        rem # %*#
    )
) > args.tmp
@echo off
endlocal

set "args="
for /F "tokens=1* delims=#" %%G in (args.tmp) DO if not defined args set "args=%%H"
setlocal EnableDelayedExpansion
set "args=!args:~1,-4!"
echo('!args!'

这种方法可以安全地获取所有参数,但是对于来自拖放操作的参数,即使如此也不足够,因为Windows存在一个错误(设计缺陷)。


Documents&More这样的文件就无法用这种方式获取。


哇,著名的Jeb!我尝试了你的方法,它很有效。直到我传递像;=,这样的东西,因为它们是分隔符,如果不加引号,它们根本不会传递给%1。而且很难(我认为)获取参数计数(并提取所有参数,而不仅仅是第一个)。是否有使用%*的解决方案(由于禁用扩展而无法在此处工作),副作用尽可能少?我有自己的尝试,我测试过了,找不到任何问题;所有的都可以正常工作(/?%~…^ - 还有什么问题吗?)。你能看一下吗?我将在以下评论中发布它: - aleksusklim
因为它是多行的,所以在从这里复制后将所有#替换为新行。其中涉及到“退格”字符,它被替换为$ - 您还必须将其替换为“7Fh”字符(在记事本中使用Ctrl + Backspace或Alt + 0127):#@setlocal enableextensions disabledelayedexpansion#@echo on&@(for %%$ in (_) do (@call#rem @ %*@#))>tmp.txt&@echo off&endlocal##set "!="&set "&="&for /F "tokens=1* delims=@" %%G in (tmp.txt) DO set "!=%%H"&if defined ! set "&=%%H"#set "&=%&:"=$%"#set "&=%&:~1,-2%"#echo "%&%"#。使用退格作为变量可以防止扩展;也用作引号替换。 - aleksusklim
@aleksusklim 当使用类似%$--%~$这样的参数时,它会失败,请参考我的编辑答案。我不理解“将退格键用作变量会防止扩展”的部分。 - jeb
1
@aleksusklim 好的,我现在明白你的观点了,但这并不安全,因为程序(也可能是另一个批处理文件)可以在参数中使用这样的字符,而且我认为甚至可以从命令行中实现。 - jeb
你缺少一个endlocal(实际上是两个);第一个桩rem最好写成@rem(但skip=已经很明智了!);另外,稍后使用set "prompt= "(带空格)+ set "prompt="(空)也会很好。但真的非常感谢!现在我将使用您的方法来解决我的主要任务,并在我的答案中展示最终代码以回答我之前的问题(我希望这不会再花费我一个月的时间...)。 - aleksusklim
显示剩余2条评论

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