我有一个由%%p
命令创建的变量for /f
当我尝试将其与一些其他引用一起使用时,例如:%%~dp
,然后在其后写一些文本,它将访问另一个变量
set var="%%~dpabc.txt"
代码输出
%%~dpa instead of %%~dp
答案 0 :(得分:4)
因此,您必须对多个令牌使用FOR / F,例如
for /f "tokens=1-16" %%a in (file) do echo %%~dpabc.txt
或者您的代码可能具有嵌套的FOR循环。像
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dpabc.txt
)
)
甚至是类似的东西
for %%a in (something) do call :sub
exit /b
:sub
for %%p in (somethingelse) do echo %%~dpabc.txt
exit /b
以上所有三个代码示例将打印出%%~dpa
的驱动器和路径,后跟“ bc.txt”。根据文档,FOR变量是全局变量,因此子例程FOR循环的DO子句可以访问%%a
和%%p
。
Aschipfl does a good job documenting the rules for how modifiers and variable letters are parsed。
每当在字符串文字之前使用FOR变量时,都必须格外小心,不能将字符串文字解释为FOR变量扩展的一部分。从您的示例可以看出,这可能很困难。使文字动态化,问题就更糟了。
set /p "myFile=Enter a file name: "
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp%myFile%
)
)
如果用户输入“ abc.txt”,那么我们就回到了起点。但是看一下代码,您可能会遇到潜在问题并不明显。
正如Gerhard和Mofi所说,如果使用的字符不能被解释为修饰符,则可以确保安全。但这并不总是那么容易,尤其是在使用FOR / F返回多个令牌的情况下。
有解决方案!
!!
停止FOR变量解析并延迟扩展如果您查看rules for how cmd.exe parses scripts,您会发现在阶段5发生延迟扩展之前,阶段4已扩展了FOR变量。这为使用!!
作为目标的硬停止提供了机会。 FOR扩展,前提是启用了延迟扩展。
setlocal enableDelayedExpansion
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp!!abc.txt
)
)
%%~dp
在阶段4中正确展开,然后在阶段5 !!
中扩展为空,产生了所需的驱动器号,后跟“ abc.txt”。
但这不能解决所有情况。 !
可以用作FOR变量,但这应该很容易避免,除非在极端情况下。
更麻烦的是必须启用延迟扩展。在这里这不是问题,但是如果FOR变量扩展为包含!
的字符串,则该字符将通过延迟扩展来解析,并且结果很可能会弄乱。
因此,只有在您知道FOR变量值不包含!!
的情况下,!
延迟扩展黑客才能安全使用。
避免所有情况下出现问题的唯一简单的万无一失的方法是将FOR变量的值转移到中间环境变量,然后切换延迟的扩展并使用整个所需的字符串。
for %%a in (something) do (
for %%p in (somethingelse) do (
set "drive=%%~dp"
setlocal enableDelayedExpansion
echo !drive!abc.txt
endlocal
)
)
有一个复杂的防弹解决方案,但是在理解其工作原理之前,它需要大量的背景信息。
cmd.exe命令处理器内部将所有字符串表示为Unicode,环境变量也表示为Unicode-可以使用0x00以外的任何Unicode代码点。这也适用于FOR可变字符。 FOR可变字符的顺序基于Unicode代码点的数字值。
但是从批处理脚本或在命令提示符下键入的cmd.exe代码仅限于活动代码页支持的字符。这似乎是一个死胡同-如果您无法使用代码访问Unicode字符,这有什么用呢?
有一个简单但不直观的解决方案:cmd.exe可以使用包含活动代码页之外的Unicode值的预定义环境变量值!
所有FOR变量修饰符都是前128个Unicode代码点内的ASCII字符。因此,如果您定义名为$ 1到$ n的变量以包含从代码点256(0x100)开始的连续范围的Unicode字符,那么可以确保您的FOR变量永远不会与修饰符混淆。
因此,如果$ 1包含代码点0x100,则将FOR变量称为%%%$1%
。而且,您可以自由使用`%%〜dp%$ 1%之类的修饰符。
此策略的另一个好处是,在解析带有“ tokens = 1-30”之类的令牌范围时,相对容易跟踪FOR变量,因为变量名称本质上是顺序的。活动代码页字符排序通常与Unicode代码点的顺序不匹配,除非您使用Unicode变量hack,否则很难访问所有30个令牌。
现在使用Unicode代码点定义$ n变量并不是一件容易的事。幸运的是,它已经完成了:-)下面的代码演示了如何定义和使用$ n变量。
@echo off
setlocal disableDelayedExpansion
call :defineForChars 1
for /f "tokens=1-16" %%%$1% in (file) do echo %%~d%$16%abc.txt
exit /b
:defineForChars Count
::
:: Defines variables to be used as FOR /F tokens, from $1 to $n, where n = Count*256
:: Also defines $max = Count*256.
:: No other variables are defined or tampered with.
::
:: Once defined, the variables are very useful for parsing lines with many tokens, as
:: the values are guaranteed to be contiguous within the FOR /F mapping scheme.
::
:: For example, you can use $1 as a FOR variable by using %%%$1%.
::
:: FOR /F "TOKENS=1-31" %%%$1% IN (....) DO ...
::
:: %%%$1% = token 1, %%%$2% = token 2, ... %%%$31% = token 31
::
:: This routine never uses SETLOCAL, and works regardless whether delayed expansion
:: is enabled or disabled.
::
:: Three temporary files are created and deleted in the %TEMP% folder, and the active
:: code page is temporarily set to 65001, and then restored to the starting value
:: before returning. Once defined, the $n variables can be used with any code page.
::
for /f "tokens=2 delims=:." %%P in ('chcp') do call :DefineForCharsInternal %1
exit /b
:defineForCharsInternal
set /a $max=%1*256
>"%temp%\forVariables.%~1.hex.txt" (
echo FF FE
for %%H in (
"0 1 2 3 4 5 6 7 8 9 A B C D E F"
) do for /l %%N in (1 1 %~1) do for %%A in (%%~H) do for %%B in (%%~H) do (
echo %%A%%B 0%%N 0D 00 0A 00
)
)
>nul certutil.exe -decodehex -f "%temp%\forVariables.%~1.hex.txt" "%temp%\forVariables.%~1.utf-16le.bom.txt"
>nul chcp 65001
>"%temp%\forVariables.%~1.utf8.txt" type "%temp%\forVariables.%~1.utf-16le.bom.txt"
<"%temp%\forVariables.%~1.utf8.txt" (for /l %%N in (1 1 %$max%) do set /p "$%%N=")
for %%. in (dummy) do >nul chcp %%P
del "%temp%\forVariables.%~1.*.txt"
exit /b
:defineForChars
例程是在DosTips上开发的,是easily access many tokens with a FOR /F statement更大的团队努力的一部分。
该线程的以下帖子介绍了:defineForChars
例程和变体:
答案 1 :(得分:3)
此行为是由for
变量引用及其~
修饰符的解析的贪婪性质引起的。鉴于已经检测到前面的%
/ %%
符号,基本上遵循这些规则:
~
;如果是,则:
fdpnxsatz
中尽可能多地使用以下字符(每个字符甚至多次),这些字符位于定义for
变量引用或{{1}的字符之前}-标志;如果遇到这样的$
符号,则:
$
;如果找到,则:
:
之后有一个字符,请将其用作:
变量引用并按预期方式扩展,除非未定义,否则不要扩展; for
是最后一个字符,则 :
将崩溃! cmd.exe
)不会展开任何内容; :
-符号)使用所有修饰符扩展$
变量; for
)将下一个字符用作~
变量引用并扩展,除非未定义该变量,或者甚至没有跟随的字符,则不要扩展; 答案 2 :(得分:1)
正如for
meta-variable parsing rules中已经解释的那样,~
修饰符检测以贪婪的方式发生。但是您可以停止通过另一个for
元变量(最终会扩展为空)或jeb在comment中建议的~$
修饰符进行解析,甚至不需要另一个for
元变量,因此可以使用任何现有的元变量:
rem // Using `%%~#` will expand to an empty string (note that `#` is not a valid `~`-modifier):
for %%# in ("") do (
rem // Establish a `for`-loop that defines meta-variables `%%a` to `%%p`:
for /F "tokens=1-16" %%a in ("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16") do (
rem /* Since `d`, `p` and `a` are all valid `~`-modifiers and a `for` meta-variable
rem `%%b` exists while `b` is not a valid `~`-modifier, `%%~dpab` is expanded: */
echo(%%~dpabc.txt
rem /* `for` meta-variable parsing is stopped after `%%~dp`, because the following `%`
rem is not a valid `~`-modifier, and neither exists a `for` meta-variable named `%`;
rem `%%~#` is expanded to an empty sting then (parsing surely stops at `#`): */
echo(%%~dp%%~#abc.txt
rem /* The following does not even require a particular `for` meta-variable like `%%#`,
rem it just uses the existing one `%%p` with the `~$`-modifier that specifies an
rem environment variable; since there is no variable name in between `$` and `:`,
rem there is certainly no such variable (since they must have names), hence `$~%:p`
rem expands to an empty string; note that `~$` is always the very last modifier: */
echo(%%~dp%%~$:pabc.txt
)
)
请注意,如果存在名为for
的{{1}}元变量(这种情况不太常见,但有可能),这种方法将失败。