{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/jvns-ca/2024-11-29-why-pipes-sometimes-get-stuck-buffering/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "Here’s a niche terminal problem that has bothered me for years but that I never really understood until a few weeks ago. Let’s say you’re running this command to watch for some specific output in a log file:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nIf log lines are being added to the file relatively slowly, the result I’d see is… nothing! It doesn’t matter if there were matches in the log file or not, there just wouldn’t be any output.\n\nI internalized this as “uh, I guess pipes just get stuck sometimes and don’t show me the output, that’s weird”, and I’d handle it by just running `grep thing1 /some/log/file | grep thing2` instead, which would work.\n\nSo as I’ve been doing a terminal deep dive over the last few months I was really excited to finally learn exactly why this happens.\n\n### [why this happens: buffering](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#why-this-happens-buffering)\n\nThe reason why “pipes get stuck” sometimes is that it’s VERY common for programs to buffer their output before writing it to a pipe or file. So the pipe is working fine, the problem is that the program never even wrote the data to the pipe!\n\nThis is for performance reasons: writing all output immediately as soon as you can uses more system calls, so it’s more efficient to save up data until you have 8KB or so of data to write (or until the program exits) and THEN write it to the pipe.\n\nIn this example:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nthe problem is that `grep thing1` is saving up all of its matches until it has 8KB of data to write, which might literally never happen.\n\n### [programs don’t buffer when writing to a terminal](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programs-don-t-buffer-when-writing-to-a-terminal)\n\nPart of why I found this so disorienting is that `tail -f file | grep thing` will work totally fine, but then when you add the second `grep`, it stops working!! The reason for this is that the way `grep` handles buffering depends on whether it’s writing to a terminal or not.\n\nHere’s how `grep` (and many other programs) decides to buffer its output:\n\n- Check if stdout is a terminal or not using the `isatty` function\n  - If it’s a terminal, use line buffering (print every line immediately as soon as you have it)\n  - Otherwise, use “block buffering” – only print data if you have at least 8KB or so of data to print\n\nSo if `grep` is writing directly to your terminal then you’ll see the line as soon as it’s printed, but if it’s writing to a pipe, you won’t.\n\nOf course the buffer size isn’t always 8KB for every program, it depends on the implementation. For `grep` the buffering is handled by libc, and libc’s buffer size is defined in the `BUFSIZ` variable. [Here’s where that’s defined in glibc](https://github.com/bminor/glibc/blob/c69e8cccaff8f2d89cee43202623b33e6ef5d24a/libio/stdio.h#L100).\n\n(as an aside: “programs do not use 8KB output buffers when writing to a terminal” isn’t, like, a law of terminal physics, a program COULD use an 8KB buffer when writing output to a terminal if it wanted, it would just be extremely weird if it did that, I can’t think of any program that behaves that way)\n\n### [commands that buffer & commands that don’t](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#commands-that-buffer-commands-that-don-t)\n\nOne annoying thing about this buffering behaviour is that you kind of need to remember which commands buffer their output when writing to a pipe.\n\nSome commands that **don’t** buffer their output:\n\n- tail\n- cat\n- tee\n\nI think almost everything else will buffer output, especially if it’s a command where you’re likely to be using it for batch processing. Here’s a list of some common commands that buffer their output when writing to a pipe, along with the flag that disables block buffering.\n\n- grep (`--line-buffered`)\n- sed (`-u`)\n- awk (there’s a `fflush()` function)\n- tcpdump (`-l`)\n- jq (`-u`)\n- tr (`-u`)\n- cut (can’t disable buffering)\n\nThose are all the ones I can think of, lots of unix commands (like `sort`) may or may not buffer their output but it doesn’t matter because `sort` can’t do anything until it finishes receiving input anyway.\n\nAlso I did my best to test both the Mac OS and GNU versions of these but there are a lot of variations and I might have made some mistakes.\n\n### [programming languages where the default “print” statement buffers](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programming-languages-where-the-default-print-statement-buffers)\n\nAlso, here are a few programming language where the default print statement will buffer output when writing to a pipe, and some ways to disable buffering if you want:\n\n- C (disable with `setvbuf`)\n- Python (disable with `python -u`, or `PYTHONUNBUFFERED=1`, or `sys.stdout.reconfigure(line_buffering=False)`, or `print(x, flush=True)`)\n- Ruby (disable with `STDOUT.sync = true`)\n- Perl (disable with `$| = 1`)\n\nI assume that these languages are designed this way so that the default print function will be fast when you’re doing batch processing.\n\nAlso whether output is buffered or not might depend on how you print, for example in C++ `cout << \"hello\\n\"` buffers when writing to a pipe but `cout << \"hello\" << endl` will flush its output.\n\n### [when you press `Ctrl-C` on a pipe, the contents of the buffer are lost](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#when-you-press-ctrl-c-on-a-pipe-the-contents-of-the-buffer-are-lost)\n\nLet’s say you’re running this command as a hacky way to watch for DNS requests to `example.com`, and you forgot to pass `-l` to tcpdump:\n\n```\nsudo tcpdump -ni any port 53 | grep example.com\n```\n\nWhen you press `Ctrl-C`, what happens? In a magical perfect world, what I would *want* to happen is for `tcpdump` to flush its buffer, `grep` would search for `example.com`, and I would see all the output I missed.\n\nBut in the real world, what happens is that all the programs get killed and the output in `tcpdump`’s buffer is lost.\n\nI think this problem is probably unavoidable – I spent a little time with `strace` to see how this works and `grep` receives the `SIGINT` before `tcpdump` anyway so even if `tcpdump` tried to flush its buffer `grep` would already be dead.\n\nAfter a little more investigation, there is a workaround: if you find `tcpdump`’s PID and `kill -TERM $PID`, then tcpdump will flush the buffer so you can see the output. That’s kind of a pain but I tested it and it seems to work.\n\n### [redirecting to a file also buffers](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#redirecting-to-a-file-also-buffers)\n\nIt’s not just pipes, this will also buffer:\n\n```\nsudo tcpdump -ni any port 53 > output.txt\n```\n\nRedirecting to a file doesn’t have the same “`Ctrl-C` will totally destroy the contents of the buffer” problem though – in my experience it usually behaves more like you’d want, where the contents of the buffer get written to the file before the program exits. I’m not 100% sure whether this is something you can always rely on or not.\n\n### [a bunch of potential ways to avoid buffering](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#a-bunch-of-potential-ways-to-avoid-buffering)\n\nOkay, let’s talk solutions. Let’s say you’ve run this command:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nI asked people on Mastodon how they would solve this in practice and there were 5 basic approaches. Here they are:\n\n#### [solution 1: run a program that finishes quickly](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-1-run-a-program-that-finishes-quickly)\n\nHistorically my solution to this has been to just avoid the “command writing to pipe slowly” situation completely and instead run a program that will finish quickly like this:\n\n```\ncat /some/log/file | grep thing1 | grep thing2 | tail\n```\n\nThis doesn’t do the same thing as the original command but it does mean that you get to avoid thinking about these weird buffering issues.\n\n(you could also do `grep thing1 /some/log/file` but I often prefer to use an “unnecessary” `cat`)\n\n#### [solution 2: remember the “line buffer” flag to grep](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-2-remember-the-line-buffer-flag-to-grep)\n\nYou could remember that grep has a flag to avoid buffering and pass it like this:\n\n```\ntail -f /some/log/file | grep --line-buffered thing1 | grep thing2\n```\n\n#### [solution 3: use awk](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-3-use-awk)\n\nSome people said that if they’re specifically dealing with a multiple greps situation, they’ll rewrite it to use a single `awk` instead, like this:\n\n```\ntail -f /some/log/file |  awk '/thing1/ && /thing2/'\n```\n\nOr you would write a more complicated `grep`, like this:\n\n```\ntail -f /some/log/file |  grep -E 'thing1.*thing2'\n```\n\n(`awk` also buffers, so for this to work you’ll want `awk` to be the last command in the pipeline)\n\n#### [solution 4: use `stdbuf`](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-4-use-stdbuf)\n\n`stdbuf` uses LD\\_PRELOAD to turn off libc’s buffering, and you can use it to turn off output buffering like this:\n\n```\ntail -f /some/log/file | stdbuf -o0 grep thing1 | grep thing2\n```\n\nLike any `LD_PRELOAD` solution it’s a bit unreliable – it doesn’t work on static binaries, I think won’t work if the program isn’t using libc’s buffering, and doesn’t always work on Mac OS. Harry Marr has a really nice [How stdbuf works](https://hmarr.com/blog/how-stdbuf-works/) post.\n\n#### [solution 5: use `unbuffer`](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-5-use-unbuffer)\n\n`unbuffer program` will force the program’s output to be a TTY, which means that it’ll behave the way it normally would on a TTY (less buffering, colour output, etc). You could use it in this example like this:\n\n```\ntail -f /some/log/file | unbuffer grep thing1 | grep thing2\n```\n\nUnlike `stdbuf` it will always work, though it might have unwanted side effects, for example `grep thing1`’s will also colour matches.\n\nIf you want to install unbuffer, it’s in the `expect` package.\n\n### [that’s all the solutions I know about!](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#that-s-all-the-solutions-i-know-about)\n\nIt’s a bit hard for me to say which one is “best”, I think personally I’m mostly likely to use `unbuffer` because I know it’s always going to work.\n\nIf I learn about more solutions I’ll try to add them to this post.\n\n### [I’m not really sure how often this comes up](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#i-m-not-really-sure-how-often-this-comes-up)\n\nI think it’s not very common for me to have a program that slowly trickles data into a pipe like this, normally if I’m using a pipe a bunch of data gets written very quickly, processed by everything in the pipeline, and then everything exits. The only examples I can come up with right now are:\n\n- tcpdump\n- `tail -f`\n- watching log files in a different way like with `kubectl logs`\n- the output of a slow computation\n\n### [what if there were an environment variable to disable buffering?](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#what-if-there-were-an-environment-variable-to-disable-buffering)\n\nI think it would be cool if there were a standard environment variable to turn off buffering, like `PYTHONUNBUFFERED` in Python. I got this idea from a [couple](https://blog.plover.com/Unix/stdio-buffering.html) of [blog posts](https://blog.plover.com/Unix/stdio-buffering-2.html) by Mark Dominus in 2018. Maybe `NO_BUFFER` like [NO\\_COLOR](https://no-color.org/)?\n\nThe design seems tricky to get right; Mark points out that NETBSD has [environment variables called `STDBUF`, `STDBUF1`, etc](https://man.netbsd.org/setbuf.3) which gives you a ton of control over buffering but I imagine most developers don’t want to implement many different environment variables to handle a relatively minor edge case.\n\nI’m also curious about whether there are any programs that just automatically flush their output buffers after some period of time (like 1 second). It feels like it would be nice in theory but I can’t think of any program that does that so I imagine there are some downsides.\n\n### [stuff I left out](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#stuff-i-left-out)\n\nSome things I didn’t talk about in this post since these posts have been getting pretty long recently and seriously does anyone REALLY want to read 3000 words about buffering?\n\n- the difference between line buffering and having totally unbuffered output\n- how buffering to stderr is different from buffering to stdout\n- this post is only about buffering that happens **inside the program**, your operating system’s TTY driver also does a little bit of buffering sometimes\n- other reasons you might need to flush your output other than “you’re writing to a pipe”\n",
  "authors": [],
  "captured_at": "2026-09-19T21:30:23.395188495+00:00",
  "category": "programming",
  "content_html": "<p>Here’s a niche terminal problem that has bothered me for years but that I never really understood until a few weeks ago. Let’s say you’re running this command to watch for some specific output in a log file:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | grep thing1 | grep thing2\n</span></code></pre>\n<p>If log lines are being added to the file relatively slowly, the result I’d see is… nothing! It doesn’t matter if there were matches in the log file or not, there just wouldn’t be any output.</p>\n<p>I internalized this as “uh, I guess pipes just get stuck sometimes and don’t show me the output, that’s weird”, and I’d handle it by just running <code>grep thing1 /some/log/file | grep thing2</code> instead, which would work.</p>\n<p>So as I’ve been doing a terminal deep dive over the last few months I was really excited to finally learn exactly why this happens.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#why-this-happens-buffering\">why this happens: buffering</a></h3>\n<p>The reason why “pipes get stuck” sometimes is that it’s VERY common for programs to buffer their output before writing it to a pipe or file. So the pipe is working fine, the problem is that the program never even wrote the data to the pipe!</p>\n<p>This is for performance reasons: writing all output immediately as soon as you can uses more system calls, so it’s more efficient to save up data until you have 8KB or so of data to write (or until the program exits) and THEN write it to the pipe.</p>\n<p>In this example:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | grep thing1 | grep thing2\n</span></code></pre>\n<p>the problem is that <code>grep thing1</code> is saving up all of its matches until it has 8KB of data to write, which might literally never happen.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programs-don-t-buffer-when-writing-to-a-terminal\">programs don’t buffer when writing to a terminal</a></h3>\n<p>Part of why I found this so disorienting is that <code>tail -f file | grep thing</code> will work totally fine, but then when you add the second <code>grep</code>, it stops working!! The reason for this is that the way <code>grep</code> handles buffering depends on whether it’s writing to a terminal or not.</p>\n<p>Here’s how <code>grep</code> (and many other programs) decides to buffer its output:</p>\n<ul>\n<li>Check if stdout is a terminal or not using the <code>isatty</code> function\n<ul>\n<li>If it’s a terminal, use line buffering (print every line immediately as soon as you have it)</li>\n<li>Otherwise, use “block buffering” – only print data if you have at least 8KB or so of data to print</li>\n</ul>\n</li>\n</ul>\n<p>So if <code>grep</code> is writing directly to your terminal then you’ll see the line as soon as it’s printed, but if it’s writing to a pipe, you won’t.</p>\n<p>Of course the buffer size isn’t always 8KB for every program, it depends on the implementation. For <code>grep</code> the buffering is handled by libc, and libc’s buffer size is defined in the <code>BUFSIZ</code> variable. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/bminor/glibc/blob/c69e8cccaff8f2d89cee43202623b33e6ef5d24a/libio/stdio.h#L100\">Here’s where that’s defined in glibc</a>.</p>\n<p>(as an aside: “programs do not use 8KB output buffers when writing to a terminal” isn’t, like, a law of terminal physics, a program COULD use an 8KB buffer when writing output to a terminal if it wanted, it would just be extremely weird if it did that, I can’t think of any program that behaves that way)</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#commands-that-buffer-commands-that-don-t\">commands that buffer &amp; commands that don’t</a></h3>\n<p>One annoying thing about this buffering behaviour is that you kind of need to remember which commands buffer their output when writing to a pipe.</p>\n<p>Some commands that <strong>don’t</strong> buffer their output:</p>\n<ul>\n<li>tail</li>\n<li>cat</li>\n<li>tee</li>\n</ul>\n<p>I think almost everything else will buffer output, especially if it’s a command where you’re likely to be using it for batch processing. Here’s a list of some common commands that buffer their output when writing to a pipe, along with the flag that disables block buffering.</p>\n<ul>\n<li>grep (<code>--line-buffered</code>)</li>\n<li>sed (<code>-u</code>)</li>\n<li>awk (there’s a <code>fflush()</code> function)</li>\n<li>tcpdump (<code>-l</code>)</li>\n<li>jq (<code>-u</code>)</li>\n<li>tr (<code>-u</code>)</li>\n<li>cut (can’t disable buffering)</li>\n</ul>\n<p>Those are all the ones I can think of, lots of unix commands (like <code>sort</code>) may or may not buffer their output but it doesn’t matter because <code>sort</code> can’t do anything until it finishes receiving input anyway.</p>\n<p>Also I did my best to test both the Mac OS and GNU versions of these but there are a lot of variations and I might have made some mistakes.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programming-languages-where-the-default-print-statement-buffers\">programming languages where the default “print” statement buffers</a></h3>\n<p>Also, here are a few programming language where the default print statement will buffer output when writing to a pipe, and some ways to disable buffering if you want:</p>\n<ul>\n<li>C (disable with <code>setvbuf</code>)</li>\n<li>Python (disable with <code>python -u</code>, or <code>PYTHONUNBUFFERED=1</code>, or <code>sys.stdout.reconfigure(line_buffering=False)</code>, or <code>print(x, flush=True)</code>)</li>\n<li>Ruby (disable with <code>STDOUT.sync = true</code>)</li>\n<li>Perl (disable with <code>$| = 1</code>)</li>\n</ul>\n<p>I assume that these languages are designed this way so that the default print function will be fast when you’re doing batch processing.</p>\n<p>Also whether output is buffered or not might depend on how you print, for example in C++ <code>cout &lt;&lt; &quot;hello\\n&quot;</code> buffers when writing to a pipe but <code>cout &lt;&lt; &quot;hello&quot; &lt;&lt; endl</code> will flush its output.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#when-you-press-ctrl-c-on-a-pipe-the-contents-of-the-buffer-are-lost\">when you press <code>Ctrl-C</code> on a pipe, the contents of the buffer are lost</a></h3>\n<p>Let’s say you’re running this command as a hacky way to watch for DNS requests to <code>example.com</code>, and you forgot to pass <code>-l</code> to tcpdump:</p>\n<pre><code><span class=\"code-snippet\">sudo tcpdump -ni any port 53 | grep example.com\n</span></code></pre>\n<p>When you press <code>Ctrl-C</code>, what happens? In a magical perfect world, what I would <em>want</em> to happen is for <code>tcpdump</code> to flush its buffer, <code>grep</code> would search for <code>example.com</code>, and I would see all the output I missed.</p>\n<p>But in the real world, what happens is that all the programs get killed and the output in <code>tcpdump</code>’s buffer is lost.</p>\n<p>I think this problem is probably unavoidable – I spent a little time with <code>strace</code> to see how this works and <code>grep</code> receives the <code>SIGINT</code> before <code>tcpdump</code> anyway so even if <code>tcpdump</code> tried to flush its buffer <code>grep</code> would already be dead.</p>\n<p>After a little more investigation, there is a workaround: if you find <code>tcpdump</code>’s PID and <code>kill -TERM $PID</code>, then tcpdump will flush the buffer so you can see the output. That’s kind of a pain but I tested it and it seems to work.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#redirecting-to-a-file-also-buffers\">redirecting to a file also buffers</a></h3>\n<p>It’s not just pipes, this will also buffer:</p>\n<pre><code><span class=\"code-snippet\">sudo tcpdump -ni any port 53 &gt; output.txt\n</span></code></pre>\n<p>Redirecting to a file doesn’t have the same “<code>Ctrl-C</code> will totally destroy the contents of the buffer” problem though – in my experience it usually behaves more like you’d want, where the contents of the buffer get written to the file before the program exits. I’m not 100% sure whether this is something you can always rely on or not.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#a-bunch-of-potential-ways-to-avoid-buffering\">a bunch of potential ways to avoid buffering</a></h3>\n<p>Okay, let’s talk solutions. Let’s say you’ve run this command:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | grep thing1 | grep thing2\n</span></code></pre>\n<p>I asked people on Mastodon how they would solve this in practice and there were 5 basic approaches. Here they are:</p>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-1-run-a-program-that-finishes-quickly\">solution 1: run a program that finishes quickly</a></h4>\n<p>Historically my solution to this has been to just avoid the “command writing to pipe slowly” situation completely and instead run a program that will finish quickly like this:</p>\n<pre><code><span class=\"code-snippet\">cat /some/log/file | grep thing1 | grep thing2 | tail\n</span></code></pre>\n<p>This doesn’t do the same thing as the original command but it does mean that you get to avoid thinking about these weird buffering issues.</p>\n<p>(you could also do <code>grep thing1 /some/log/file</code> but I often prefer to use an “unnecessary” <code>cat</code>)</p>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-2-remember-the-line-buffer-flag-to-grep\">solution 2: remember the “line buffer” flag to grep</a></h4>\n<p>You could remember that grep has a flag to avoid buffering and pass it like this:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | grep --line-buffered thing1 | grep thing2\n</span></code></pre>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-3-use-awk\">solution 3: use awk</a></h4>\n<p>Some people said that if they’re specifically dealing with a multiple greps situation, they’ll rewrite it to use a single <code>awk</code> instead, like this:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file |  awk &#39;/thing1/ &amp;&amp; /thing2/&#39;\n</span></code></pre>\n<p>Or you would write a more complicated <code>grep</code>, like this:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file |  grep -E &#39;thing1.*thing2&#39;\n</span></code></pre>\n<p>(<code>awk</code> also buffers, so for this to work you’ll want <code>awk</code> to be the last command in the pipeline)</p>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-4-use-stdbuf\">solution 4: use <code>stdbuf</code></a></h4>\n<p><code>stdbuf</code> uses LD_PRELOAD to turn off libc’s buffering, and you can use it to turn off output buffering like this:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | stdbuf -o0 grep thing1 | grep thing2\n</span></code></pre>\n<p>Like any <code>LD_PRELOAD</code> solution it’s a bit unreliable – it doesn’t work on static binaries, I think won’t work if the program isn’t using libc’s buffering, and doesn’t always work on Mac OS. Harry Marr has a really nice <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hmarr.com/blog/how-stdbuf-works/\">How stdbuf works</a> post.</p>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-5-use-unbuffer\">solution 5: use <code>unbuffer</code></a></h4>\n<p><code>unbuffer program</code> will force the program’s output to be a TTY, which means that it’ll behave the way it normally would on a TTY (less buffering, colour output, etc). You could use it in this example like this:</p>\n<pre><code><span class=\"code-snippet\">tail -f /some/log/file | unbuffer grep thing1 | grep thing2\n</span></code></pre>\n<p>Unlike <code>stdbuf</code> it will always work, though it might have unwanted side effects, for example <code>grep thing1</code>’s will also colour matches.</p>\n<p>If you want to install unbuffer, it’s in the <code>expect</code> package.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#that-s-all-the-solutions-i-know-about\">that’s all the solutions I know about!</a></h3>\n<p>It’s a bit hard for me to say which one is “best”, I think personally I’m mostly likely to use <code>unbuffer</code> because I know it’s always going to work.</p>\n<p>If I learn about more solutions I’ll try to add them to this post.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#i-m-not-really-sure-how-often-this-comes-up\">I’m not really sure how often this comes up</a></h3>\n<p>I think it’s not very common for me to have a program that slowly trickles data into a pipe like this, normally if I’m using a pipe a bunch of data gets written very quickly, processed by everything in the pipeline, and then everything exits. The only examples I can come up with right now are:</p>\n<ul>\n<li>tcpdump</li>\n<li><code>tail -f</code></li>\n<li>watching log files in a different way like with <code>kubectl logs</code></li>\n<li>the output of a slow computation</li>\n</ul>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#what-if-there-were-an-environment-variable-to-disable-buffering\">what if there were an environment variable to disable buffering?</a></h3>\n<p>I think it would be cool if there were a standard environment variable to turn off buffering, like <code>PYTHONUNBUFFERED</code> in Python. I got this idea from a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.plover.com/Unix/stdio-buffering.html\">couple</a> of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.plover.com/Unix/stdio-buffering-2.html\">blog posts</a> by Mark Dominus in 2018. Maybe <code>NO_BUFFER</code> like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://no-color.org/\">NO_COLOR</a>?</p>\n<p>The design seems tricky to get right; Mark points out that NETBSD has <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://man.netbsd.org/setbuf.3\">environment variables called <code>STDBUF</code>, <code>STDBUF1</code>, etc</a> which gives you a ton of control over buffering but I imagine most developers don’t want to implement many different environment variables to handle a relatively minor edge case.</p>\n<p>I’m also curious about whether there are any programs that just automatically flush their output buffers after some period of time (like 1 second). It feels like it would be nice in theory but I can’t think of any program that does that so I imagine there are some downsides.</p>\n<h3><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#stuff-i-left-out\">stuff I left out</a></h3>\n<p>Some things I didn’t talk about in this post since these posts have been getting pretty long recently and seriously does anyone REALLY want to read 3000 words about buffering?</p>\n<ul>\n<li>the difference between line buffering and having totally unbuffered output</li>\n<li>how buffering to stderr is different from buffering to stdout</li>\n<li>this post is only about buffering that happens <strong>inside the program</strong>, your operating system’s TTY driver also does a little bit of buffering sometimes</li>\n<li>other reasons you might need to flush your output other than “you’re writing to a pipe”</li>\n</ul>\n",
  "content_markdown": "Here’s a niche terminal problem that has bothered me for years but that I never really understood until a few weeks ago. Let’s say you’re running this command to watch for some specific output in a log file:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nIf log lines are being added to the file relatively slowly, the result I’d see is… nothing! It doesn’t matter if there were matches in the log file or not, there just wouldn’t be any output.\n\nI internalized this as “uh, I guess pipes just get stuck sometimes and don’t show me the output, that’s weird”, and I’d handle it by just running `grep thing1 /some/log/file | grep thing2` instead, which would work.\n\nSo as I’ve been doing a terminal deep dive over the last few months I was really excited to finally learn exactly why this happens.\n\n### [why this happens: buffering](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#why-this-happens-buffering)\n\nThe reason why “pipes get stuck” sometimes is that it’s VERY common for programs to buffer their output before writing it to a pipe or file. So the pipe is working fine, the problem is that the program never even wrote the data to the pipe!\n\nThis is for performance reasons: writing all output immediately as soon as you can uses more system calls, so it’s more efficient to save up data until you have 8KB or so of data to write (or until the program exits) and THEN write it to the pipe.\n\nIn this example:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nthe problem is that `grep thing1` is saving up all of its matches until it has 8KB of data to write, which might literally never happen.\n\n### [programs don’t buffer when writing to a terminal](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programs-don-t-buffer-when-writing-to-a-terminal)\n\nPart of why I found this so disorienting is that `tail -f file | grep thing` will work totally fine, but then when you add the second `grep`, it stops working!! The reason for this is that the way `grep` handles buffering depends on whether it’s writing to a terminal or not.\n\nHere’s how `grep` (and many other programs) decides to buffer its output:\n\n- Check if stdout is a terminal or not using the `isatty` function\n  - If it’s a terminal, use line buffering (print every line immediately as soon as you have it)\n  - Otherwise, use “block buffering” – only print data if you have at least 8KB or so of data to print\n\nSo if `grep` is writing directly to your terminal then you’ll see the line as soon as it’s printed, but if it’s writing to a pipe, you won’t.\n\nOf course the buffer size isn’t always 8KB for every program, it depends on the implementation. For `grep` the buffering is handled by libc, and libc’s buffer size is defined in the `BUFSIZ` variable. [Here’s where that’s defined in glibc](https://github.com/bminor/glibc/blob/c69e8cccaff8f2d89cee43202623b33e6ef5d24a/libio/stdio.h#L100).\n\n(as an aside: “programs do not use 8KB output buffers when writing to a terminal” isn’t, like, a law of terminal physics, a program COULD use an 8KB buffer when writing output to a terminal if it wanted, it would just be extremely weird if it did that, I can’t think of any program that behaves that way)\n\n### [commands that buffer & commands that don’t](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#commands-that-buffer-commands-that-don-t)\n\nOne annoying thing about this buffering behaviour is that you kind of need to remember which commands buffer their output when writing to a pipe.\n\nSome commands that **don’t** buffer their output:\n\n- tail\n- cat\n- tee\n\nI think almost everything else will buffer output, especially if it’s a command where you’re likely to be using it for batch processing. Here’s a list of some common commands that buffer their output when writing to a pipe, along with the flag that disables block buffering.\n\n- grep (`--line-buffered`)\n- sed (`-u`)\n- awk (there’s a `fflush()` function)\n- tcpdump (`-l`)\n- jq (`-u`)\n- tr (`-u`)\n- cut (can’t disable buffering)\n\nThose are all the ones I can think of, lots of unix commands (like `sort`) may or may not buffer their output but it doesn’t matter because `sort` can’t do anything until it finishes receiving input anyway.\n\nAlso I did my best to test both the Mac OS and GNU versions of these but there are a lot of variations and I might have made some mistakes.\n\n### [programming languages where the default “print” statement buffers](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#programming-languages-where-the-default-print-statement-buffers)\n\nAlso, here are a few programming language where the default print statement will buffer output when writing to a pipe, and some ways to disable buffering if you want:\n\n- C (disable with `setvbuf`)\n- Python (disable with `python -u`, or `PYTHONUNBUFFERED=1`, or `sys.stdout.reconfigure(line_buffering=False)`, or `print(x, flush=True)`)\n- Ruby (disable with `STDOUT.sync = true`)\n- Perl (disable with `$| = 1`)\n\nI assume that these languages are designed this way so that the default print function will be fast when you’re doing batch processing.\n\nAlso whether output is buffered or not might depend on how you print, for example in C++ `cout << \"hello\\n\"` buffers when writing to a pipe but `cout << \"hello\" << endl` will flush its output.\n\n### [when you press `Ctrl-C` on a pipe, the contents of the buffer are lost](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#when-you-press-ctrl-c-on-a-pipe-the-contents-of-the-buffer-are-lost)\n\nLet’s say you’re running this command as a hacky way to watch for DNS requests to `example.com`, and you forgot to pass `-l` to tcpdump:\n\n```\nsudo tcpdump -ni any port 53 | grep example.com\n```\n\nWhen you press `Ctrl-C`, what happens? In a magical perfect world, what I would *want* to happen is for `tcpdump` to flush its buffer, `grep` would search for `example.com`, and I would see all the output I missed.\n\nBut in the real world, what happens is that all the programs get killed and the output in `tcpdump`’s buffer is lost.\n\nI think this problem is probably unavoidable – I spent a little time with `strace` to see how this works and `grep` receives the `SIGINT` before `tcpdump` anyway so even if `tcpdump` tried to flush its buffer `grep` would already be dead.\n\nAfter a little more investigation, there is a workaround: if you find `tcpdump`’s PID and `kill -TERM $PID`, then tcpdump will flush the buffer so you can see the output. That’s kind of a pain but I tested it and it seems to work.\n\n### [redirecting to a file also buffers](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#redirecting-to-a-file-also-buffers)\n\nIt’s not just pipes, this will also buffer:\n\n```\nsudo tcpdump -ni any port 53 > output.txt\n```\n\nRedirecting to a file doesn’t have the same “`Ctrl-C` will totally destroy the contents of the buffer” problem though – in my experience it usually behaves more like you’d want, where the contents of the buffer get written to the file before the program exits. I’m not 100% sure whether this is something you can always rely on or not.\n\n### [a bunch of potential ways to avoid buffering](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#a-bunch-of-potential-ways-to-avoid-buffering)\n\nOkay, let’s talk solutions. Let’s say you’ve run this command:\n\n```\ntail -f /some/log/file | grep thing1 | grep thing2\n```\n\nI asked people on Mastodon how they would solve this in practice and there were 5 basic approaches. Here they are:\n\n#### [solution 1: run a program that finishes quickly](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-1-run-a-program-that-finishes-quickly)\n\nHistorically my solution to this has been to just avoid the “command writing to pipe slowly” situation completely and instead run a program that will finish quickly like this:\n\n```\ncat /some/log/file | grep thing1 | grep thing2 | tail\n```\n\nThis doesn’t do the same thing as the original command but it does mean that you get to avoid thinking about these weird buffering issues.\n\n(you could also do `grep thing1 /some/log/file` but I often prefer to use an “unnecessary” `cat`)\n\n#### [solution 2: remember the “line buffer” flag to grep](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-2-remember-the-line-buffer-flag-to-grep)\n\nYou could remember that grep has a flag to avoid buffering and pass it like this:\n\n```\ntail -f /some/log/file | grep --line-buffered thing1 | grep thing2\n```\n\n#### [solution 3: use awk](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-3-use-awk)\n\nSome people said that if they’re specifically dealing with a multiple greps situation, they’ll rewrite it to use a single `awk` instead, like this:\n\n```\ntail -f /some/log/file |  awk '/thing1/ && /thing2/'\n```\n\nOr you would write a more complicated `grep`, like this:\n\n```\ntail -f /some/log/file |  grep -E 'thing1.*thing2'\n```\n\n(`awk` also buffers, so for this to work you’ll want `awk` to be the last command in the pipeline)\n\n#### [solution 4: use `stdbuf`](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-4-use-stdbuf)\n\n`stdbuf` uses LD\\_PRELOAD to turn off libc’s buffering, and you can use it to turn off output buffering like this:\n\n```\ntail -f /some/log/file | stdbuf -o0 grep thing1 | grep thing2\n```\n\nLike any `LD_PRELOAD` solution it’s a bit unreliable – it doesn’t work on static binaries, I think won’t work if the program isn’t using libc’s buffering, and doesn’t always work on Mac OS. Harry Marr has a really nice [How stdbuf works](https://hmarr.com/blog/how-stdbuf-works/) post.\n\n#### [solution 5: use `unbuffer`](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#solution-5-use-unbuffer)\n\n`unbuffer program` will force the program’s output to be a TTY, which means that it’ll behave the way it normally would on a TTY (less buffering, colour output, etc). You could use it in this example like this:\n\n```\ntail -f /some/log/file | unbuffer grep thing1 | grep thing2\n```\n\nUnlike `stdbuf` it will always work, though it might have unwanted side effects, for example `grep thing1`’s will also colour matches.\n\nIf you want to install unbuffer, it’s in the `expect` package.\n\n### [that’s all the solutions I know about!](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#that-s-all-the-solutions-i-know-about)\n\nIt’s a bit hard for me to say which one is “best”, I think personally I’m mostly likely to use `unbuffer` because I know it’s always going to work.\n\nIf I learn about more solutions I’ll try to add them to this post.\n\n### [I’m not really sure how often this comes up](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#i-m-not-really-sure-how-often-this-comes-up)\n\nI think it’s not very common for me to have a program that slowly trickles data into a pipe like this, normally if I’m using a pipe a bunch of data gets written very quickly, processed by everything in the pipeline, and then everything exits. The only examples I can come up with right now are:\n\n- tcpdump\n- `tail -f`\n- watching log files in a different way like with `kubectl logs`\n- the output of a slow computation\n\n### [what if there were an environment variable to disable buffering?](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#what-if-there-were-an-environment-variable-to-disable-buffering)\n\nI think it would be cool if there were a standard environment variable to turn off buffering, like `PYTHONUNBUFFERED` in Python. I got this idea from a [couple](https://blog.plover.com/Unix/stdio-buffering.html) of [blog posts](https://blog.plover.com/Unix/stdio-buffering-2.html) by Mark Dominus in 2018. Maybe `NO_BUFFER` like [NO\\_COLOR](https://no-color.org/)?\n\nThe design seems tricky to get right; Mark points out that NETBSD has [environment variables called `STDBUF`, `STDBUF1`, etc](https://man.netbsd.org/setbuf.3) which gives you a ton of control over buffering but I imagine most developers don’t want to implement many different environment variables to handle a relatively minor edge case.\n\nI’m also curious about whether there are any programs that just automatically flush their output buffers after some period of time (like 1 second). It feels like it would be nice in theory but I can’t think of any program that does that so I imagine there are some downsides.\n\n### [stuff I left out](https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/#stuff-i-left-out)\n\nSome things I didn’t talk about in this post since these posts have been getting pretty long recently and seriously does anyone REALLY want to read 3000 words about buffering?\n\n- the difference between line buffering and having totally unbuffered output\n- how buffering to stderr is different from buffering to stdout\n- this post is only about buffering that happens **inside the program**, your operating system’s TTY driver also does a little bit of buffering sometimes\n- other reasons you might need to flush your output other than “you’re writing to a pipe”\n",
  "dateCreated": "2026-09-19T21:30:23.395188495+00:00",
  "date_created": "2024-11-29T08:23:31+00:00",
  "date_modified": "2024-11-29T08:23:31+00:00",
  "date_published": "2024-11-29T08:23:31+00:00",
  "description": "Here’s a niche terminal problem that has bothered me for years but that I never really understood until a few weeks ago. Let’s say you’re running this command to watch for some specific output in a log file: tail -f /some/log/file | grep…",
  "external_url": "https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/jvns-ca/2024/11/2024-11-29-why-pipes-sometimes-get-stuck-buffering.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/jvns-ca/2024/11/2024-11-29-why-pipes-sometimes-get-stuck-buffering.md"
  },
  "id": "urn:aggr:item:39aaea75553b1a300fb49d102e8233f03c95f1ea",
  "isBasedOn": {
    "@id": "https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/"
  },
  "mainEntity": {
    "@id": "https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/jvns-ca/2024-11-29-why-pipes-sometimes-get-stuck-buffering/#webpage"
    },
    "dateModified": "2024-11-29T08:23:31+00:00",
    "datePublished": "2024-11-29T08:23:31+00:00",
    "headline": "Why pipes sometimes get \"stuck\": buffering",
    "timeRequired": "PT9M",
    "url": "https://jvns.ca/blog/2024/11/29/why-pipes-get-stuck-buffering/",
    "wordCount": 1887
  },
  "name": "Archived snapshot: Why pipes sometimes get \"stuck\": buffering",
  "preview": null,
  "reading_minutes": 9,
  "replicated_at": null,
  "source": "jvns-ca",
  "source_name": "Julia Evans",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-09-19T21:30:23.395188495+00:00",
  "title": "Why pipes sometimes get \"stuck\": buffering",
  "url": "https://aggr.aymericbeaumet.com/items/jvns-ca/2024-11-29-why-pipes-sometimes-get-stuck-buffering/",
  "word_count": 1887
}