中国DOS联盟

-- 联合DOS 推动DOS 发展DOS --

联盟域名:www.cn-dos.net 论坛域名:www.cn-dos.net/forum
DOS,代表着自由开放与发展,我们努力起来,学习FreeDOS和Linux的自由开放与GNU精神,共同创造和发展美好的自由与GNU GPL世界吧!

中国DOS联盟论坛
现在时间是 2026-06-14 20:57
中国DOS联盟论坛 » DOS批处理 & 脚本技术(批处理室) » 简析环境变量和变量延迟特殊字符以及中介法的微妙关系
楼 主 简析环境变量和变量延迟特殊字符以及中介法的微妙关系 发表于 2007-05-23 15:52 ·  中国 浙江 杭州 华数宽带
银牌会员
★★★
积分 2,000
发帖 621
注册 2007-01-01 00:00
UID 75212
性别 男
状态 离线
已作修改
本文主要以例子展开了讨论:
对每个例子的结果进行分析;并揭示其中的一些现象;
例一:

@echo off
set "var=kljlk!tsd!21%mk%gd"
set var

结果为

var=kljlk!tsd!21gd

(注意到 此时setlocal默认为disabledelayedexpansion !!不被识别)
因此把%mk%看做环境变量被替换掉;而mk没有被定义因此%mk%被替换为空

再来看加了setlocal enabledelayedexpansion后会是怎样;
例二:

@echo off&setlocal enabledelayedexpansion
set "var=kljlk!tsd!21%mk%gd"
echo.!var!

结果为

kljlk21gd

注意到 此时setlocal声明为enabledelayedexpansion
因此把!tsd!和%mk%均被看做环境变量而被替换掉;而tsd和mk没有被定义因此均被替换为空
(由于优先级的不同;%mk%将在预处理被先被替换,然后!tsd!被替换;原因后面有讲到)


如果把kljlk!tsd!21%mk%gd放到t.txt然后
再通过%%a做为中介进行传递后会有什么现象呢
例三:

@echo off
for /f "delims=" %%a in (t.txt) do (
set "var=%%a"
set var
)

结果为:

var=kljlk!tsd!21%mk%gd


如果按照上面所说的此时%mk%应该被替换为空;
但是这里经过一个%%a的中介;此时的结果不会把%mk%替换;即使mk环境变量有定义也不会替换掉;而是按字面输出;

实际上这跟cmd的预处理机制有关;

当CMD读取for语句时,他的所有语句将一同被读取,并完成预处理工作,这其中就包括环境变量%%的扩展;

因为字符串在文本中 并没有以字符串的形式出现在 for语句中;因此其中的%%躲过了预处理机制;

但是当启用延迟环境变量后
例如:
例四

@echo off
for /f "delims=" %%a in (t.txt) do (
set "var=%%a"
set var
)

结果为:

var=kljlk21%mk%gd

首先他会和上面一样对for所有语句一起读取;完成对%%的扩展;(%%的优先级高)
然后当CMD读取了一条完整的语句之后,
它不会立即执行变量的扩展行为,而会在某个单条语句执行之前再进行扩展,也就是说,这个扩展行为被“延迟”了
例如在set "var=%%a"时 只有执行到这条命令前才会对这条命令进行扩展;
而此时%%a已经被kljlk!tsd!21%mk%gd所代替;因此将会对其中的!!进行扩展.
他会识别其中是否存在!;此时的扩展只处理!!而不处理%%(因为对%%的处理过程在先前已经进行完;而那时因为字符串在文本中 并没有以字符串的形式出现在 for语句中;因此其中的%%躲过了预处理机制;);


然而只有当该字符串中含有!时, cmd才会对该字符串进行再次处理;
举例:
例五:

@echo off&setlocal enabledelayedexpansion
for /f "delims=" %%a in (t.txt) do (
set "var=%%a"
set var
)

t.txt的内容

kl&>jl^k!tsd!21%mk%gd
kl&>jlk^!tsd^!21%mk%gd
dgssdgdg^gds^gdsa

结果为:

var=kl&>jlk21%mk%gd
var=kl&>jlk!tsd!21%mk%gd
var=dgssdgdg^gds^gdsa

注意到 第一行的 ^ 已经不存在了;(所在行包含!!)
而第三行的^没有被处理;(所在行不包含!!)

(包含一个!也会对^进行处理;)
由此可见 启用延迟环境变量后 ;
在每次执行语句前;
cmd会检查是否含有!;
如果存在就对其进行必要的预处理后再执行.


注:此时setlocal默认为disabledelayedexpansion因此!tsd!是因为不给识别而不被替换和%mk%不被替换的原因不同.
如果想强迫进行二次转变(即把%mk%的值按其环境变量值警醒替换)再赋值给var可以使用 call set "var=%%a"
也就是说:
例六:

