0

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 ?

1
  • @zevzek You are right, the behavior is not different that cat | ls. I think what I don't understand is what it means to own the stdin, 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, 2021 at 16:06

2 Answers 2

4

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.

10
  • @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, exec ls then wait, cat behaves likes it is buffering ?
    – cassepipe
    Oct 19, 2021 at 16:12
  • @ChrisDown, mm. The line buffering on input is the same for both cat and tr 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 with stty, sure. Of course, that could be different on some other system I'm not familiar with.
    – ilkkachu
    Oct 19, 2021 at 16:13
  • @cassepipe, that sounds like a different question, and one what would require seeing the code.
    – ilkkachu
    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.
    – ilkkachu
    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 one read() or many. Thanks.
    – ilkkachu
    Oct 20, 2021 at 19:34
2

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .