最大限度原样输出含特殊字符的指定行内容+简析环境变量和变量延迟特殊字符以及中介法的微妙关系

http://cndos.fam.cx/forum/viewthread.php?tid=30815

在CMD中,对含特殊字符的文本内容的输出处理一直是件很令人头痛的事情:如果要兼容特殊字符,一般会用引号把内容括起来再输出,但是这样一来,就会在所有输出行的首尾都添加了引号,如果对输出后的引号十分在意的话,这个方案就没法实行了。可是,除了这个方案之外,似乎没有别的方案能完美地解决这个难题。(注:完美方案请参考23楼bjsh的代码)


  最近这段时间略有闲暇,把这个问题又拿出来思考了一阵子,几经修改,就有了代码1,发出来让大家测试一下:

代码1:

@echo off
 :: 思路:把所有的特殊符号转义之后输出
 :: 所受限制:要处理的文件不能用引号括起来;
 cd.>output.txt
 for /f "delims=" %%i in ('findstr /n .* test.txt') do (
     set "str=%%i"
     call set "str=%%str:*:=%%"
     if defined str (call :output) else echo.>>output.txt
 )
 start output.txt
 exit

 :output
 set "str=%str:^=^^%"
 set "str=%str:>=^>%"
 set "str=%str:<=^<%"
 set "str=%str:|=^|%"
 set "str=%str:&=^&%"
 set "str=%str:"=^"%"
 call echo.%%str%%>>output.txt
 goto :eof
修改自21楼bjsh的代码如下,个人认为是比较完美的方案了:

  代码2:
@echo off
 cd.>output.txt
 for /f "delims=" %%i in ('findstr /n .* test.txt') do (
     set "str=%%i"
     call set "str=%%str:*:=%%"
     if defined str (call :output) else echo.>>output.txt
 )
 start output.txt
 exit

 :output
 set "str=%str:^=^^%"
 set "str=%str:"=%"
 set "str=%str:>=^>%"
 set "str=%str:<=^<%"
 set "str=%str:&=^&%"
 set "str=%str:|=^|%"
 set "str=%str:="%"
 (call echo.%%str%%)>>output.txt
 goto :eof

最完美的代码如下(来自23楼bjsh的代码,本人仅作少量改动):

  代码3:
@echo off
 cd.>output.txt
 for /f "delims=" %%i in ('findstr /n .* test.txt') do (
         set "var=%%i"
         setlocal enabledelayedexpansion
         set var=!var:*:=!
         (echo.!var!)>>output.txt
         endlocal
 )
 start output.txt

测试文件test.txt的内容(请注意:其中一个空行是以空格组成的):
"aou"eo

 ;euou%^>
 ::::aeui
    
 :::E2uo alejou 3<o2io|
 ^aue||%ou

 !aue!
 aoue eou 2
 !str!auoeu!ueo &&
 euo 8
 ueyi^^^^aueuo2
 ~ ! @ # $ % ^ & * ( () " ok " No " <>nul
 set ok=^

关于代码1及代码2,分析如下:

  ① 按照一般的思路,for语句中引用变量,都是使用 setlocal enabledelayedexpansion 语句来启用变量延迟功能,但是,这个功能有个致命的缺陷:当要处理的字符串中含有感叹号的时候,会把感叹号对及其之间的所有字符串置换为空,所以,代码1和代码2抛弃 setlocal 方案,使用 call 一段子过程的方案;
  ② 如果要处理的文本含有奇数个引号的话,echo.%str%>>output.txt 语句将会出错,所以直接把引号替换为特殊的不可见字符之后再输出(也就是在代码中显示出来的黑框);感谢lxmxn的测试和bjsh的分析;
  ③ 在 :output 子过程中,set "str=%str:^=^^%" 一句必须放在所有替换语句之前,否则,将会把^重复替换,导致结果不准确;感谢 lxmxn 的测试;
  ④ 普通的 for 语句会忽略以分号打头的行内容,对空行也会忽略掉,所以,使用 findstr .* test.txt 语句来显示所有行(包括空行);delims=: 会把行首的所有冒号抛弃,所以,使用了 call set "str=%%str:*:=%%" 语句来避免这种情况;
  ⑤ (call echo.%%str%%)>>output.txt 语句中echo后紧跟的点号不能省略,否则,当行内容为空格的时候,输出后的内容将会显示echo的当前状态;使用call语句是为了兼容带引号的行;使用括号是为了能正确处理行尾是以空格分隔的单独的1~9这九个数字。
  ⑥ :output 标签段之所以不用 for %%i in (^^ ^> ^< ^| ^&) do call set "str=%%str:%%i=^%%i%%" 这样的语句,是因为这条替换语句并不能正确替换,看来for语句中的 call 延迟机制确实有点让人费解。

  关于代码3,由于水平有限,只能做点肤浅而模糊的分析:在这段代码中,利用变量延迟功能来完整地获取特殊字符,并在适当的时候终止变量延迟,以避免因变量延迟过度而造成字符串被识别为变量的问题,实际上,这还是CMD预处理机制在起作用。值得注意的是,setlocal 语句的位置不能与 set 语句做调换,否则,仍然会导致感叹号被识别为变量引用符号,从而被抛弃掉。


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


本文主要以例子展开了讨论: 对每个例子的结果进行分析;并揭示其中的一些现象; 例一:

@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

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值