Board logo

标题: [分享]批处理利用set /p与重定向输入分行获取文本内容 [打印本页]

作者: CrLf    时间: 2011-7-18 00:44     标题: [分享]批处理利用set /p与重定向输入分行获取文本内容

本帖最后由 CrLf 于 2012-1-31 13:22 编辑

起因是前几天的某个帖子中看到 cmd<1.txt 的用法,原以为1.txt 中的 pause 之所以被跳过是因为执行完后马上接收到了一个回车符,于是我把1.txt 中的所有 pause 都改成 pause&rem ,并去除所有回车符进行试验,下为去除回车符的代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. set hh=^
  3. ::获取换行符
  4. for %%a in (
  5.         "@echo off" "pause&rem" "echo abc" "pause&rem"
  6. ) do set str=!str!!hh!%%~a
  7. echo !str:~2!>3.txt
  8. ::只用换行符断行
  9. cmd<3.txt
  10. echo;
  11. echo ________end________
  12. pause
复制代码
假设修改后 1.txt内容如下(测试时,此文本中不存在回车符):
  1. @echo off
  2. pause&rem
  3. echo abc
  4. pause&rem
复制代码
结果仍然没有等待用户输入,并且还吞掉了下一行 echo 的第一个字符,导致 cmd 显示:
  1.         请按任意键继续. . .
  2.         'cho' 不是内部或外部命令,也不是可运行的程序
  3.         或批处理文件。
  4.         请按任意键继续. . .
复制代码
对此感到非常疑惑,百思不得其解之下去请教寒夜版主,他问我 pause 等待的是什么输入,我才忽然醒悟,原来 pause 等待的是“任意键”,也就是说,它把 echo 的 e 当作用户输入给接收了(因为行末的 0D 0A被 cmd 接收了,所以 pause 接收的第一个字符就是下一行的首字符 e),因此这里用 pause 无法实现暂停的效果,这就推翻了我原来的认识,证明等待用户输入的命令并不是以回车符作为终止输入的信号。
(另:其实现想来这个问题也好解决,既然是类似 cmd /c "pause"<1.txt 这样的原因造成 pause 从 1.txt 获取了任意输入,那么改成 pause<con 即可重定向为从键盘获取输入)

进一步思考一下,众所周知, set /p 首行=<1.txt 能获取 1.txt 第一行,那么对含有大量 set /p 的语块进行重定向,又是什么结果呢?
  1. @echo off
  2. (for /l %%a in (1 1 10) do set /p .%%a=)<%0
  3. set.
  4. pause
复制代码
可以看到,通过 set /p 配合重定向,能够把文本每一行都设为变量值,这是全新的技巧,更重要的是,这是一种全新的遍历文本的方式,它相当于不跳过空行的 for until ,这与 for /f 的 skip 参数相映成趣,而且还对特殊字符有极佳的兼容性。不过有得必有失,使用 set /p 赋值时,变量长度不能超过 1024 字节,所以局限了这个技巧的适用范围。
        
激动之余,又产生了两个疑惑:
    1、set /p 是以什么为依据断行
    2、当循环数大于文本行数时,为什么没有停顿下来等待用户输入