@echo off
set "mk=152"
for /f "delims=" %%a in (t.txt) do (
call set "var=%%a"
set var
)

的结果则为:

var=kljlk!tsd!21152gd


call set "var=%%a"
实际上是让命令解释器再来一次预处理来达到目的的


再来看%%a另一个例子
例七:

@echo off
echo ^&*&^^&%$%^%$#$1213<>:Lksgsd

结果为:

&*
'^' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
'Lksgsd' 不是内部或外部命令,也不是可运行的程序
或批处理文件。


大家都知道这是因为里面有特殊字符;
但是看下面的例子;把^&*&^^&%$%^%$#$1213<>:Lksgsd放到t.txt中;用%%a做为中介后:
例七:

@echo off
for /f "delims=" %%a in (t.txt) do (
echo.%%a
)

结果则为:

^&*&^^&%$%^%$#$1213<>:Lksgsd


所有的都是原样输出;丝毫没有因为特殊字符而受影响;(原因上面已讲到)
注:如果t.txt中有直接回车的空行则会跳过;原因在for本身而不是%%a;


再来个!var!的特殊例子
例八:

@echo off
set "var=^&*&^^&$^$#$1213<>Lksgsd"
echo.%var%

结果为:

此时不应有 >。

结果出错

做如下的修改
例九:

@echo off&setlocal enabledelayedexpansion
set "var=^&*&^^&$^$#$1213<>Lksgsd"
echo.!var!

结果为:

^&*&^^&$^$#$1213<>Lksgsd

结果正确;
但是此种方法仍然受上面所述的原因影响;
比如如果var=后面的值含有!!和%%等则会发生替换;还有比如中间有:则会与set发生关系而导致结果不准确;
当然可以利用%%a中介可以避免 %% 被替换;但是!!却有上述原因没法逃过被替换;



最后给出个综合应用的例子:
对文件指定行进行输出;不论是否含有特殊字符:
这里就把整个文件输出;对指定行可以修改为 findstr /n .* test.txt^|findstr "^10:" 等;


@echo off
for /f "delims=" %%a in ('findstr /n .* test.txt') do (
set "var=%%a"
setlocal enabledelayedexpansion
set var=!var:*:=!
echo.!var!
endlocal
)

短短的几行代码包含了很多技巧;

测试文本:
test.txt

"aou"eo
;euou%^>
::::aeui

:::E2uo alejou 3<o2io|
^aue||%ou

aoue eou 2
euo 8
ege#6758!7^^9098!98%$&^
ueyi^^^^aueuo2
~ ! @ # $ % ^ & * ( () " ok " No " <>nul
ege#6758^^^!7^^^^9098^!98%$&^^

~ ! @ # $ %" ^ "& * ( () " ok " No " <>nul
sdsdg:sadgs


并没有经过大量的测试;诸位帮忙测试;

[ Last edited by bjsh on 2007-5-27 at 12:03 PM ]
本帖最近评分记录 (共 4 条) 点击查看详情
评分人分数时间
qzwqzw +9 2007-05-27 16:19
stornager +3 2007-06-11 16:05
huahua0919 +4 2007-12-17 19:41
tireless +15 2009-03-17 23:31
2 发表于 2007-05-24 07:54 ·  中国 广东 电信
荣誉版主
★★★★
batch fan
积分 5,226
发帖 1,737
注册 2006-03-10 00:38
UID 51697
来自 成都
状态 离线
  测试了不少内容,并把这个过程转化为一篇长帖,佩服你的耐心。

  我觉得,一切的一切,应该和CMD的预处理机制有关,可惜水平有限,难以做深入的分析——这个时候,习惯性地又想起了willsort,不知道何年何月才能又看到他那深入本质的分析。
尺有所短,寸有所长,学好CMD没商量。
考虑问题复杂化,解决问题简洁化。
3 发表于 2007-05-24 08:48 ·  中国 湖北 武汉 电信
版主
★★★★★
积分 11,386
发帖 4,938
注册 2006-07-23 17:10
UID 59080
状态 离线
bjsh 真是很有耐心,学习。
4 服了! 发表于 2007-05-24 09:12 ·  中国 河北 秦皇岛 电信
初级用户
积分 68
发帖 32
注册 2007-04-01 12:12
UID 83621
性别 男
状态 离线
bjsh版主太厉害了! 我以前不太了解那个setlocal enabledelayedexpansion 到底有什么用,现在知道了不少了!而且最重要的是他们之间详细的关联,很受益!
我还从中学到了 echo 和 echo. 的区别,虽然不是很清楚!
还有那个findstr /n .* test.txt ,"."后面有*和没有*的区别,
非常感谢!!
5 发表于 2007-05-25 14:27 ·  中国 辽宁 大连 沙河口区 联通
初级用户
积分 70
发帖 38
注册 2007-03-24 09:25
UID 82762
性别 男
状态 离线
向楼主学习~~~
6 发表于 2007-05-25 14:39 ·  中国 广东 广州 鹏博士宽带
初级用户
★★
积分 176
发帖 78
注册 2007-04-15 10:53
UID 85268
性别 男
状态 离线
喜欢看深入的分析。收藏。
7 发表于 2007-05-27 00:52 ·  中国 江西 南昌 电信
银牌会员
★★★
天的白色影子
积分 2,343
发帖 636
注册 2004-03-06 00:00
UID 19350
性别 男
状态 离线
Originally posted by bjsh at 2007-5-23 15:52:
for会自动给每个特殊字符加上转义符号;


