【问题标题】:How to split double quoted strings with embedded spaces deliminated with spaces in a batch file?如何使用批处理文件中的空格分隔嵌入空格的双引号字符串?
【发布时间】:2011-10-28 21:55:28
【问题描述】:

我正在努力改进我提出的作为How to write a batch file showing path to executable and version of Python handling Python scripts on Windows? 问题答案的脚本。为了防止 Open With 对话框,我想读取 ftype 命令的输出,提取路径 从中可执行并检查它是否存在。

之后

@echo off
setlocal EnableDelayedExpansion 
rem c:\ftype Python.File ->
rem Python.File="c:\path with spaces, (parentheses) and % signs\python.exe" "%1" %*
for /f "tokens=2 delims==" %%i in ('ftype Python.File') do (
    set "reg_entry=%%i"
)

reg_entry's 内容是

"c:\path with spaces and (parentheses) and % signs\python.exe" "%1" %*

我如何拆分这个来获得 "c:\path with spaces, (parentheses) and % signs\python.exe""%1"%*?

编辑
在阅读了 Aacini 的答案后,我尝试使用 call,它几乎可以工作。但是,它不处理 % 符号。

@echo off
setlocal EnableDelayedExpansion 
set input="c:\path with spaces and (parentheses) and %% signs\python.exe" "%%1" %%*
echo !input!
call :first_token output !input!
echo !output!
goto :eof

:first_token
set "%~1=%2"
goto :eof

输出

"c:\path with spaces and (parentheses) and % signs\python.exe" "%1" %*
"c:\path with spaces and (parentheses) and 1"

【问题讨论】:

  • 我查看了我计算机的 FTYPE 输出,发现了一些文件未包含在引号中的条目,即使路径包含空格也是如此。我担心您使用 FTYPE 的策略可能不可靠。
  • @dbenham 我也注意到了这一点。然而,考虑到我在编写看似简单的批处理文件时遇到的所有这些问题,我忽略了这个问题。

标签: windows parsing batch-file


【解决方案1】:

与 CALL 解析器非常相似的另一种解析器是简单的 FOR。有两个复杂的因素:

1- 如果 FOR 包含 !,则在启用延迟扩展时不得扩展 FOR。这很容易处理。

2- 内容不得包含通配符*?? 可以临时替换然后返回。但是没有简单的方法来搜索和替换*

由于这个问题是试图解析出一个路径,而路径不能包含通配符,这个问题很容易解决,无需使用 CALL。为了完整起见,我在测试用例中添加了!

@echo off
setlocal disableDelayedExpansion
set input="c:\path with spaces, ampersand &, carets ^ and (parentheses)! and %% signs\python.exe" "%%1" %%*
set input
set "output="
setlocal enableDelayedExpansion
for %%A in (!input!) do if not defined output endlocal & set output=%%A
set output

如果我们可以依赖第一个标记总是用引号括起来的事实,那么解决方案就更容易了。我们可以使用 FOR /F 并将 EOL 和 DELIMS 都设置为 "

@echo off
setlocal disableDelayedExpansion
set input="c:\path with spaces, ampersand &, carets ^ and (parentheses)! and %% signs\python.exe" "%%1" %%*
set input
set "output="
setlocal enableDelayedExpansion
for /f eol^=^"^ delims^=^" %%A in ("!input!") do endlocal & set output="%%A"
set output

但是,我只是查看了我的 FTYPE 输出,发现有些条目没有被引用,即使它们在路径中包含空格!我认为此页面上的任何答案都不会解决此问题。事实上,这个问题背后的整个前提可能是有缺陷的。

【讨论】:

  • 您的评论如果我们可以依赖第一个标记总是用引号括起来的事实,那么解决方案就更容易了。 似乎表明第一个解决方案有效即使第一个标记没有用引号括起来。
  • @Piotr Dobrogost 正确。只要路径不包含任何空格或特殊字符,我的第一个解决方案就可以使用不带引号的路径。如果路径确实包含空格/特殊字符,则必须引用路径。当然,如果没有引号,第二种解决方案将永远无法工作。
  • 感谢您的信息。但是,我注意到我在How to write a batch file showing path to executable and version of Python handling Python scripts on Windows? 改进的解决方案中丢失了for /f "tokens=2 delims==" %%i in ('ftype Python.File') do set reg_entry=%%i 之后的! 符号
  • @Piotr Dobrogost - 关注您的链接并在评论中提供解决方案。
【解决方案2】:

这是 Batch 的直接能力。在批处理中,批处理文件的参数用空格分隔,参数可以用引号引起来,因此只需将 reg_entry 的值作为批处理文件的参数传递,然后在其中获取每个参数:

C:\>type test.bat
@echo off
:loop
echo %1
shift
if not "%1" == "" goto loop

.

C:\>echo %reg_entry%
"c:\path with spaces and (parentheses) and % signs\python.exe" "%1" %*

