I was just playing around in the terminal and I did this:

printf 'some string\n' | {
    tee /dev/fd/3 | {
        : && sed 's/some/string/'
} 3>&0

I was really surprised when I was met with a screen full of:

string string
string string
string string

It went on pretty much forever. I pared it down some, and to better demonstrate, maybe try this:

echo | tee /dev/fd/0 | sed =

For me, it presents output like:


And so on. The few times I've tried it by the time I could hit CTRL+C I was at line 200k+. I tried this in bash, dash, sh, and zsh and all present the same results.

What is going on there? How are the file-descriptors passing over that |pipe between, what I thought were, separate processes? Is this reliable and/or expected behavior? Are there other ways to generate this effect?

For reference:

echo '#blank' | {
    uname -r
    readlink -f /dev/fd/0
    tee /dev/fd/0
} | sed '=;5q'


  • On zsh, it does not work for me - $ZSH_VERSION 5.0.2 $ echo | tee /dev/fd/0 | sed = tee: /dev/fd/0: Text file busy – Volker Siegel Jun 18 '14 at 9:11
  • @VolkerSiegel - It works for me in zsh - and everything else - but it didn't used to. This is new, which is why I was taken by surprise. Believe me, I've encountered that very same text file busy error more times than I can count. I believe this has a lot less to do with the shell - if anything - than it does with the way the kernel handles |pipes and/or the way tee does. So it's either a kernel option, a newer kernel feature, or a newer coreutils feature. I'm not sure which. In any case it behaves the same for me in X and in the console. – mikeserv Jun 18 '14 at 9:23
  • Interesting; Works for me in bash like you describe, actually. BASH_VERSION: 4.3.11(1)-release on Linux 3.13.0-24. – Volker Siegel Jun 18 '14 at 9:29
  • @VolkerSiegel - That is very interesting, and may invalidate my previous assumption... Thanks very much for the information. I'm going to try to figure out how to test it more thoroughly. – mikeserv Jun 18 '14 at 9:31
  • If I run bash as bash --norc --noprofile, I get the error tee: /dev/fd/0: Text file busy in bash too. So it's probably related to some shell option or similar. – Volker Siegel Jun 18 '14 at 9:34

/dev/fd/0 is the current process's standard input; tee writes its input to both to the file(s) you give it and to stdout. So tee is reading a (blank) line, then writing that line to both stdout (sed) and its own input, where it reads the line it just wrote, writes it to stdout and its own input, etc., creating an infinite loop of (empty) lines*. The sed command just prints the line number and then the line.

echo --> tee --> sed

* When provided more input than just a simple echo, this command will loop over the input, repeating it over and over.

Note: OSX (BSD) doesn't let you write to /dev/fd/0, so you can't do this.

| improve this answer | |
  • At what point does tee start writing over what it reads? For instance seq 100 | tee /dev/fd/0 | sed 101q gets ..\n100\n1\n ... – mikeserv Jun 18 '14 at 4:23
  • @mikeserv It never overwrites its input, it just appends to it. It will basically repeat the provided input over and over. – Kevin Jun 18 '14 at 4:25
  • Well, that's very cool. Thanks for the answer. – mikeserv Jun 18 '14 at 4:26
  • 2
    That's Linux specific. On linux /dev/fd/x is a symlink to a resource. In this case the resource acts like a named-pipe, so opening /dev/fd/3 in write mode, doesn't duplicate the fd 3 as it would on other systems, but opens (in a difference mode (WR instead of RD)) the pipe, so you get the writing end of the pipe (the other end as that opened on 3). – Stéphane Chazelas Jun 18 '14 at 12:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.