由于未初始化的数据指针,管道进入SET/P会失败?

asc*_*pfl 5 windows cmd pipe batch-file

假设我们有一个文本文件sample.txt:

one
two
...
Run Code Online (Sandbox Code Playgroud)

现在我们要删除第一行:

two
...
Run Code Online (Sandbox Code Playgroud)

一个快速的方式做到这一点是使用输入重定向,set /P以及findstr1(我知道有使用其他方法morefor /F,但让我们忘记他们现在):

@echo off
< "sample.txt" (
    set /P =""
    findstr "^"
)
Run Code Online (Sandbox Code Playgroud)

输出将按预期进行.

然而,当我更换输入重定向为什么是空的输出<通过type与管道|:

@echo off
type "sample.txt" | (
    set /P =""
    findstr "^"
)
Run Code Online (Sandbox Code Playgroud)

当我更换set /P =""pause > nul,输出的是什么,我期待的-输入文件输出,但与第一行的丢失的第一个字符(因为它是由消耗pause).但为什么set /P似乎消耗所有东西而不是像重定向<方法一样只消耗第一行?那是一个错误吗?

对我来说,似乎set /P无法充分初始化到管道数据的读指针.

我在Windows 7和Windows 10上看到了这种奇怪的行为.


变得更加奇怪:当多次调用包含管道的脚本时,例如通过类似循环for /L %I in (1,1,1000) do @pipe.bat,并且输入文件包含大约十五行或更多,有时(几千次中的几次)输入文件的片段是回; 那个片段每次都完全一样; 似乎开头总共缺少80个字节.


1) findstr挂起,以防最后一行没有被换行符终止,所以让我们假设这样.

MC *_* ND 5

检索数据时,set /p尝试使用来自stdin的数据填充1023字符缓冲区(如果可用).一旦读取操作结束,就会搜索第一行结束,一旦找到它(或者已经到达缓冲区的末尾),SetFilePointer就会调用API以在读取行结束后重新定位输入流指针.这样,下一个读操作将开始在读取线之后检索数据.

当磁盘文件与输入流关联时,这可以完美地工作,但正如Microsoft在SetFilePointer文档中所述

HFILE参数必须是指存储在探寻设备上的文件; 例如,磁盘卷.调用SetFilePointer带有手柄的非探寻设备功能,如管道或通信设备不被支持,即使SetFilePointer函数不能返回一个错误.在这种情况下,SetFilePointer函数的行为是未定义的.

发生的事情是,虽然没有产生任何错误,但当stdin与管道相关联时,重新定位读指针的调用失败,指针不会被移回,并且1023字节(或可用读取字节数)保持读取.

编辑以回应Aacini请求

set命令由eSet函数处理,该函数调用SetWork以确定set将执行哪种类型的命令.

因为它是set /pSetPromptUser函数被调用,并从该函数的ReadBufFromInput函数被调用

add     esp, 0Ch
lea     eax, [ebp+var_80C]
push    eax             ; int
push    3FFh            ; int
lea     eax, [ebp+Value]
push    eax             ; int
xor     esi, esi
push    0FFFFFFF6h      ; nStdHandle
mov     word ptr [ebp+Value], si
call    edi ; GetStdHandle(x) ; GetStdHandle(x)
push    eax             ; hFile
call    _ReadBufFromInput@16 ; ReadBufFromInput(x,x,x,x)
Run Code Online (Sandbox Code Playgroud)

3FFh从标准输入句柄请求(1023)字符(0FFFFFFF6h= -10= STD_INPUT_HANDLE)

ReadBufFromInput使用GetFileTypeAPI来确定它是应该从控制台还是从文件中读取

; Attributes: bp-based frame

; int __stdcall ReadBufFromInput(HANDLE hFile, int, int, int)
_ReadBufFromInput@16 proc near

hFile= dword ptr  8

; FUNCTION CHUNK AT .text:4AD10D3D SIZE 00000006 BYTES

mov     edi, edi
push    ebp
mov     ebp, esp
push    [ebp+hFile]     ; hFile
call    ds:__imp__GetFileType@4 ; GetFileType(x)
and     eax, 0FFFF7FFFh
cmp     eax, 2
jz      loc_4AD10D3D
Run Code Online (Sandbox Code Playgroud)