.

C:\>test %reg_entry%
"c:\path with spaces and (parentheses) and % signs\python.exe"
"%1"
%*

【讨论】:

  • 一切都很好,但是如何在现有批处理文件的上下文中使用这种方法?解析call的参数和解析批处理文件的参数一样吗?如果是这样,与将它们传递到命令行上的批处理文件相比,我是否需要在批处理文件中对这些参数进行任何额外的转义?
  • 几乎一样,如果内容在引号内,则不需要转义。唯一造成麻烦的字符是插入符号,每个插入符号(引号内)将被调用加倍
【解决方案3】:

正如 Aacini 所说,您的问题可以通过使用 CALL 语句的内部参数拆分来解决。

为避免 call 丢失 % 符号,您可以在 call 扩展之前将它们加倍。
关键线是set "input=!input:%%=%%%%!",百分号在其中一个解析器阶段减半,因此将单个% 替换为%%

但即便如此,这个解决方案也不是完美的!

此解决方案存在&<>| 等特殊字符的问题,在您的情况下只有&,因为这是文件名/路径中唯一的合法字符。
可以通过将行 set "%~1=%2" 更改为 set ^"%~1=%2" 来避免这种情况,这样可以确保 %2 使用周围的引号。

但是现在您遇到了另一个问题,所有插入符号都加倍了,
所以我必须用set "output=!output:^^=^!" 替换输出。

新代码如下所示

@echo off
setlocal EnableDelayedExpansion 
set input="c:\path with spaces, exlcamation mark^!, ampersand &, carets ^ and (parentheses) and %% signs\python.exe" "%%1" %%*
echo !input!
set "input=!input:%%=%%%%!"
call :first_token output !input!
set "output=!output:^^=^!"
echo !output!
goto :eof

:first_token
set ^"%~1=%2"
goto :eof

编辑:用于处理感叹号!
您需要将:first_token 函数更改为

:first_token
setlocal DisableDelayedExpansion
set ^"temp=%2"
set ^"temp=%temp:!=^!%"
(
endlocal
set ^"%~1=%temp%"
)
goto :eof

【讨论】:

  • 还有一个错误 - 您忘记处理 !,这是路径中另一个可能导致问题的有效字符。
  • :-) 你知道了,我没有忘记,但你也知道,这个call 解决方案不能简单地处理这个问题
  • 我为这种特殊情况找到了一个简单解决方案,因为字符串总是被引用
【解决方案4】:

基本上你要做的就是将整个字符串转换成它的元素,就像解析器会做的那样。在您的情况下,由于 Windows 规则关于允许空格的位置,词法分析可能会起到作用。

从根本上说,您需要在 .cmd 文件中构建一个带有标签和条件 goto 的有限状态机。 FSA 具有处理您希望收集的元素的各个部分的状态。在开始状态下,您决定是否看到空白(跳过并返回开始)、双引号(转到 FSA 处理双引号字符串的部分)或非空白(转到收集非空白字符的 FSA)。

收集双引号字符串的 FSA 部分会挑选字符,直到找到另一个双引号;这就是让您在双引号字符串中捕获空白的原因。我认为您必须检查“转义”双引号(其中两个连续),如果找到,请将它们替换为单个双引号并继续收集字符。

这非常难看,因为 CMD 脚本具有非常糟糕的字符串处理能力。通过在 DOS 命令提示符下键入 HELP SET 可以找到您需要知道的每一件(丑陋的)事情。特别是,子字符串的形式为%VAR:~n,m%,它从环境变量%VAR% 中的索引n 开始挑选m 字符。我发现它对SET TEMP=%VAR% 很有用,然后通过简单的序列一一从%TEMP% 中剥离字符,例如

SET CHAR=%TEMP:~0,1%
SET TEMP=%TEMP:~1%

享受吧。

【讨论】:

  • 我冒昧地修复了您的代码 sn-ps 中的一些小错误,例如 SET %var%=... 中不必要的 % 或错误的 1 应该是 @987654332 @(表示第一个字符)。请看一下,以防万一我弄错了。
  • 很好的解释,是的,它可以用有限状态机来解决,但这里没有必要,因为批处理有这个内置
  • 是的,如果您可以分叉另一个 .CMD 文件。我们在世界上受到限制,以尽量减少周围的文件数量,所以我们坚持以艰难的方式去做。不幸的是,这让我们擅长它:-{
  • 您不需要另一个 CMD 文件,正如 Aacini、dbenham 和我在我们的回答中所展示的那样(但也许我完全误解了您的最后评论,因为我很难翻译它)
  • @jeb 在你们建议的解决方案中有很多警告(“加倍,逃避那个,...哎呀”)和陷阱,我认为我们会坚持我们的方案。至少我们可以随心所欲地调整它。而且写起来也没有那么难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-14
  • 2011-07-11
  • 2021-05-20
  • 1970-01-01
相关资源
最近更新 更多