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
?

- 180
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.

- 138,973
-
@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 ? – cassepipe Oct 19 '21 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. – ilkkachu Oct 19 '21 at 16:13 -
@cassepipe, that sounds like a different question, and one what would require seeing the code. – ilkkachu Oct 19 '21 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. – ilkkachu Oct 20 '21 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. – ilkkachu Oct 20 '21 at 19:34 -
@zevzek It seems like you disagree with the answer I accepted. Would you mind crafting a more elaborate answer or pointing to documentation ? – cassepipe Oct 21 '21 at 16:13
-
@ilkkachu You asked to see some code so there is a naive version in C that does not behave the same (although cat is also connected to a pipe) : https://paste.gg/p/anonymous/50699b0b9ed74ceabb7718393ac7ad68 – cassepipe Oct 21 '21 at 18:04
-
@cassepipe, it looks to me you have
cat
andls
in reverse there (i.e. you're doingcat | ls
, notls | cat
). Also, the parent process has a copy of the write end of the pipe, socat
never sees EOF and never exits. Try e.g. what happens if you add the-n
switch tocat
. And then swap the programs around andclose(pipe_fd[WRITE_END]);
in the parent process before thewaitpid()
calls. Also you should check the return status of theexec()
calls, or add a call to_exit()
after them to make sure the children don't carry on running the rest of the code if theexec()
s fail. – ilkkachu Oct 21 '21 at 19:42 -
@cassepipe, on the other hand, if you did
cat | ls
on purpose, and expectedcat
to get a SIGPIPE and die, it doesn't because the parent also holds a copy of the read end of the pipe, so SIGPIPE doesn't get sent. So, close that on the parent too. – ilkkachu Oct 21 '21 at 19:52 -
@ilkkachu I was indeed trying to reproduce
cat | ls
. As you said I closed both pipe ends in the parent, and I do get the same behaviour. Thanks for the help. – cassepipe Oct 22 '21 at 09:26
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.

- 781
- 1
- 4
- 12
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 ? – cassepipe Oct 21 '21 at 16:06