当然,要从命令提示符发送EOF, 按Enter键后按Ctrl-Z就行了。
C:\> type con > file.txt line1 line2 ^Z
这个工作, file.txt
包含line1\r\nline2\r\n
。 但是,如果没有最后一个换行符,你怎么能这样做,以便file.txt
包含line1\r\nline2
?
在Linux中,解决方法是按Ctrl-D两次1 。 但在Windows上的等效物是什么? 命令提示符将愉快地在一行的末尾打印^Z
s而不发送EOF。 (如果你按回车键 ,那么你input的任何^Z
都会被作为文字转义字符写入文件!)
如果在Windows上没有办法做到这一点,那为什么?
1 https://askubuntu.com/questions/118548/how-do-i-end-standard-input-without-a-newline-character
命令type con > file.txt
在cmd shell中没有针对^Z
任何特殊处理,因为目标文件不是con
并且未以Unicode(UTF-16LE)输出模式运行type
命令。 在这种情况下,唯一的^Z
处理是在ReadFile
调用本身中,对于控制台输入缓冲区来说,如果以^Z
开头的行有一个未公开的行为来返回0字节。
让我们用一个附加的调试器来检查它,注意读取的字节数( lpNumberOfBytesRead
)是第四个参数(x64中的寄存器r9),它通过引用作为输出参数返回。
C:\Temp>type con > file.txt Breakpoint 1 hit KERNELBASE!ReadFile: 00007ffc`fb573cc0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000068`c5d1dfa8=000001e3000001e7 0:000> r r9 r9=00000068c5d1dfd0 0:000> pt line1 KERNELBASE!ReadFile+0xa9: 00007ffc`fb573d69 c3 ret 0:000> dd 68c5d1dfd0 l1 00000068`c5d1dfd0 00000007
正如你所看到的,阅读"line1\r\n"
是7个字符,正如所料。 接下来让我们输入"\x1aline2\r\n"
,看看ReadFile
据报道有多少个字节:
0:000> g Breakpoint 1 hit KERNELBASE!ReadFile: 00007ffc`fb573cc0 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000068`c5d1dfa8=0000000000000000 0:000> r r9 r9=00000068c5d1dfd0 0:000> pt ^Zline2 KERNELBASE!ReadFile+0xa9: 00007ffc`fb573d69 c3 ret 0:000> dd 68c5d1dfd0 l1 00000068`c5d1dfd0 00000000
正如你上面看到的,这次它读取0字节,即EOF。 在^Z
之后输入的所有内容都被忽略了。
然而,你想要的是通常得到这种行为,无论^Z
出现在输入缓冲区中。 type
将为你做这个,但只有在Unicode模式下执行,即cmd /u /c type con > file.txt
。 在这种情况下,cmd确实有特殊的处理来扫描^Z
的输入。 但我敢打赌,你不需要一个UTF-16LE文件,特别是因为cmd不写一个BOM来允许编辑检测UTF编码。
你运气好,因为它发生copy con file.txt
正是你想要的。 在内部调用cmd!ZScanA
来扫描每行的^Z
字符。 我们可以在调试器中看到这一点,但是这次我们完全没有记录的领域。 在检查时,看起来这个函数的第三个参数(寄存器r8在x64中)是作为输入参数读取的字节数。
让我们再次输入7个字符的字符串"line1\r\n"
:
C:\Temp>copy con file.txt line1 Breakpoint 0 hit cmd!ZScanA: 00007ff7`cf4c26d0 48895c2408 mov qword ptr [rsp+8],rbx ss:00000068`c5d1e9d0=0000000000000000 0:000> r r8; dd @r8 l1 r8=00000068c5d1ea64 00000068`c5d1ea64 00000007
输出时,扫描的长度保持7个字符:
0:000> pt cmd!ZScanA+0x4f: 00007ff7`cf4c271f c3 ret 0:000> dd 68c5d1ea64 l1 00000068`c5d1ea64 00000007 0:000> g
接下来输入23(0x17)字符串"line2\x1a Ignore this...\r\n"
:
line2^Z Ignore this... Breakpoint 0 hit cmd!ZScanA: 00007ff7`cf4c26d0 48895c2408 mov qword ptr [rsp+8],rbx ss:00000068`c5d1e9d0=0000000000000000 0:000> r r8; dd @r8 l1 r8=00000068c5d1ea64 00000068`c5d1ea64 00000017
这次扫描的长度只是^Z
之前的5个字符:
0:000> pt cmd!ZScanA+0x4f: 00007ff7`cf4c271f c3 ret 0:000> dd 68c5d1ea64 l1 00000068`c5d1ea64 00000005
我们期望file.txt是12个字节,它是:
C:\Temp>for %a in (file.txt) do @echo %~za 12
更一般地说,如果Windows控制台程序想要实现接近Unix终端行为的Ctrl + D处理,则可以使用宽字符控制台函数ReadConsoleW
,通过引用将CONSOLE_READCONSOLE_CONTROL
结构传递为pInputControl
。 该结构的dwCtrlWakeupMask
字段是一个位掩码,用于设置哪个控制字符将立即终止读取。 例如,位4启用Ctrl + D。 我写了一个简单的测试程序来演示这种情况:
C:\Temp>.\test Enter some text: line1 You entered: line1\x04
在上面的例子中你看不到这个,但是这个读取是通过按Ctrl + D立即终止的,甚至没有按下回车键。 ^D
控制字符(即'\x04'
)保留在输入缓冲区中,这对于多个控制字符需要不同行为的情况很有用。