如何从命令提示符*不带换行符*发送EOF?



当然,要从命令提示符发送EOF,Enter后跟Ctrl-Z就可以了。

C:> type con > file.txt
line1
line2
^Z

这有效,file.txt包含line1rnline2rn.但是,如果没有最后一个换行符,您怎么能做同样的事情,以便file.txt包含line1rnline2

在 Linux 中,解决方案是按Ctrl-D两次1。但是Windows上的等价物是什么?命令提示符将愉快地在行尾打印^Zs,而无需发送EOF。(如果按Enter,则键入的任何^Z都将作为文字转义字符写入文件!

如果在Windows上无法执行此操作,那为什么呢?


1https://askubuntu.com/questions/118548/how-do-i-end-standard-input-without-a-newline-character

命令type con > file.txt对cmd shell中的^Z没有任何特殊处理,因为目标文件未con,并且type命令未在Unicode(UTF-16LE)输出模式下运行。在这种情况下,唯一的^Z处理是在ReadFile调用本身中,对于控制台输入缓冲区,如果行以^Z开头,则具有未记录的行为,即返回 0 字节读取。

让我们使用附加的调试器来检查这一点,注意读取的字节数 (lpNumberOfBytesRead) 是第 4 个参数(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

正如您在上面看到的,正如预期的那样,阅读"line1rn"是 7 个字符。接下来,让我们输入"x1aline2rn",看看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字符。我们可以在调试器中看到这一点,但这次我们处于完全未记录的领域。经过检查,此函数的第三个参数(x64 中的寄存器 r8)似乎是作为 in-out 参数读取的字节数。

让我们再次开始输入 7 个字符的字符串"line1rn"

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) 个字符的字符串"line2x1a Ignore this...rn"

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: line1x04

在上面的示例中看不到这一点,但是通过按 Ctrl+D 立即终止此读取,甚至没有按回车键。^D控制字符(即'x04') 保留在输入缓冲区中,这在您希望多个控制字符具有不同行为时非常有用。

最新更新