On Thu, 9 Oct 2025 at 02:19, Pourko via Bug reports for the GNU Bourne Again SHell <[email protected]> wrote:
> Now, if only we had a way to say: read -d "" --noblock > Yeah, and it would help if « read -t0 -n0 » worked consistently with other timeout values too. «read» treating zero timeout as meaning hopefully-non-destructive look-ahead was added in the weekly snapshot on 2008-09-04 (commit:48ff544772dfa6b8fa3dda69361a5d64a9b0cbcd). Before that, a zero timeout was always an error. In hindsight I think this was a suboptimal design choice; I would have preferred it to be treated like O_NONBLOCK. «read» treating zero size as meaning non-destructive (but less portable) look-ahead was added in the weekly snapshot on 2017-06-30 (commit:9952f68c0817d518471ed88090c79dbc761b96de). In this case 0 returned by the «read» syscall means “no error” rather than EOF, and the «read» built-in usefully returns 0 in this case. However this depends on how the «read» syscall treats a zero «count» parameter. (Some kernels interpret it to mean return 0 if at least one byte is already available, and otherwise either wait until a byte is available, or return EAGAIN if opened with O_NONBLOCK. Other kernels return 0 immediately without checking for available input, presumably on the theory that zero bytes are already available.) Before this change, no attempt was made to check whether input was available, and the return status was effectively random. Currently, very short timeouts (less than about 30µs on my machine) always result in timing out, even when data is already available. This makes them largely indistinguishable from a zero timeout, at least on systems that can report EAGAIN from a zero-sized non-blocking read. I'm guessing this is because this corresponds to the (very) small delay between setting an alarm timer, and then initiating a read. Since this amounts to a race condition, fixing it can't break anything that wasn't already broken. The question is whether an actual zero timeout should be treated the same as (but faster than) a very short timeout, and whether zero-sized read should be forced to behave as a look-ahead even on kernels that don't directly support it? The current behaviour runs counter to reasonable expectations based on time-limited I/O in other environments: absent very hard real-time considerations, a time limit on an IO operation should be about the data in transit to or from the host, rather than about the time it takes to transfer data out of or into the process that's making the request. (And when hard real-time limits apply, I wouldn't want the kernel copying large blobs into the process's address space while the timer is still running.) In particular, a zero timeout on read should simply mean “give me what you already have, without waiting for any more to become available”; it should not mean “give up before giving me stuff that's already available, based on some implementation race condition”. Before I go on, I acknowledge that there's now 17 years of compatibility precedent, and therefore simply “fixing” the behaviour of «read -t0» could cause problems to existing scripts. Currently when «read» is given «-t0», both «-n» and «-d» are ignored, so does anyone think any of the following proposed changes would break existing working scripts? 1. Explicitly define the behaviour or «read -t0 -N0» as a non-destructive lookahead, while deprecating «read -t0» without «-N0» for this purpose. 2. Alter «read -t0 -N$count» to place up «$count» bytes of input in «REPLY», but only from what is already available. In particular, when reading from a pipe, it consumes exactly the whole content of the kernel's pipe buffer (½KiB for POSIX; 4KiB or one vmpage for Linux) provided that «$count» is large enough). 3. Alter «read -t0 -d$'\n'» to place a line of input in «REPLY» if one is already available. If only a partial line of input is available (missing a newline), then: (3a) when reading from a tty that's already in ICANON (“cooked”) mode, «REPLY» is left empty without consuming any input; and (3b) in all other cases, any input that is consumed is placed in «REPLY», but the consumption is unspecified and explicitly documented as “may change”. 4. Alter «read -t0 -n0 -d$'\n'» to perform look-ahead while remaining in ICANON mode. 5. The behaviour «read -t$non_zero -N$count» and «read -t$non_zero -d$'\n'» be stabilized to be consistent with (1) through (4); in particular, this means that any SIGALRM timer should not start until after a non-blocking read has reported «EAGAIN». 6. The behaviour of «-d» with something other than $'\n' be made as consistent as possible with (1) through (5) (As an implementation detail, when reading from a tty, it would be nice to use «termios.c_cc[VEOL]» for «-d» on systems that support it.) If there's interest, I'll see if I can work up suitable patches. Question: should timeouts be rounded up or down when using the tty timer facilities («termios.c_cc[VTIME]»)? Second question: if we're at EOF on a plain file, should using a non-zero timer wait for more data to be added to the file? -Martin PS: Tentative related proposals: [a] if the stdin is open on a directory, use the «getdents» syscall instead of «read». Follow-up question: what format should be used for the returned data? Should this be controlled by a variable such as «BASH_READDIR_FMT»? (Perhaps as a format string similar to either find -printf or stat(1).) Or should «read» just ignore IFS and put one field in each named variable or array element in some fixed order? [b] if the stdin is a socket, use the «recvmsg» syscall instead of «read», which honours the sender's packetisation, rather than relying on delimiter bytes. Consider adding a shopt to cause «|» and «|&» to create AF_LOCAL sockets rather than pipes.
