【问题标题】:How to send EOF from command prompt *without newline*?如何从命令提示符发送EOF *没有换行符*?
【发布时间】:2017-05-09 07:17:34
【问题描述】:

当然,要从命令提示符发送 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-D1。但是 Windows 上的等价物是什么?命令提示符将在行尾愉快地打印^Zs,而不发送EOF。 (如果您按 Enter,那么您输入的任何 ^Zs 都会作为文字转义字符写入文件!

如果在 Windows 上无法做到这一点,那为什么?


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

【问题讨论】:

  • copy con file.txt 也应该像你想要的那样工作。

标签: windows batch-file eof


【解决方案1】:

命令 type con > file.txt 在 cmd shell 中对 ^Z 没有任何特殊处理,因为目标文件不是 con 并且 type 命令不是以 Unicode (UTF-16LE ) 输出模式。在这种情况下,唯一的^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 字符。我们可以在调试器中看到这一点,但这次我们处于完全无证的领域。经检查,该函数的第三个参数(x64 中的寄存器 r8)似乎是作为输入输出参数读取的字节数。

让我们重新开始,输入 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 结构作为 @ 987654354@。该结构的dwCtrlWakeupMask 字段是一个位掩码,用于设置哪些控制字符将立即终止读取。例如,位 4 启用 Ctrl+D。我写了一个简单的测试程序来演示这个案例:

C:\Temp>.\test
Enter some text: line1
You entered: line1\x04

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

【讨论】:

  • 谢谢@eryksun!对幕后发生的事情进行了非常彻底和有趣的分析...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-13
相关资源
最近更新 更多