并且,在这种情况下,它是一个管道(GetFileType返回3)代码跳转到该ReadBufFromFile函数

; Attributes: bp-based frame

; int __stdcall ReadBufFromFile(HANDLE hFile, LPWSTR lpWideCharStr, DWORD cchWideChar, LPDWORD lpNumberOfBytesRead)
_ReadBufFromFile@16 proc near

var_C= dword ptr -0Ch
cchMultiByte= dword ptr -8
NumberOfBytesRead= dword ptr -4
hFile= dword ptr  8
lpWideCharStr= dword ptr  0Ch
cchWideChar= dword ptr  10h
lpNumberOfBytesRead= dword ptr  14h
Run Code Online (Sandbox Code Playgroud)

此函数将调用ReadFileAPI函数以检索指定数量的字符

push    ebx             ; lpOverlapped
push    [ebp+lpNumberOfBytesRead] ; lpNumberOfBytesRead
mov     [ebp+var_C], eax
push    [ebp+cchWideChar] ; nNumberOfBytesToRead
push    edi             ; lpBuffer
push    [ebp+hFile]     ; hFile
call    ds:__imp__ReadFile@20 ; ReadFile(x,x,x,x,x)
Run Code Online (Sandbox Code Playgroud)

迭代返回的缓冲区以搜索行尾,一旦找到它,输入流中的指针就会在找到的poisition之后移动

.text:4AD06A15 loc_4AD06A15:                           
.text:4AD06A15                 cmp     [ebp+NumberOfBytesRead], 3
.text:4AD06A19                 jl      short loc_4AD06A2D
.text:4AD06A1B                 mov     al, [esi]
.text:4AD06A1D                 cmp     al, 0Ah
.text:4AD06A1F                 jz      loc_4AD06BCF
.text:4AD06A25
.text:4AD06A25 loc_4AD06A25:                           
.text:4AD06A25                 cmp     al, 0Dh
.text:4AD06A27                 jz      loc_4AD06D14
.text:4AD06A2D
.text:4AD06A2D loc_4AD06A2D:                           
.text:4AD06A2D                 movzx   eax, byte ptr [esi]
.text:4AD06A30                 cmp     byte ptr _DbcsLeadCharTable[eax], bl
.text:4AD06A36                 jnz     loc_4AD12018
.text:4AD06A3C                 dec     [ebp+NumberOfBytesRead]
.text:4AD06A3F                 inc     esi
.text:4AD06A40
.text:4AD06A40 loc_4AD06A40:                           
.text:4AD06A40                 cmp     [ebp+NumberOfBytesRead], ebx
.text:4AD06A43                 jg      short loc_4AD06A15

.text:4AD06BCF loc_4AD06BCF:                          
.text:4AD06BCF                 cmp     byte ptr [esi+1], 0Dh
.text:4AD06BD3                 jnz     loc_4AD06A25
.text:4AD06BD9                 jmp     loc_4AD06D1E

.text:4AD06D14 loc_4AD06D14:                           
.text:4AD06D14                 cmp     byte ptr [esi+1], 0Ah
.text:4AD06D18                 jnz     loc_4AD06A2D
.text:4AD06D1E
.text:4AD06D1E loc_4AD06D1E:                          
.text:4AD06D1E                 mov     eax, [ebp+var_C]
.text:4AD06D21                 mov     [esi+2], bl
.text:4AD06D24                 sub     esi, edi
.text:4AD06D26                 inc     esi
.text:4AD06D27                 inc     esi
.text:4AD06D28                 push    ebx             ; dwMoveMethod
.text:4AD06D29                 push    ebx             ; lpDistanceToMoveHigh
.text:4AD06D2A                 mov     [ebp+cchMultiByte], esi
.text:4AD06D2D                 add     esi, eax
.text:4AD06D2F                 push    esi             ; lDistanceToMove
.text:4AD06D30                 push    [ebp+hFile]     ; hFile
.text:4AD06D33                 call    ds:__imp__SetFilePointer@16 ; SetFilePointer(x,x,x,x)
Run Code Online (Sandbox Code Playgroud)