I know this is a useless command but I would like to understand why, wether in bash
or zsh
, when I enter cat | ls
, cat
will prompt me for input but only but will return after just one line whereas for example </dev/stdin tr -d 'e' | ls
will let me input various line until I hit Ctrl + D
?
2 Answers
The same reason why this only prints anything after 1 second:
(echo abc; sleep 1; echo def) | tr -d e |cat
With stdout redirected to a pipe, most utilities start to buffer the output and only write anything there after they have a full block of data. (With the size of the block being an implementation detail, it's probably some kilobytes.) There are some utilities you can use to disable the buffering, see: Turn off buffering in pipe.
But some implementations of cat
don't do buffering, and write immediately. That includes at least the GNU and Busybox implementations. What plain cat
does with buffering is not specified by POSIX, there's only cat -u
to prevent buffering.
Here, since ls
doesn't read anything from stdin and hence the pipe, it probably finishes and exits faster than you can type anything. So, when cat
/tr
gets around to writing anything, the pipe is already closed, and the writer gets a SIGPIPE and exits. Since cat
here writes what it reads immediately without buffering, it gets the signal immediately after the first input line. On the other hand, since tr
waits to get a full buffer to write anything, the first input line doesn't trigger it. If you entered enough data, tr
would also eventually write it to the pipe, get the signal and exit.
Like you said, piping to ls
is silly, since it doesn't read anything. You could use something like true
or false
instead.
-
@ChrisDown Not sire I fully understans but could this explain why when I try make a program in C that forks, redirect stdout to a pipe, exec
cat
, forks again, execls
then wait,cat
behaves likes it is buffering ? Commented Oct 19, 2021 at 16:12 -
@ChrisDown, mm. The line buffering on input is the same for both
cat
andtr
here. As far as I understand, it also has nothing to do with anything the process itself does for stdin, but with the terminal being set to do line buffering (and editing) before the process sees anything. We could change that withstty
, sure. Of course, that could be different on some other system I'm not familiar with.– ilkkachuCommented Oct 19, 2021 at 16:13 -
@cassepipe, that sounds like a different question, and one what would require seeing the code.– ilkkachuCommented Oct 19, 2021 at 16:14
-
@zevzek, while we're at it, I always had the impression that
setvbuf()
wouldn't do anything on an input stream, but is there something that explicitly says what should happen? Both cppreference and the POSIX text only say vaguely that "many implementations only provide line buffering on input from terminal devices" and e.g. the man page on Debian doesn't seem to say anything.– ilkkachuCommented Oct 20, 2021 at 19:12 -
@zevzek, ah yes, of course. Of course that didn't affect the visible behaviour of my silly earlier test, e.g.
fgets()
still reads until it gets a full line, regardless of if it uses oneread()
or many. Thanks.– ilkkachuCommented Oct 20, 2021 at 19:34
cat
without an argument will read from stdin, waiting until the first line is entered since stdin is line buffered when referring to an interactive device, and output it to its stdout, which is redirected to the stdin of ls
. ls
does not read from stdin at all, so it will simply output the list of files to stdout and exit. Since the ls
-end of the pipe is closed by ls
(cat
's stdout), cat
will also exit.
The same behavior can be observed with any program which doesn't read from stdin and outputs to stdout. For example:
cat | uname -a
Note: </dev/stdin
is superfluous. It will redirect stdin to stdin.
cat | ls
. I think what I don't understand is what it means to own thestdin
, I am not familiar with what that means which is why I did not understand the behavior. I have been answered below that it had to do with buffering but is there more to it ?