和寒夜版主一起做了几个试验,证明无论是单纯的 0D 回车符或者 0A 换行符都无法实现平时在 cmd 窗口中敲回车结束 set /p 输入的效果,必须出现连续的一组 0D 0A 才能够终止对一个 set /p 的输入。关于终止 set /p 输入的“特征码”,25 楼的 mxxcgzxxx 提出了更合理的猜想:0D 0A 和 0A 0D 这两种组合都能起到终止 set /p 输入的作用。
(25楼链接:http://bbs.bathome.net/viewthrea ... muid=30406#pid86638)
        
而第二个问题,绕了半天弯子终于得到一个比较合理的猜测:当重定向的输入被前面执行的命令取用完的时候,剩下的就是从空设备的输入,也就是 set /p .5=<nul。所以假如文本有N行,那么超过第 N 次的 set /p 都接收到了来自空设备的输入因而没有被赋值,示例如下,假设原 bat 为:
  1. (set /p .a=
  2. set /p .b=)<只有一行的文件.txt
  3. set.
复制代码
其作用相当于:
  1. set /p .a=<只有一行的文件.txt
  2. ::取首行
  3. set /p .b=<nul
  4. ::从空设备获取输入,等于无输入
  5. set.
  6. ::显示以 . 开头的变量
复制代码
通过这个猜测和其他一些命令接收重定向输入时表现出的特性衍生出一个推测,那就是 cmd 在接受重定向输入到命令的时候,也许是一个字符一个字符顺序传递给语块\语句的,那些能够接受重定向输入的命令会自发地从中获取输入,直到命令自行关闭输入句柄为止。
        
这可以理解为 cmd 中出现重定向输入的时候,输入中的字符在排队等候被命令依次提走,一直到无字符可提的时候,重定向输入的来源就成为了一个空设备 nul。
        
好比一个旅行团在打车,出现愿意载客的出租车时,队伍就有序地依次上车,一辆车客满后就再等下一辆(旅行团并不知道当前这辆车何时客满,他们只需要机械地让排头的人上车、直到司机喊停为止),最终所有人都打车走光,这时候新来的出租车就找不到客人了,所以空车离开时当然还是空车。
        
当然有些命令是以任意合法字符或者固定字符来判定何时结束输入的,比如 choice、set /p 和 pause,这就很有利用的价值。
        
此处仅以 set /p 举几个例子:
  1. @echo off
  2. set /p line=要获取的行所在行数:
  3. (for /l %%a in (1 1 %line%) do set /p 内容=)<a.txt
  4. set 内容
  5. ::获取指定行内容的新方法,由于无需遍历整个文本,要获取的行位置靠前的情况下有很大优势
复制代码
  1. @echo off
  2. (for /l %%a in (1 1 100) do set /p .%%a=)<%0
  3. ::不跳过空行赋值,但是 tmplinshi 版主的测试结果表明这比常规办法稍慢,它只在某些场合有优势,比如只获取前 N 行时,或者要兼容空行的情况,再或者需要兼容特殊字符的时候。
复制代码
(5楼链接:http://bbs.bathome.net/viewthrea ... muid=30406#pid86516)
  1. @echo off&setlocal enabledelayedexpansion
  2. set n=1
  3. (for /l %%a in (1 1 5) do (
  4.     if defined .!n! set /a n+=1
  5.     set /p .!n!=
  6. ))<%0
  7. ::当然也同样可以跳过空行只将前 N 行赋值,但是这里的“前 N 行”计数时其实还是包括空行的,如果是要求取不把空行计算在内的前 N 行,我想最经济的方法就是先用 findstr . a.txt 输出非空行再分别赋值了
复制代码
  1. @echo off
  2. (for /l %%a in (1 1 7) do (
  3.     pause
  4.     set /p echo=
  5.     echo !echo!
  6. )<%0
  7. ::去除每行行首第一个任意字符的另一种方法,如果不计较效率的话,用 choice 可以只保留指定字符
复制代码
  1. @echo off
  2. (for /l %%a in (1 2 7) do (
  3.     set /a a=1,b=2
  4.     set /p a=
  5.     set /p b=
  6.     if !a!==!b! echo 相等
  7. )<%0
  8. ::以两行为周期判断其内容是否相等,这比起老方法省下了许多麻烦,比如无需用 setlocal、endlocal 来兼容特殊字符
复制代码
  1. @echo off
  2. (for %%a in (
  3. 1-关回显 2-循环体 3-循环内容 4-do 4-设变量 5-输入 6-查看变量 7-注释
  4. ) do (
  5.     set /p .%%a=
  6. ))<%0
  7. set.
  8. ::可以通过无参数的for来循环,实现了以往无法实现的效果
复制代码
  1. @echo off&setlocal enabledelayedexpansion
  2. (for /f "tokens=1* delims=:" %%a in ('findstr /n .* 1.txt') do (
  3.     set t2=
  4.     set /p t2=
  5.     echo;%%b!t2!
  6. ))<2.txt>合并.txt
  7. ::由于可以有两个不同的输入来源并存,所以双文本乃至多文本合并就成为轻而易举的事了
复制代码
  1. @echo off&setlocal enabledelayedexpansion
  2. for %%a in (a\*.txt b\*.txt) do set /a n+=1
  3. dir /b /a-d /o-n b\*.txt>list.$
  4. ::计算文件总数为 %n%,生成要复制的文件列表为 list.$
  5. (for /l %%a in (1 1 %n%) do (
  6. if not exist a\%%a.txt (
  7. set /p f=
  8. copy "b\!f!" "a\%%a.txt"
  9. )
  10. ))<list.$
  11. ::以往让人头疼的按递增文件名复制文件的问题,也可以这样解决
  12. del /f list.$>nul
  13. pause
复制代码
无奈的是,可以分段接受重定向输入的命令寥寥无几,所以暂时还没有想到更多的实用技巧,还是等待大家来补充吧。

感谢 寒夜孤星、mxxcgzxxx、tmplinshi 等人的指点和共同探讨、测试。
作者: mxxcgzxxx    时间: 2011-7-18 07:43

由于楼主的研究精神,于是乎截取文本头几行,或截取文本指定行的问题迎刃而解!
赞一个
作者: tmplinshi    时间: 2011-7-18 09:52

不错,有一定的实用性。

例如,再也不用使用 findstr 来保留空行了,只不过要知道文本的行数。示例:
  1. @echo off
  2. set str=
  3. (
  4.     for /l %%a in (1 1 7) do (
  5.         setlocal enabledelayedexpansion
  6.         set /p str=
  7.         if defined str (echo !str!) else echo ---------空行
  8.         endlocal
  9.     )
  10. ) <a.txt
  11. pause
复制代码

作者: CrLf    时间: 2011-7-18 10:05

获取文件行数可以用:
  1. find /c /v ""<1.txt
复制代码
虽然也是外部命令,但是效率比 findstr /n 要高
作者: tmplinshi    时间: 2011-7-18 10:22

本帖最后由 tmplinshi 于 2011-7-18 11:35 编辑

我测试了一个 11651 行的文本(没有空行),结果是 for /f 更快。

测试代码:

set_p.bat:
  1. @echo off
  2. (
  3.     for /l %%a in (1 1 11651) do (
  4.         setlocal enabledelayedexpansion
  5.         set /p str=
  6.         echo,!str: =_!
  7.         endlocal
  8.     )
  9. ) <test.txt >test_1.txt
复制代码
for_f.bat:
  1. @echo off
  2. (
  3.     for /f "delims=" %%a in (test.txt) do (
  4.         set str=%%a
  5.         setlocal enabledelayedexpansion
  6.         echo,!str: =_!
  7.         endlocal
  8.     )
  9. ) >test_2.txt
复制代码
测试结果:
e:\我的文档\桌面>timeit set_p.bat

Version Number:   Windows NT 5.1 (Build 2600)
Exit Time:        10:19 am, Monday, July 18 2011
Elapsed Time:     0:00:15.500
Process Time:     0:00:14.031
System Calls:     430474
Context Switches: 56632
Page Faults:      39304
Bytes Read:       19381026
Bytes Written:    741100
Bytes Other:      2573192

e:\我的文档\桌面>timeit for_f.bat

Version Number:   Windows NT 5.1 (Build 2600)
Exit Time:        10:20 am, Monday, July 18 2011
Elapsed Time:     0:00:12.484
Process Time:     0:00:12.328
System Calls:     159911
Context Switches: 2458
Page Faults:      15433
Bytes Read:       598486
Bytes Written:    597572
Bytes Other:      2345164

作者: tmplinshi    时间: 2011-7-18 10:36

  1. @echo off
  2. setlocal enabledelayedexpansion
  3. (
  4.     for /l %%a in (1 1 7) do (
  5.         pause >nul
  6.         set /p echo=
  7.         echo !echo!
  8.     )
  9. ) <%0
  10. pause
复制代码
有点想不通的是,pause 为什么不直接接收 %0 的内容,而要接收它下一行的 set /p。
作者: cjiabing    时间: 2011-7-18 10:45

很好很强大,值得研究
作者: mxxcgzxxx    时间: 2011-7-18 10:54

本帖最后由 mxxcgzxxx 于 2011-7-20 22:36 编辑
@echo off
setlocal enabledelayedexpansion
(
    for /l %%a in (1 1 10) do (
        pause >nul
        set /p echo=
        echo !echo!
    )
)
tmplinshi 发表于 2011-7-18 10:36


有趣的结果:起头的()被忽略,最后一个P被上一行吃了
所以用这种办法还得小心
作者: tmplinshi    时间: 2011-7-18 11:00

8# mxxcgzxxx


是每一行前面的第一个字符没有了,顶楼有说。
作者: mxxcgzxxx    时间: 2011-7-18 11:05

本帖最后由 mxxcgzxxx 于 2011-7-18 11:11 编辑

9# tmplinshi
哦,是啊,没注意,pause吃了后面的输入!
也就是说<%0被分成了两下,第一个字被pause吃了,剩下的才轮到set/p,
可以用作去头几个字符用了
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. ::从每行第三个字符截取
  4. (
  5.     for /l %%a in (1 1 10) do (
  6.         for /l %%1 in (1 1 3) do pause >nul
  7.         set /p echo=
  8.         echo %%a !echo!
  9.         )
  10. ) <%0
  11. pause
复制代码

作者: CrLf    时间: 2011-7-18 11:12

6# tmplinshi

5楼结果是因为 for 会跳过空行,如果要兼容空行的话,结果就要反过来了...
pause 是有接收字符的,它接收“任意字符”,也就是把每行开头的那个字符给吞了
作者: mxxcgzxxx    时间: 2011-7-18 11:50

下面这个测试很有趣。空行被吃一个字符后,理应是换行符被吃,但造成换行符加下一行的(被吸上来了
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. (
  4.     for /l %%a in (1 1 10) do (
  5.         pause>nul
  6.         set /p echo%%a=
  7.         echo %%a !echo%%a!
  8.         )
  9. ) <%0
  10. pause
复制代码

作者: tmplinshi    时间: 2011-7-18 12:07

12# mxxcgzxxx


截图工具推荐用 FSCapture:
    颜色少的用 png 格式,保存时选 256 色;
    颜色多的用 jpg 格式,比如人物照片等。
作者: Batcher    时间: 2011-7-18 13:24

13# tmplinshi


Reduce Color, 2 Colors
这样是不是更小更清晰
作者: mxxcgzxxx    时间: 2011-7-18 13:27

本帖最后由 mxxcgzxxx 于 2011-7-18 13:40 编辑

13# tmplinshi

学习,之前没想过这个问题哈,是要省点空间
我都是用抓屏键,用图画工具弄的,换成PNG反面有50多KB
作者: CrLf    时间: 2011-7-19 01:00

5# tmplinshi


我用2235行的厚黑学全集试了试(题外话,下载回来都木油看过,原来这玩意就是用来测试的...),结果相差无几
for /f:
  1. @echo off
  2. echo %time%
  3. (
  4.     for /f "delims=" %%a in (test.txt) do (
  5.         set str=%%a
  6.         setlocal enabledelayedexpansion
  7.         echo,!str: =_!
  8.         endlocal
  9.     )
  10. ) >test_2.txt
  11. echo %time%
  12. pause
复制代码
回显:
  1. 0:55:30.77
  2. 0:55:32.83
  3. 请按任意键继续. . .
复制代码
set /p:
  1. @echo off
  2. echo %time%
  3. (
  4.     for /l %%a in (1 1 2235) do (
  5.         setlocal enabledelayedexpansion
  6.         set /p str=
  7.         echo,!str: =_!
  8.         endlocal
  9.     )
  10. ) <test.txt >test_1.txt
  11. echo %time%
  12. pause
复制代码
回显:
  1. 0:55:34.07
  2. 0:55:36.37
  3. 请按任意键继续. . .
复制代码
结果是 2.04:2.30,差距约为十分之一...视实际情况而定吧,毕竟遍历方式以及来源的异同决定了这两种用法有各自适用范围~
作者: batman    时间: 2011-7-19 08:33

很好,很强大!!!
庆贺又一bathome原创技术的诞生!!!
作者: mxxcgzxxx    时间: 2011-7-19 08:41

本帖最后由 mxxcgzxxx 于 2011-7-19 08:45 编辑

从特殊字符方面来说还是有很大区别的
  1. !@#~#$%^&*()_+-={}|":?><;'\`"~!@#$%^&*()_+|":?><
复制代码
用SET/P和FOR/F分别读取并输出
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. (
  4.     for /l %%a in (1 1 1) do (
  5.         set/p echo=
  6.         echo.!echo!
  7.         )
  8. ) <1.txt >2.txt
  9. for /f "delims=" %%1 in (1.txt) do (
  10.        set "str=%%1"
  11.        echo !str!
  12.        )>>2.txt
复制代码
结果
  1. !@#~#$%^&*()_+-={}|":?><;'\`"~!@#$%^&*()_+|":?><
  2. ?><;'\`"~?><
复制代码
说明,如果在有特殊字符的情况下,SET/P更加合理而简便
作者: mxxcgzxxx    时间: 2011-7-19 09:18

本帖最后由 mxxcgzxxx 于 2011-7-19 09:39 编辑
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. (
  4.        set/p a=
  5.        set/p b=
  6.        set/p c=
  7.        echo.!a!
  8.        echo.!b!
  9.        echo.!c!
  10.        )<1.txt >2.txt
复制代码
这样子是一次性获取头三行信息,
利用这个可进行隔行获取
  1. @echo off
  2. (
  3.      for /l %%1 in (1 1 10 ) do (
  4.        setlocal enabledelayedexpansion
  5.        set/p 偶数行=
  6.        set/p 奇数行=
  7.        echo.!偶数行!>>2.txt
  8.        echo.!奇数行!>>3.txt
  9.        endlocal
  10.        )
  11. )<1.txt
复制代码
测试了多种组全发现,SET/P可以全文吸收一个字符都不错,
但无法跳过空行是一个优点也是一大问题
作者: CrLf    时间: 2011-7-19 09:44

本帖最后由 zm900612 于 2011-7-19 09:53 编辑

18# mxxcgzxxx

可以用开闭变量延迟来提升对特殊字符的兼容性,但是我没测试过这个动作对效率的影响:
  1. @echo off
  2. for /f "delims=" %%1 in (1.txt) do (
  3.        endlocal
  4.        rem 此时关闭变量延迟以免 %%1 中含有 ! 致错
  5.        set "str=%%1"
  6.        setlocal enabledelayedexpansion
  7.        rem 此时打开变量延迟以即时读取变量
  8.        echo !str!
  9. )>>2.txt
复制代码
另外,要跳过的行可以不赋值...三行一周期,获取周期中的首尾两行:
  1. (@echo off
  2. for /l %%a in (1 1 12) do (
  3.     set .1=
  4.     set .2=
  5.     set /p .1=
  6.     set /p=
  7.     set /p .3=
  8.     echo;!.1!
  9.     echo;!.2!
  10. ))<%0
  11. pause
复制代码
确实,不跳过空行既是优点也是缺点,另外还有两个不知道是优点还是缺陷的特性,一是 set /p 接收到空输入时不会清空该变量原有值,二是无法一步到位地完成变量赋值、加前后缀、切割等等复杂动作。
作者: CrLf    时间: 2011-7-19 13:39

12# mxxcgzxxx


刚刚又去向无所不能的寒夜版主请教,我们原来以为 pause 会把紧随其后的 0D 0A 整个吞掉(因为曾经尝试用 pause 取换行符无果),但是12楼的代码举了个反例,这实在让我想不通,难道 pause 能提前判断下一行是否非空、当下一行不为空时则只吞 0D 吗?

后来找到了一个更为合理的假设,那就是 pause 本来就只吞碰到的第一个字符,此处的第一个字符也就是回车符 0D,而 set /p 继续接收第二行输入时接到了 0A ( 0D 0A,所以出现了换行效果。但是当第二行也是空行时,set /p 接收到的就是 0A 0D 0A,我们猜测 set /p 默认不会将单纯的换行符 0A 设为变量,所以 set /p 赋值为空。

做了个实验证明这一点:
  1. @echo off
  2. (for /l %%a in (1 1 10) do (
  3. pause>nul
  4. pause>nul
  5. rem 用两个pause测试,看究竟是否吞了两组 0D 0A,如果 pause 是吞整个 0D 0A,那结果应该不会出现空行或者带换行符的变量
  6. set /p .%%a=))<%0
  7. set.
  8. pause
复制代码
结果果然空了一行,证明每个 pause 确实只是取走紧随其后其后的那个字符,而非能够“预知”下一行的内容。
作者: mxxcgzxxx    时间: 2011-7-19 15:47

本帖最后由 mxxcgzxxx 于 2011-7-19 15:49 编辑

21# zm900612
听起来是这个理,但下面这个情况怎么解释呢?
  1. @echo off
  2. (for /l %%a in (1 1 10) do (
  3. pause>nul
  4. rem 用两个pause测试,看究竟是否吞了两组 0D 0A,如果 pause 是吞整个 0D 0A,那结果应该不会出现空行或者带换行符的变量
  5. set /p .%%a=))<%0
  6. set.>1.txt
  7. pause
复制代码
输出的1.txt为
.1=echo off
.4=for /l %%a in (1 1 10) do (
.5=ause>nul
.6=em 用两个pause测试,看究竟是否吞了两组 0D 0A,如果 pause 是吞整个 0D 0A,那结果应该不会出现空行或者带换行符的变量
.7=et /p .%%a=))<%0
.8=et.>1.txt
.9=ause

按理应该是下面这样取值的啊,为什么到3行出错了?然道说第三行的 0D 0A 可以用两次
@echo off 0D 0A
0D 0A
0D 0A
0D 0A
(for /l %%a in (1 1 10) do ( 0D 0A
pause>nul0D 0A
rem 用两个pause测试,看究竟是否吞了两组 0D 0A,如果 pause 是吞整个 0D 0A,那结果应该不会出现空行或者带换行符的变量0D 0A
set /p .%%a=))<%0 0D 0A
set.>1.txt 0D 0A
pause 0D 0A

.1=echo off
.2=0A 0D 0A
.3=0A
for /l %%a in (1 1 10) do (
.4=ause>nul
...

作者: mxxcgzxxx    时间: 2011-7-19 15:58

有半个换行符的值写入文本后会出现一个像退格符的黑块,但我发不上论坛,
可以对TXT文本进行复制并写到批处理里面,很好用也
但无法通过变量截取的方式获得有点可惜!
作者: CrLf    时间: 2011-7-19 16:13

22# mxxcgzxxx


诡异,测试后发现一次循环中只有一个 pause 时,连续空行数以一种奇怪的规律影响着变量的赋值:当连续空行数量为1、4、6、8...等数字时,会出现行首带有换行符的变量,这究竟是什么原因导致的呢...我也想不通了
作者: mxxcgzxxx    时间: 2011-7-19 16:29

本帖最后由 mxxcgzxxx 于 2011-7-19 17:05 编辑

24# zm900612


那可以这样理解吗? 0D 0A 和0A 0D 都可以起到结束SET/P的输入过程
三行空行时
0D 0A
0D 0A
0D 0A
第一个0D被吃  SET/P得到0A 0D (强行终结)
第二个0A被吃   SET/P得到0D 0A (完整)
这样还能解释为什么三个空行被赋值两次
0A 0D 的组合有没有代表什么?
5个空行情况:
吃 ---- 留
0D---0A| 0D
0A|---0D  0A|
0D---0A| 0D
0A|---文本,所以正常不换行

以此类推,基本都合理了
1空行:0A 换行文本
2空行:完整文本
3空行:文本少头一字符
4空行:0A 换行文本
5空行:完整文本
。。。三个空行一个循环,所以:
1,4,7,10。。。为换行文本
2,5,8,11。。。为不少字的完整文本
0,3,6,9。。。为少头一字符的文本

刚才测试了下,完全符合这个规律!
作者: CrLf    时间: 2011-7-19 17:24

本帖最后由 CrLf 于 2011-7-21 16:23 编辑

25# mxxcgzxxx


好!确实很有可能,测试代码如下:
  1. @echo off
  2. set x0a=^
  3. ::空行请自行添加
  4. for /f %%a in ('copy /z %~s0 nul') do set x0d=%%a
  5. cmd /v:on /c echo abc!x0a!!x0d!123!x0a!!x0d!>1.txt
  6. (for /l %%a in (1 1 10) do set /p .%%a=)<1.txt
  7. set.
  8. pause>nul
复制代码
证明确实是以三行为一周期(汗一个,早先对此的测试结果是忘了加上被换行的那一行的,所以当时测试所得的周期很没有规律...误导人啊误导人),而且很有可能确实是像老兄说的那样,只要碰到一组 0D 0A,无论它们字符顺序如何,都能结束 set /p 的输入行为。

可用如下代码进行验证:
  1. @echo off&setlocal enabledelayedexpansion
  2. set a=^
  3. (for /f %%a in ('copy /z %0 nul') do set b=%%a
  4. set<nul /p=赋值成功!a!!b!赋值失败)>1.txt
  5. set /p e=<1.txt
  6. echo !e!
  7. pause
  8. echo d|debug 1.txt
  9. ::如果未出现赋值失败的字眼则代表 0A 0D 成功地结束了 set /p 的输入,结果证明确实如此
复制代码
越来越完善了
作者: mxxcgzxxx    时间: 2011-7-19 19:41

本帖最后由 mxxcgzxxx 于 2011-7-21 16:50 编辑

用提取的换行符玩下,虽然不知实用性在哪方面可以应用?
因为论坛会吃单空格行,所以我使用:号
  1. :
  2. @echo off
  3. ::获取0A换行符
  4. (pause>nul&set /p a=)<%0
  5. echo 你好%a%我在哪?
  6. pause>nul
复制代码
由于半个换行让“我在哪?”进行了无间道,不存在的东西
但开了变量延时后就不一样了
  1. :
  2. @echo off
  3. setlocal enabledelayedexpansion
  4. ::获取0A换行符
  5. (set/p=&pause>nul&set /p a=)<%0
  6. echo 你好!!a!我在哪啊?!a!你也不知道吗?!a!真是可惜……
  7. pause>nul
  8. set "b=123!a!456!a!789"
  9. echo !b!
  10. pause>nul
复制代码
效果不是太理想但也很好了,一次三行内容,就是后面的每行多一个符号,可以加个退格符就可以了!!
而且可以通过赋值传达给别的函数,但一定要开变量延时和加上一个退格符!

我也搜索了论坛,应该来说这个方法是快而简便获取换行符的方法,
又一个新技术诞生了哈!
  1. :
  2. @echo off
  3. setlocal enabledelayedexpansion
  4. ::获取0A换行符
  5. (pause>nul&set /p a=)<%0
  6. ::通过赋值用退格符将换行符后的空格修整得到完整实用的换行符
  7. set "b=!a!"
  8. echo !b!123456!b!789
  9. pause>nul
复制代码

作者: raymai97    时间: 2011-8-22 10:12

回复 27# mxxcgzxxx


    其实我有些混乱了,你的代码的意思是这样么?
  1. (pause>nul&set /p a=)<%0
复制代码
利用批处理开头来获取换行字符和空格字符
  1. set "b=!a!"
复制代码
换行+空格+退回字符=换行+不会再退回的退回字符=完整实用的换行符?

就达成了0d 0a组合的目的?
作者: netbenton    时间: 2011-9-3 23:02

这个真的是新发现,竟然还支持嵌套,看来多个文件合并也可以了
  1. @echo off
  2. (set/p a1=
  3.    (set /p b1=
  4.    set /p b2=
  5.    )<bb.txt
  6. set/p a2=)<aa.txt
  7. set a
  8. set b
  9. pause
复制代码

作者: DAIC    时间: 2011-9-3 23:46

回复 30# netbenton


版主请帮忙看看这个合并文件时处理特殊字符的问题吧
http://bbs.bathome.net/thread-13735-1-1.html
作者: garyng    时间: 2011-11-9 21:18

回复 31# DAIC


    我觉得你发错地方了~
作者: tkaven    时间: 2012-3-20 10:56     标题: 在文本某行中插入字符(支持任意字符,保留空行)

本帖最后由 tkaven 于 2012-3-20 18:26 编辑
  1. @echo off&color a&setlocal enabledelayedexpansion
  2. ::用法 call :insert "要修改的本文文档路径" "在哪一行下面插入文字" "所要插入的文字"
  3. call :insert "C:\Users\Administrator\Desktop\新建 Text Document.txt" "5" "我好喜欢你啊,哈哈哈"
  4. endlocal
  5. echo 操作完成,按任意键退出&pause>nul
  6. exit
  7. :insert
  8. FOR /F %%l in ('find /c /v ""^< %1') do (
  9. for /l %%a in (1 1 %%l) do (
  10. set /p str=
  11. set /a 当前行=当前行+1
  12. if !当前行! GTR %~2 (
  13. echo.!str!
  14. ) else (
  15. if !当前行! EQU %~2 (echo.!str!&echo.%~3) else (echo.!str!)
  16. )
  17. set str=
  18. )
  19. )< %1 >> "%~d1%~p1%~n1_已处理%~x1"
  20. goto :eof
复制代码

作者: liero1982    时间: 2017-2-26 05:39

问:
  1. dir | set /p v=0
复制代码
这句是不能达到获取命令输出首行文字的目的的。读了楼主的分析,估计是因为只有cr没有lf的缘故,如何通过命令补一个lf让set /p捕获前文输出呢?
作者: CrLf    时间: 2017-2-26 16:53

回复 33# liero1982


与换行符无关,是父进程不能获取子进程变量环境的原因,解释如下:

这样实际上是可以获取到的:
  1. dir | set /p VAR=^& set VAR
  2. ::此处已经捕获变量
  3. pause
  4. set VAR
  5. ::此处变量消失
  6. pause
复制代码
为什么会有这种现象,是因为管道是前后两个进程通信,cmd 如果发现前后的语句是内部命令(或用 & | && || 连接的代码块),就会启动一个 cmd 去执行这一部分
所以上面的代码相当于:
  1. cmd /c dir | cmd /c "set /p VAR=& set VAR"
复制代码
也就是说,VAR 变量是在被管道启动的子进程 cmd.exe 中赋值的,只在子进程中有效,当前运行的父进程 cmd.exe 是获取不到的
作者: liero1982    时间: 2017-2-27 00:01

回复 34# CrLf


    谢谢,受教了;那按照这个思路,是不是就真没办法用一条简单的语句实现命令输出到变量(我现在只知道用 临时文件 或者用 for /f)
作者: CrLf    时间: 2017-2-28 22:17

回复 35# liero1982


    可以试试 conset、CSet、seti 之类的第三方工具,除此之外没有简洁的办法了
作者: GNU    时间: 2017-2-28 22:19

回复 35# liero1982


    可以学习并使用PowerShell用它来取代BAT
作者: LJY4.0    时间: 2022-6-7 21:35

空设备真的是空的吗
pause<nul




欢迎光临 批处理之家 (http://bathome.net./) Powered by Discuz! 7.2