Board logo

标题: [讨论]最大限度原样输出含特殊字符的指定行内容 [打印本页]

作者: namejm     时间: 2007-5-17 21:14    标题: [讨论]最大限度原样输出含特殊字符的指定行内容

  在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 语句做调换,否则,仍然会导致感叹号被识别为变量引用符号,从而被抛弃掉。

[ Last edited by namejm on 2007-8-14 at 09:27 PM ]
作者: lxmxn     时间: 2007-5-17 22:44
感谢 namejm 兄的原创,在工作这么忙的情况下还可以顾及论坛,实感欣慰。

经过短暂的测试发现没有任何输出错误。

[ Last edited by lxmxn on 2007-5-17 at 11:16 PM ]
作者: ieutk     时间: 2007-5-17 23:25
好东东,可惜看不懂啊!
作者: zh159     时间: 2007-5-18 00:11
除了引号,其他的其实都好办
作者: bjsh     时间: 2007-5-18 08:54
以前也写过类似的:
和namejm兄的差不多;
不过没考虑过某一行都是空格的情况;
最后一句是抄namejm的;
echo.%var%的用法实在妙极.
@echo off
for /f "tokens=1 delims=" %%a in ('findstr /n .* test.txt') do set "var=%%a" & call :change
goto :eof
:change
set "var=%var:^=^^%"
set "var=%var:>=^>%"
set "var=%var:<=^<%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:*:=%"
echo.%var%
这个解决了"问题;
诸位也帮忙测试下;
作者: lxmxn     时间: 2007-5-18 12:36
To bjsh:

测试发现点小问题,请看下面我的测试文本。

test.txt

  Quote:
~ ! @ # $ % ^ & * ( () " ok " No " <>nul
set ok=^

屏幕显示

  Quote:
此时不应有 >。


作者: bjsh     时间: 2007-5-18 12:53
唉;三个引号的问题啊;
就好像
echo fsdg"dsgsa"gds" >55.txt
不会写到55.txt里一样;
我在想想吧;
难道真得非写成不可见字符.
作者: ttyp     时间: 2007-5-18 13:07
结果不对啊

aoueo

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

!aue!
!str!auoeu!ueo &&
ueyi^^^^aueuo2
=^

第一行“变成乱码,多了最后一行
作者: lxmxn     时间: 2007-5-18 13:15
To ttyp:

第一行的 " 是楼主故意变成那个特殊字符的。

你的测试文本是不是复制楼主的测试文本啊?

我这里测试完全正常。
作者: ttyp     时间: 2007-5-18 13:21
是复制的,第一行开始没看清楚。
但是最后多一行很奇怪
我的系统是W2K+SP4
作者: namejm     时间: 2007-5-18 13:26


  Quote:
Originally posted by bjsh at 2007-5-18 12:53:
唉;三个引号的问题啊;
就好像
echo fsdg"dsgsa"gds" >55.txt
不会写到55.txt里一样;
我在想想吧;
难道真得非写成不可见字符.

  经过测试,应该是引号为奇数个的时候才会有这种情况,这是 call 语句调用子过程的时候 echo 语句的一大缺陷,可能只有替换为其他字符才行。

  顶楼的代码已经精简了部分语句,set "str=%~1" 完全没有必要存在,呵呵,我的思路复杂过头了。

[ Last edited by namejm on 2007-5-18 at 01:47 PM ]
作者: ttyp     时间: 2007-5-18 13:27
哦,明白了,是我一直在测试P处理,注释了EXIT导致的
作者: bjsh     时间: 2007-5-18 13:49


  Quote:
Originally posted by namejm at 2007-5-18 01:26 PM:

  经过测试,应该是引号为奇数个的时候才会有这种情况,这是 call 语句调用子过程的时候 echo 语句的一大缺陷,可能只有替换为其他字符才行。 ...

是的;是奇数个的时候这样;
echo dsg^"sdgsad^"gdgasdg^" >5.txt
就可以了;

其实"也算特殊字符了;

重新修改一下
@echo off
set var=
for /f "delims=" %%a in ('findstr /n .* test.txt') do (
set "var=%%a"
call set "var=%%var:"=%%"
call :change
)
goto :eof
:change
set "var=%var:^=^^%"
set "var=%var:>=^>%"
set "var=%var:<=^<%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:*:=%"
if not defined var echo. & goto :eof
call set "var=%%var:="%%"
echo.%var%
再测试看还有什么问题
作者: bjsh     时间: 2007-5-18 14:08


  Quote:
c:\>set "var=~ ! @ # $ % ^ & * ( () " ok " No " <>nul"
此时不应有 >。



  Quote:
c:\>for /f "delims=" %a in (test.txt) do set "var=%a"

c:\>set "var=~ ! @ # $ % ^ & * ( () " ok " No " <>nul"
c:\>set var
var=~ ! @ # $ % ^ & * ( () " ok " No " <>nul

test.txt内容

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

发现两者的不同了吗?
前者 显示 此时不应有 >
而后者只是利用for把那段字符转换为%a再赋值给var;就不会报错了;
有意思啊;
作者: namejm     时间: 2007-5-18 14:09
  当引号为奇数个的时候,echo的结果不会有什么错误,但是,当引号个数为偶数个的时候,就会出错了。解决的办法是 echo.%var% 改为 call echo.%%var%%,但是这样一来,如果测试内容为 ~ ! @ # $ % ^ & * ( () " ok " No " a>a 的时候,会把最后一个引号之后的所有内容都抛弃掉,原因暂时不明。

————————————————————————————————
  以上言论属于使用的测试代码有误,导致结论出错。


[ Last edited by namejm on 2007-5-18 at 02:53 PM ]
作者: dikex     时间: 2007-5-18 14:13
这个可否??紧限于输出

type test.txt>output.txt
作者: bjsh     时间: 2007-5-18 14:18
?
namejm上面的帖子针对的是哪个帖子说的?

我觉得当引号个数为奇数是就会把 echo后所有的东西全部输出;
因此无法重定向;
我认为解决方法为给每个引号加转义;

对于偶数;觉得不会出错
作者: namejm     时间: 2007-5-18 14:23
  呵呵,我使用的代码不是你的版本,说法出错了,我收回这个说法。
作者: namejm     时间: 2007-5-18 14:27
  不知不觉中,bjsh已经把所有的特殊字符的输出问题都解决完了,剩下的事情就是效率的提升和代码的精简/规范问题了。
作者: namejm     时间: 2007-5-18 14:29


  Quote:
Originally posted by dikex at 2007-5-18 14:13:
这个可否??紧限于输出

type test.txt>output.txt

  type方案仅限于整个文本的输出,而我想讨论的是对指定行内容里的所有特殊字符的原样输出,我的标题有点误导人了,这就改改。
作者: bjsh     时间: 2007-5-18 14:44
重定向到文本中的代码
@echo off
if exist tmp.txt del tmp.txt
set var=
for /f "delims=" %%a in ('findstr /n .* test.txt') do (
set "var=%%a"
call set "var=%%var:"=%%"
call :change
)
goto :eof
:change
set "var=%var:^=^^%"
set "var=%var:>=^>%"
set "var=%var:<=^<%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:*:=%"
if not defined var echo.>>tmp.txt & goto :eof
call set "var=%%var:="%%"
call echo.%%var%%>>tmp.txt
注释:
call set "var=%%var:"=%%"
中的:"=%%"里的"空格"不是空格是ctrl+BackSpace的特殊字符;

[ Last edited by bjsh on 2007-5-18 at 02:47 PM ]
作者: vkill     时间: 2007-5-22 21:00
我记得关于这个在无奈何签名的那个帖子中已经有一个很好的方案了吧!
作者: bjsh     时间: 2007-5-23 15:56
最新代码:
简练且效率高
@echo off
for /f "delims=" %%a in ('findstr /n .* test.txt') do (
        set "var=%%a"
        setlocal enabledelayedexpansion
        set var=!var:*:=!
        echo.!var!
        endlocal
)
有兴趣的帮忙测试
作者: namejm     时间: 2007-5-23 22:41
  这可能是最简洁高效的代码了,变量延迟大有文章可作啊。
作者: bjsh     时间: 2007-5-23 22:56
简析环境变量和变量延迟特殊字符以及中介法的微妙关系

是写上面代码的后感..

[ Last edited by bjsh on 2007-11-14 at 12:11 AM ]
作者: zjl5     时间: 2007-10-5 05:00
不知道效果怎么样..
@echo off
for /f "eol= delims=" %%i in (a.txt) do (
set type=%%i
for /l %%j in (0,1,100) do (
setlocal EnableDelayedExpansion
if "!type:~%%j,1!"==" " (set/p= <nul)
if "!type:~%%j,1!"=="=" (set/p= <nul&set/p==<nul) else (
call set/p="%%type:~%%j,1%%"<nul)
endlocal)
echo.
)
pause>nul

作者: zzs162     时间: 2008-4-25 05:23
呵呵`不错`佩服``
作者: radem     时间: 2008-6-22 00:27
受益非浅!
但还有一问题:怎样用P修改含特殊符号的文本,如:
将test.txt
ip 125.219.59.216

sv_contact  "568ggstart@163.Com"
// 服务器构建者的联系邮箱

sv_send_resources 1
// 自动向客户端传送地图关联的 *.res文件里包括的资源文件

sv_maxrate 25000
// 服务器最大传输速率 <0-25000>


改为:
ip 59.32.211.105

sv_contact  "568ggstart@163.Com"
// 服务器构建者的联系邮箱

sv_send_resources 1
// 自动向客户端传送地图关联的 *.res文件里包括的资源文件

sv_maxrate 25000
// 服务器最大传输速率 <0-25000>

作者: silanger     时间: 2008-9-16 20:57
太高深了,光看不顶说不过去,顶一下又恐说我外行凑热闹。还是顶了吧
作者: laihaibin08     时间: 2008-10-19 17:25
我们几位版主真是太有才了
作者: different     时间: 2008-12-23 01:30


  Quote:
Originally posted by namejm at 2007-5-17 21:14:
  最完美的代码如下(来自23楼bjsh的代码,本人仅作少量改动):

  代码3:
CODE:  [Copy to clipboard]
--------------------------------------------------------------------------------

@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

学习了,运行时报错,不过输出的txt却是正确的!

[ Last edited by different on 2008-12-23 at 01:43 ]
作者: bjjgq     时间: 2009-12-3 21:14
精彩呀,我想补充点,不知道对不对
findstr /n .* test.txt是要找到所有的行,并在其前加上行号如“1:”
所以在后面的循环中才会出现这样的一句:
set var=!var:*:=! 来删掉如“1:”这样的一批行号
如有不对之处请指正!
作者: icebat     时间: 2009-12-9 19:03
两个字, 佩服,   23 楼,  日哦。。。