批处理之家's Archiver

batman 发表于 2011-3-1 23:09

[分享]批处理多行回退(规律讨论篇)

全文由本人同步发表在个人qq空间:[url=http://user.qzone.qq.com/841615149/infocenter]http://user.qzone.qq.com/841615149/infocenter[/url]
  还记得刚刚接触批处理时很喜欢写特效,看着自己编写的代码在dos窗口中变成一幅幅跳动的画面时,心里那个成就感啊。。。可是,慢慢就发现用批写特效真的是很费劲,受到的限制太多了。其中最受不了的就是动画的实现基本靠cls刷屏重输出,整个画面闪动的厉害,一点都不连贯,这些都是因为dos的逐行输出性质决定的。但值得庆幸的是微软总算还是创造了一个神奇的退格符“”,用它和空格组合可以将最后行进行重输出而不用cls来刷屏,如下代码:[code]
@echo off&setlocal enabledelayedexpansion
set "str=  我在向不断向左移动哦"
for /l %%a in (1,1,24) do set "t=!t! "
echo 请注意下面文字的变化&echo.&set /p===^><nul
:lp
for /l %%a in (1,1,11) do (
     set /a a=6*%%a
     for %%b in (!a!) do set "t1=!t:~%%b!"
     set /p=!str:~%%a!<nul
     for /l %%b in (1,1,2000) do echo>nul
     set /p=!t1!<nul
)
goto lp[/code]画面总算不闪而且流畅了,这都是神奇的“”的功劳啊。那么它是怎么实现这一效果的呢?“”的学名叫做退格符,但它这个退格是将光标(即输出点)退回一格,请记住它并不会删除前面的输出内容。但它和空格的组合“  ”就能成功将前一格的内容删除并将光标回退一格(大家想一想为什么能够实现)。在这里,我还要提醒大家退格每次只回退一个字节的距离,这个距离相当于单字节字符(数字、字母、字符)的宽度以及双字节字符(汉字、标点)的一半宽度。所以我们只要知道要回退删除的距离为n(以单字节计算),就可以用n个“  ”组合来实现精确的回退删除,如果要重输出整行,我们就可以采用足够多的“  ”组合来实现(而不用知道要回退的距离)。但是这种方法仅仅只能对末行进行重输出,如果要对末行以上的行进行重输出就只能用cls刷屏重输出了。当然,我们还可以用将一帧帧的画面先输出到临时文本中,cls后再用type对临时文本进行输出,如此来实现对画面的重写,而这个要比单单cls刷屏重写要显得流畅些(因为无论是多少内容,type都是一次性输出):[code]@echo off&setlocal enabledelayedexpansion
set "str=我是一帧帧的画面,连贯起来就成了动画"
for /l %%a in (0,1,17) do (
     echo !str:~%%a,1!>tmp
     type tmp
     for /l %%b in (1,1,2000) do echo>nul
     cls
)
del /q tmp&pause[/code]但是,当输出的内容很多时,用type方法效率上就有问题了。而且很多时候我们仅仅是对画面的局部进行变化,如果每次要输出整个画面确实是一种效率上的浪费(个人认为)。那么,还有没有别的办法来实现对画面局部变化呢?前面我特意提到神奇的“”,如果仅仅局限在一行内实现回退是称不上神奇的。那么它真正的神奇之处在哪里呢?一个偶然的机会,本人发现用echo输出一个tab+n个退格符就能将光标回退到原点,注意这个n是足够多,本人当时取的是1000,就在论坛发表了以下代码:[code]@echo off&setlocal enabledelayedexpansion
:: 灵感来源于cn-dos趣味东的多行回退
:: 趣味东发现用set /p输出一个tab+n个退格就将光标回退多行,但并不好控制
:: 于是本人就想到是不是能将光标回退到原点(屏幕左上角)
:: 结果发现用echo输出一个tab+n个退格就能将光标退回到原点过一格的位置,但有错误信息输出
:: 于是,用2>nul屏蔽错误信息,并再加一个退格将光标退回原点
:: 下面通过代码进行简单演示,至于原因本人暂未搞明白
::请将下面的tab换成实际的制表符
set "t=tab"
title 神奇的回退
for /l %%a in (1,1,1000) do set "k=!k!"
for /l %%a in (1,1,10) do echo ○○○○○○○○○○
ping /n 2 127.1>nul
:: 这里将光标退回原点处,请仔细注意光标的位置
echo %t%%k% 2>nul&set /p=<NUL
pause>nul
:: 请注意6-10行第6-10个字符的输出是没有改变的
for /l %%a in (1,1,5) do echo ●●●●●¤¤¤¤¤
for /l %%a in (1,1,5) do echo ⊙⊙⊙⊙⊙
pause>nul[/code]这确实是一个重大的突破,但是按这个方法将光标退回到原点后再用echo,方法可以将光标重定位到要修改的行的行首(仅能到定位到行首),就能方便地实现画面的局部修改。但本人在一次测试时(当时是输出的150行)发现光标没有按计划退到原点,而是只是回退了99行。这下看来这个光标回退行是有规律的,于是,本人写下了下面的测试代码来寻找这一规律(当时猜想应该是个10*行+n的规律):[code]@echo off&setlocal enabledelayedexpansion
::请将下面的tab换成实际的制表符
set "t=tab"
for /l %%a in (1,1,210) do (
     set "k=!k!"&cls
     for /l %%b in (1,1,20) do (
         for /l %%c in (1,1,20) do set "str=!str!■"
         echo !str!&set "str="
      )
      2>nul echo %t%!k!
      echo %%a
      for /l %%b in (1,1,1000) do echo>nul
)
pause>nul[/code]通过这个测试,终于得出了计算公式,退格符的个数=要回退的行(含空行)*10+2。如此以来,我们就掌握了回退行的规律了,通过与echo,的配合就能随时实现光标的上上下下了(当然指在行首)哈哈,下面就小写一段特效用以“自诩”_|_":[code]@echo off&setlocal enabledelayedexpansion
mode con cols=50 lines=12&color 9f
::请将下面的tab换成实际的制表符
set "t=tab"
for /l %%a in (1,1,11) do (
    set /p=     *<nul
    for %%b in (1 11) do if %%a equ %%b set "flag=a"
    if defined flag (
       for /l %%b in (1,1,39) do set /p=*<nul
       set "flag="
       ) else (
       if %%a equ 6 (
          set /p=                无 为                *<nul
          ) else (     
          for /l %%b in (1,1,38) do set /p= <nul
          set /p=*<nul
       )
    )
    echo.
)
for /l %%a in (1,1,52) do set "k=!k!"
2>nul echo,%t%!k!&set "k=!k:~30!"
for /f "tokens=3 delims=:" %%a in (%~0) do (  
    set /a n+=1,m=n%%2
    set /p=     *<nul
    for /l %%b in (1,1,12) do set /p= <nul
    set /p=%%a<nul
    for /l %%b in (1,1,2000) do echo>nul
    if !m! equ 0 (
       2>nul echo,%t%%k%
       ) else (
       echo,&echo,
    )
)
for /l %%b in (1,1,2000) do echo>nul
2>nul echo,%t%%k%
echo      *               无 为
for /f "tokens=3 delims=:" %%a in (%~0) do echo      *            %%a            
pause>nul   
::#:#:光阴无声逝如风
::#:#:一梦醒觉行囊空
::#:#:可叹寒窗十年问
::#:#:不入尘缘竟峥嵘
::#:#:遥想子成家业时
::#:#:情迷书墨一老翁[/code]小诗也是自己乱写的,代码也没有简化,权当好玩了,呵呵。

【扩展资料】
探讨:ECHO;TAB若干退格字符将光标多行回退与窗口列宽的关系
[url]http://bbs.bathome.net/thread-41792-1-1.html[/url]

wc726842270 发表于 2011-3-2 08:46

版主的代码也让我明白了CMD是以16进制—双字节读取内容的。刚才试过保存为UTF—8和用ASC||字符(在ANSI的情况下应该用0补位了吧,所以也是2个字节),在退格符处理ANSI中的字符时有点小意外,不过这好像是处理补位的结果吧。不过还是希望有个明确的答案
是不是因为补位会被过滤啊,现在还仅仅是个猜测

[[i] 本帖最后由 wc726842270 于 2011-3-2 08:55 编辑 [/i]]

cjiabing 发表于 2011-3-2 10:00

batman 是个人才,谢谢分享,终于有人肯下功夫来研究这一特效了,这个功能将极大地促进批处理动画的发展,期待中!~

Batcher 发表于 2014-12-26 16:41

test-1.bat 退格符直接写在脚本里面[code]@echo off
echo bbs.bathome.net
set /p =光标回退(非cls清屏)动画效果演示:<nul
for /l %%i in (1,1,10) do (
    set /p =%%i<nul
    set /p =<nul
    ping -n 2 127.1 >nul
)
echo,
pause[/code]test-2.bat 用命令生成退格符[code]@echo off
echo bbs.bathome.net
for /f %%i in ('echo prompt $H ^| cmd') do (
    set "KeyBS=%%i"
)
set /p =光标回退(非cls清屏)动画效果演示:<nul
for /l %%i in (1,1,10) do (
    set /p =%%i<nul
    set /p =%KeyBS%<nul
    ping -n 2 127.1 >nul
)
echo,
pause[/code]test-3.bat 倒计时+不换行+空格开头[code]@echo off
echo   bbs.bathome.net
for /f %%i in ('echo prompt $H ^| cmd') do (
    set "KeyBS=%%i"
)
echo   倒计时:
for /l %%i in (9,-1,1) do (
    set /p "= %KeyBS%  %%i"<nul
    set /p "=%KeyBS%%KeyBS%%KeyBS%"<nul
    timeout /t 1 >nul
)
echo,
echo   执行完毕
pause[/code]

老刘1号 发表于 2017-4-9 10:28

这么6的帖子没人顶不科学啊

1055367558 发表于 2017-9-24 10:47

诶呦,不错啊。。以前还不知道怎么用[code]@echo off
set /p =光标回退显示时间(非cls清屏):<nul
:a
set/p =%time:~0,8%<nul
for /l %%k in (1,1,8) do (set/p=<nul)
ping -n 1 127.1 >nul
goto a[/code]

yyz219 发表于 2021-10-22 09:20

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=198076&ptid=11266]5#[/url] [i]老刘1号[/i] [/b]
确实很强大。学习学习

hnfeng 发表于 2023-3-14 11:30

厉害,强大啊

dos-a 发表于 2023-4-3 19:53

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=71616&ptid=11266]1#[/url] [i]batman[/i] [/b]


    现在win10上无法实现多行回退啊

页: [1]

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.