Search code examples
fish

Why does fish's echo builtin behave differently from bash's builtin?


Given a function bar that runs echo bar every second forever, I expect bar | cat to print one line every second. It does not. It doesn't print anything until I hit ^C. There seems to be some superfluous buffering going on.

yes | cat works just fine. bar | cat works as expected in bash.

As to why this matters, please see this other question.


Solution

  • We have a command with a builtin in a pipeline:

    echo bar | cat
    

    bash forks new processes to execute builtins in pipelines. So there's two new processes: one for cat, the other to run the builtin echo. The processes are created with pipes connecting them, and they run independently.

    fish always runs builtins directly, without creating new processes for them. So it executes echo bar, then sends the output to cat. The input and output for builtins and functions (but not external commands) is fully buffered. We're working on removing that limitation, but we're not there yet.

    The bash behavior may seem more desirable in this case, but there's a heavy price for it, which is that builtins and functions do different things (or just don't work) inside pipelines. Example:

    echo foo | read some_var ; echo $some_var
    

    In fish, this will set some_var=foo, as you would expect. In bash, this silently fails: the assignment "works," but only within the transient process spawned to execute the read builtin, so the value isn't visible to the next command. This should give you some appreciation for why fish always runs builtins directly.