请恕冒昧
我感觉这一句很有问题
for不会为特殊字符加转义符号
这样会额外增加它的预处理负担

对于例3的结果分析
应该是因为"kljlk!tsd!21%mk%gd"这个串没有被预处理
因为只有预处理过程才会处理这些特殊字符

也就是说
这个串因为藏在文件中
“躲过了”cmd的预处理机制审查
所以得以全身

至于例5的结果分析
那是因为对!!的分析和其它字符不属于同一阶段的预处理范畴
也就是说
对%"这些是初审
而对!则是复审
过的了初审的!
未必过的了复审
全看命令延迟扩展这根指针的指向呢

总的来说
因为cmd处理所有字符都是有先有后
所以必然会产生所谓优先级的问题
比如%的优先级会很高
因为它很早就被转义解释了
"和^都不会对它发生作用
8 发表于 2007-05-27 10:22 ·  中国 浙江 杭州 华数宽带
银牌会员
★★★
积分 2,000
发帖 621
注册 2007-01-01 00:00
UID 75212
性别 男
状态 离线
是的;
确实是我理解有误; 已经修改
多谢qzwqzw兄的指点;

当CMD读取for语句时,他的所有语句将一同被读取,并完成预处理工作,这其中就包括环境变量%%的扩展;

因为字符串在文本中 并没有以字符串的形式出现在 for语句中;因此其中的%%躲过了预处理机制;

启用延迟环境变量后
首先他会和上面一样对for所有语句一起读取;完成对%%的扩展;
然后当CMD读取了一条完整的语句之后,他会识别其中是否存在!!;此时的扩展只识别!!而不识别%%;
只用当该字符串中含有!!, cmd才会对该字符串进行再次处理;
举例:

@echo off&setlocal enabledelayedexpansion
for /f "delims=" %%a in (t.txt) do (
set "var=%%a"
set var
)

t.txt的内容为

kl&>jl^k!tsd!21%mk%gd
kl&>jlk^!tsd^!21%mk%gd
dgssdgdg^gds^gdsa


结果为:

var=kl&>jlk21%mk%gd
kl&>jlk!tsd!21%mk%gd
var=dgssdgdg^gds^gdsa


注意到 第一行的 ^ 已经不存在了;
而第三行的^没有被处理;

由此可见 启用延迟环境变量后 ;
在每次执行语句前;
cmd会检查是否含有!!;
如果存在就对其进行必要的预处理后再执行.

[ Last edited by bjsh on 2007-5-27 at 10:58 AM ]
9 发表于 2007-05-27 16:48 ·  中国 江苏 连云港 联通
高级用户
★★★
前进者
积分 641
发帖 303
注册 2007-01-10 02:57
UID 76009
性别 男
状态 离线
佩服啊,我就没有这种耐心,有的时候测试最多只能做几分钟。
我相信总有一天,总会遇到一个人可以相濡以沫、相吻以湿!
10 发表于 2007-05-28 12:29 ·  中国 广东 深圳 电信
初级用户
★★
积分 135
发帖 61
注册 2007-04-14 09:23
UID 85115
性别 男
状态 离线
不错。。收藏学习。。
11 发表于 2007-09-03 10:57 ·  中国 云南 西双版纳傣族自治州 电信
初级用户
★★
积分 100
发帖 52
注册 2006-03-02 15:06
UID 51231
来自 云南
状态 离线
谢了
12 发表于 2007-09-03 20:24 ·  中国 湖南 长沙 电信
新手上路
积分 17
发帖 9
注册 2007-09-03 11:58
UID 96447
性别 男
状态 离线
看了受益不少
13 发表于 2007-09-29 15:05 ·  中国 浙江 宁波 电信
初级用户
积分 87
发帖 42
注册 2007-09-27 19:25
UID 98424
性别 男
状态 离线
这么好的帖子,不顶还是人吗?
14 发表于 2007-09-29 15:05 ·  中国 浙江 宁波 电信
初级用户
积分 87
发帖 42
注册 2007-09-27 19:25
UID 98424
性别 男
状态 离线
向版主看齐
15 发表于 2007-09-29 15:22 ·  中国 浙江 杭州 联通
新手上路
积分 2
发帖 1
注册 2006-03-24 15:52
UID 52721
状态 离线
let me see...
论坛跳转: