OK, running a similar test with instrumentation gets: $ time ( trap ' echo BANG ' SIGALRM ; while :; do printf TEST; sleep .00$((1+RANDOM%2)); echo; done | for ((r=100000 ;r>0; --r)) do line= ; read -t .002 line; rc=$?; [[ $line = TEST ]] ; echo "STATUS $rc $? $line" ; done ) |& sort | uniq -c
1 +# Hit read timeout SIGALRM, run from line 699 in ./read.def #+ STATUS 142 1 TES 1 +# Hit read timeout SIGALRM, run from line 72 in zread.c #+ STATUS 142 1 2 +# Hit read timeout SIGALRM, run from line 865 in ./read.def #+ STATUS 142 1 T 1 +# Hit read timeout SIGALRM, run from line 865 in ./read.def #+ STATUS 142 1 TES 16 +# Hit read timeout SIGALRM, run from line 913 in ./read.def #+ STATUS 142 0 TEST 1721 +# Hit read timeout SIGALRM, run from line 913 in ./read.def #+ STATUS 142 1 3 STATUS 0 0 TEST 32649 STATUS 0 1 34367 STATUS 142 0 TEST 31235 STATUS 142 1 2 STATUS 142 1 EST 2 STATUS 142 1 T real 3m21.411s user 1m14.054s sys 1m46.493s That's to say, that out of 100000 reads: - 0.003% had both `read` returning 0 and filling var with TEST. - 34.4% had `read returning non-zero but filling var with TEST. - 32.6% had `read` returning 0 but leaving var empty (which is presumably the delayed bare newline after previously filling the var with TEST but returning 142). - 1.7% were handled by `check_read_timeout()`, and all had read returning a status of 142 (and leaving var empty). I note that 1721+32649-34367=3 so I infer that the last 3 cases are interconnected: Of that 1.7% almost all were handled by this section of read.def: /* I don't think this clause ever tests true. */ if (skip_ctlnul && saw_escape && i == 1 && input_string[0] == CTLNUL) saw_escape = 0; /* Avoid dequoting bare CTLNUL */ input_string[i] = '\0'; * check_read_timeout ();* #if defined (READLINE) if (edit) free (rlbuf); #endif -Martin PS: I've since run other tests that confirm the association I noted above, but I didn't keep the output. I'll run them again if anyone wants to see a more precise demonstration.