Eureka
I think I got it, thanks to OJBaker's excellent thread:
How Set/p works Here are OJBaker's rules (with one small correction)
A) Reading characters:Characters are read from the input stream and put in a character buffer until one of three conditions is true:
[1]: The 2 byte end-of-line sequence is read, either CRLF or LFCR.
[2]: There are 1023 characters in the char buffer. (buffer full)
[3]: A timeout occurs
B) Processing the char buffer:- All control-characters from the end of the char buffer are discarded (possible data loss)
- If there is a NUL character in the char buffer, then all characters following the first NUL in the char buffer
will be discarded (data loss)
C) Moving from char buffer to Var:- If char buffer is empty report error condition: Set errorlevel to 1 (meaning No value entered)
- If char buffer is not empty: Move the string from char buffer to the variable named in the set/p command.
D) Set/p is done and returns control to the batch file.OJBakker states he mostly tested with redirection. But then jeb tested with pipes and got slightly different results, though not fully explained.
I think OJBakker's rules are almost perfect except:
During Step A) - When SET /P is reading piped data it does NOT stop reading from the input stream when CRLF or LFCR is read. Condition [1] only applies if reading from a redirected file or directly from the console.
- There is no timeout condition when reading directly from the console. Condition [3] only applies when reading from redirected or piped input.
During Step B) - All characters after the first occurrence of CRLF or LFCR or NUL are discarded.
- Any remaining trailing control characters are also discarded.
This accounts for nearly all observed behavior:
The fact that condition 1 does not apply to piped data explains the original problem cited in this thread.
The timeout condition explains why introducing a delay between each ECHO on the left causes the piped data to be read properly. The trick is in getting the minimum delay that always produces correct results.
And here is some interesting code that shows how a piped version can give the correct result without introducing a delay. Each value is printed normally with trailing CRLF. The trick is to follow each value with fill data such that the length of value + CRLF + filler is exactly 1023 bytes.
left.batCode: Select all
@echo off
setlocal enableDelayedExpansion
set ^"LF=^
^" Empty blank line above is critical - DO NO REMOVE
for /f %%A in ('copy /Z "%~dpf0" nul') do set "CR=%%A"
set "buffer="
for /l %%N in (1 1 1200) do set "buffer=!buffer!."
for /l %%A in (1 1 20) do (
set "val=line%%A!CR!!LF!!buffer!"
echo !val:~0,%1!
)
exit /b
right.batCode: Select all
@echo off
setlocal enableDelayedExpansion
for /l %%N in (1 1 20) do (
set "var="
set /p "var="
echo [!var!]
)
exit /b
When left.bat writes in chunks of 1023 (1021 + CRLF from ECHO), then everything works great!Code: Select all
C:\test> left 1021 | right
[line1]
[line2]
[line3]
[line4]
[line5]
[line6]
[line7]
[line8]
[line9]
[line10]
[line11]
[line12]
[line13]
[line14]
[line15]
[line16]
[line17]
[line18]
[line19]
[line20]
But when left.bat writes too many or too few bytes, then things quickly get out of synch:Code: Select all
C:\test> left 1020 | right
[line1]
[line2]
[ine3]
[ne4]
[e5]
[6]
[]
[
......................................................... data removed to shrink line ....................................................................]
[........................................................ data removed to shrink line .....................................................................]
[........................................................ data removed to shrink line ....................................................................]
[........................................................ data removed to shrink line ...................................................................]
[........................................................ data removed to shrink line ..................................................................]
[........................................................ data removed to shrink line .................................................................]
[........................................................ data removed to shrink line ................................................................]
[........................................................ data removed to shrink line ...............................................................]
[........................................................ data removed to shrink line ..............................................................]
[........................................................ data removed to shrink line .............................................................]
[........................................................ data removed to shrink line ............................................................]
[........................................................ data removed to shrink line ...........................................................]
[........................................................ data removed to shrink line ..........................................................]
C:\test> left 1022 | right
[line1]
[
line2]
[]
[.]
[..]
[...]
[....]
[.....]
[......]
[.......]
[........]
[.........]
[..........]
[...........]
[............]
[.............]
[..............]
[...............]
[................]
[.................]
Note that the CRLF is not needed at the end of the 1023 byte chunk. A slight change to left.bat demonstrates:
left2.batCode: Select all
@echo off
setlocal enableDelayedExpansion
set ^"LF=^
^" Empty blank line above is critical - DO NO REMOVE
for /f %%A in ('copy /Z "%~dpf0" nul') do set "CR=%%A"
set "buffer="
for /l %%N in (1 1 1200) do set "buffer=!buffer!."
for /l %%A in (1 1 20) do (
set "val=line%%A!CR!!LF!!buffer!"
<nul set /p "=!val:~0,%1!"
)
exit /b
When writing too few bytes, it first seems that it works, but that is only because SET /P is slow, introducing enough delay for the timeout to kick in:
Code: Select all
C:\test>left2 1022|right
[line1]
[line2]
[line3]
[line4]
[line5]
[line6]
[line7]
[line8]
[line9]
[line10]
[line11]
[line12]
[line13]
[line14]
[line15]
[line16]
[line17]
[line18]
[line19]
[line20]
If output of left2 is captured in a file, and then the file is piped into right, then the problems are manifested:
Code: Select all
C:\test> left2 1022 >test1022.txt
C:\test> type test1022.txt | right
[line1]
[ine2]
[ne3]
[e4]
[5]
[]
[
................................................ data removed to shrink line ..............................................................line8]
[............................................... data removed to shrink line ...............................................................line9]
[............................................... data removed to shrink line ..............................................................line10]
[............................................... data removed to shrink line .............................................................line11]
[............................................... data removed to shrink line ............................................................line12]
[............................................... data removed to shrink line ...........................................................line13]
[............................................... data removed to shrink line ..........................................................line14]
[............................................... data removed to shrink line .........................................................line15]
[............................................... data removed to shrink line ........................................................line16]
[............................................... data removed to shrink line .......................................................line17]
[............................................... data removed to shrink line ......................................................line18]
[............................................... data removed to shrink line .....................................................line19]
[............................................... data removed to shrink line ....................................................line20]
[............................................... data removed to shrink line ...................................................]
C:\test> left2 1024 >test1024.txt
C:\test> type test1024.txt | right
[line1]
[.line2]
[..line3]
[...line4]
[....line5]
[.....line6]
[......line7]
[.......line8]
[........line9]
[.........line10]
[..........line11]
[...........line12]
[............line13]
[.............line14]
[..............line15]
[...............line16]
[................line17]
[.................line18]
[..................line19]
[...................line20]
But if the correct length of 1023 is output, then everything works:
Code: Select all
C:\test> left2 1023 > test1023.txt
C:\test> type test1023.txt | right
[line1]
[line2]
[line3]
[line4]
[line5]
[line6]
[line7]
[line8]
[line9]
[line10]
[line11]
[line12]
[line13]
[line14]
[line15]
[line16]
[line17]
[line18]
[line19]
[line20]
I'm happy now because at least the mystery is solved
Well, actually I'm still pissed because MicroSoft made SET /P pretty much worthless with pipes.
Dave Benham