由于未初始化的数据指针,无法插入SET/P吗? [英] Piping into SET /P fails due to uninitialised data pointer?
问题描述
假设我们有一个文本文件sample.txt
:
Supposing we have got a text file sample.txt
:
one
two
...
现在我们要删除第一行:
Now we want to remove the first line:
two
...
一种快速的方法是使用输入重定向set /P
和findstr
1 (我知道还有其他使用more
或for /F
的方法,但是让我们忘记关于它们的信息):
A quick way to do that is to use input redirection, set /P
and findstr
1 (I know there are other ways using more
or for /F
, but let us forget about them for now):
@echo off
< "sample.txt" (
set /P =""
findstr "^"
)
输出将与预期的一样.
但是,当我用type
和管道|
替换输入重定向<
时,为什么输出为空:
However, why is the output empty when I replace the input redirection <
by type
and a pipe |
:
@echo off
type "sample.txt" | (
set /P =""
findstr "^"
)
当我用pause > nul
替换set /P =""
时,输出是我所期望的-输出输出文件,但缺少第一行的第一个字符(因为它被pause
占用).但是,为什么set /P
似乎消耗了所有内容,而不是像重定向<
方法那样只消耗了第一行?那是个错误吗?
When I replace set /P =""
by pause > nul
, the output is what I expect -- the input file is output but with the first character of the first line missing (as it is consumed by pause
). But why does set /P
seem to consume everything instead of only the first line like it does with the redirection <
approach? Is that a bug?
在我看来,set /P
未能充分初始化指向管道数据的读取指针.
To me it looks like set /P
fails to adequately initialise the reading pointer to the piped data.
我在Windows 7和Windows 10上看到了这种奇怪的行为.
I watched that strange behaviour on Windows 7 and on Windows 10.
它变得更加不可思议:当多次调用包含管道的脚本时,例如通过像for /L %I in (1,1,1000) do @pipe.bat
这样的循环,而输入文件包含约十五行或更多行,有时(千分之几)返回输入文件的片段;每次该片段都是完全相同的;似乎一开始总是缺少80个字节.
It becomes even more weird: when calling the script containing the pipe multiple times, for instance by a loop like for /L %I in (1,1,1000) do @pipe.bat
, and the input file contains about fifteen lines or more, sometimes (a few times out of thousand) a fragment of the input file is returned; that fragment is exactly the same each time; it seems that there are always 80 bytes missing at the beginning.
1)findstr
挂起,以防最后一行没有被换行符终止,因此让我们假设在那里.
1) findstr
hangs in case the last line is not terminated by a line-break, so let us assume such is there.
推荐答案
检索数据时,set /p
尝试用stdin的数据填充1023字符缓冲区(如果有).读操作结束后,将搜索行的第一行,一旦找到行的第一行(或到达缓冲区的末尾),将调用SetFilePointer
API以在输入的行末之后重新定位输入流指针.读行.这样,下一次读取操作将在读取行之后开始检索数据.
When retrieving data, the set /p
tries to fill a 1023 character buffer (if they are available) with data from stdin. Once this read operation has ended, the first end of line is searched and once it has been found (or the end of the buffer has been reached), the SetFilePointer
API is called to reposition the input stream pointer after the end of the read line. This way the next read operation will start to retreive data after the read line.
当磁盘文件与输入流相关联时,此方法可完美运行,但正如Microsoft在
This works flawlessly when a disk file is associated with the input stream, but as Microsoft states in the SetFilePointer
documentation
hFile 参数必须引用存储在搜寻设备上的文件; 例如磁盘卷.调用 SetFilePointer 函数,使用 处理非寻找设备,例如管道或通讯设备 即使 SetFilePointer 函数可能不支持该设备 不返回错误. SetFilePointer 函数的行为 这种情况是不确定的.
The hFile parameter must refer to a file stored on a seeking device; for example, a disk volume. Calling the SetFilePointer function with a handle to a non-seeking device such as a pipe or a communications device is not supported, even though the SetFilePointer function may not return an error. The behavior of the SetFilePointer function in this case is undefined.
正在发生的事情是,虽然没有产生任何错误,但在将stdin与管道相关联,指针未移回并且1023个字节(或可用的读取字节数)的情况下,重新定位读取指针的调用失败继续阅读.
What is happening is that, while not generating any error, the call to reposition the read pointer fails when stdin is associated with a pipe, the pointer is not moved back and the 1023 bytes (or the number of available read bytes) keep read.
编辑以响应Aacini的请求
edited in response to Aacini request
set
命令由eSet
函数处理,该函数调用SetWork
以确定将执行哪种类型的set
命令.
The set
command is processed by the eSet
function, who calls SetWork
to determine which type of set
command will be executed.
由于它是set /p
,因此调用SetPromptUser
函数,并从该函数中调用ReadBufFromInput
函数
As it is a set /p
the SetPromptUser
function is called and from this function the ReadBufFromInput
function is called
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)
it requests 3FFh
(1023) characters from standard input handle (0FFFFFFF6h
= -10
= STD_INPUT_HANDLE
)
ReadBufFromInput
使用GetFileType
API来确定是从控制台还是从文件读取
ReadBufFromInput
uses the GetFileType
API to determine if it should read from the console or from a file
; 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
and, as in this case it is a pipe (GetFileType
returns 3
) the code jumps to the ReadBufFromFile
function
; 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
此函数将调用ReadFile
API函数以检索指示的字符数
This function will call the ReadFile
API function to retrive the indicated number of characters
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)
迭代返回的缓冲区以寻找行尾,一旦找到行末,输入流中的指针将在找到位置后移动
The returned buffer is iterated in search of an end of line, and once it is found, the pointer in the input stream is moved after the found 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)
这篇关于由于未初始化的数据指针,无法插入SET/P吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!