1

I was hoping to do something like this:

echo 'foo' >&3  3|  cat

Basically, I want to write 'foo' to 3, and then only pipe the data in 3 to cat. But the above doesn't work, I get:

bash: 3: Bad file descriptor

Does anyone understand what I am trying to do?

With Node.js, I have working example, here: https://gist.github.com/ORESoftware/0ad178f4512bbf956e54dd08f2412883

In that Node.js gist, which seems reliable, at the command line we do:

node foo.js 3> some-file.sh

and in the node process we are writing to the 3 file descriptor.

But I am wondering how to do it with pipes instead of redirection.

Something like this:

node foo.js 3|  cat
Bart
  • 2,221
  • "I am wondering how to do it with pipes instead of redirection" There's no way with anonymous pipes - these default to stdout. But probably you could make a named pipe with mkfifo and wire two commands together in subshells. Something like this https://unix.stackexchange.com/a/18903/85039 – Sergiy Kolodyazhnyy Aug 26 '19 at 06:23

4 Answers4

4
{ echo 'hello on fd3' >&3; } 3>&1 | cat

Here, echo writes to standard output, but we redirect it to file descriptor 3 (this corresponds to the writing to w that you do in your Node application). We then output the stream on file descriptor 3 to standard output to be able to send it over the pipe to cat.

To also discard the standard output:

{ echo 'hello on fd1'; echo 'hello on fd3' >&3; } 3>&1 1>/dev/null | cat

The first echo writes to standard output, and the second writes, via a redirection as in the first example, to file descriptor 3. File descriptor 3 is then sent to standard output (to be able to pipe it) while standard output is discarded.

Note that the 1 in 1>/dev/null is not actually needed and just added here for clarity.

Both pipelines output hello on fd3 only.

For your Node application:

node foo.js 3>&1 | cat

or, to also discard standard output,

node foo.js 3>&1 1>/dev/null | cat

or, to send standard output to standard error (the terminal by default),

node foo.js 3>&1 1>&2 | cat
Kusalananda
  • 333,661
  • I don't want to mix 1 and 3. In short, I am looking to use 1 for metadata, 2 for errors, 3 for data. – Alexander Mills Aug 26 '19 at 06:49
  • @AlexanderMills Sure. But to pipe fd 3, you have to redirect it to standard output. There is no way around that. – Kusalananda Aug 26 '19 at 06:50
  • I see what you are saying, so maybe process substitution is a less verbose alternative? idk – Alexander Mills Aug 26 '19 at 06:51
  • @AlexanderMills You question only ever asks about how to send the single stream on fd 3 to cat. It never shows what you want to do with standard output or standard error. – Kusalananda Aug 26 '19 at 06:52
  • Well by default both stdout and stderr are going to the terminal, right, that's fine. A simple use case is to not be like curl. curl writes it's metadata to stderr which is so annoying and dumb. I want to silence the metatdata but still see errors. – Alexander Mills Aug 26 '19 at 06:53
  • 1
    @AlexanderMills By default, with only a 3>&1 redirection, you would mingle the standard output with the output to fd 3, and standard error would go to the terminal. If you want to pipe fd 3 and send standard output to the terminal, use 3>&1 1>&2 | cat (this would mingle standard output and error on the terminal). – Kusalananda Aug 26 '19 at 06:54
  • nice, that makes sense, ty – Alexander Mills Aug 26 '19 at 06:55
  • and I suppose the ordering of the redirection commands matters? – Alexander Mills Aug 26 '19 at 06:57
  • @AlexanderMills Most definitely, yes. Swapping them would intermingle fd 3 and standard output (and nothing would actually be piped to cat). – Kusalananda Aug 26 '19 at 06:58
4

You can do it without process substitutions and without mixing fd 1 and 3 or fd 1 and 2:

producer(){ echo to_out; echo >&2 to_err; echo >&3 to_fd3; }
consumer(){ sed "s/^/consumer $@: /" >/dev/tty; }

{ producer 3>&1 >&4 | consumer 3; } 4>&1 | consumer 1
to_err
consumer 1: to_out
consumer 3: to_fd3

{ producer 3>&1 >&4 | consumer 3; } 4>&1
to_out
to_err
consumer 3: to_fd3
0

Not quite a pipe via process-substitution:

# cmd 3> >(cat)

Or, if you are ok to mix the output of fd3 and stdout,

# cmd 3>&1 | cat
  • I believe there is a way to avoid mixing stdout in the last example, probably via another file descriptor, but that gets complex. – D. Ben Knoble Aug 26 '19 at 06:03
  • 1
    sadly this doesn't work, and I don't know why, it says that file descriptor 3 is not writable, it's not open by using the process substitution for some reason – Alexander Mills Aug 26 '19 at 06:06
  • see my answer, I wish this were easier, I think if it were commonplace to write metadata to 1, errors to 2, and data to 3, the software world would be better. – Alexander Mills Aug 26 '19 at 06:13
  • 1
    @AlexanderMills I don't see why that wouldn't work. Maybe your app expects a seekable or readable file descriptor as its fd 3? Please add a complete reproducible testcase to your Q. I don't see how your answer is different (other than trying to open a process subst pipe as rw -- which may only work on *bsd and isn't needed in your example) -- it could be written in a single line as { echo a >&3; } 3> >(cat >temp.sh) –  Aug 26 '19 at 09:47
  • @AlexanderMills notice that on FreeBSD, you need to mount fdescfs in order to use /dev/fd/3 and larger. Please add complete OS details to your Q. –  Aug 26 '19 at 09:50
0

So with bash, this does work:

exec 3<> >(cat > temp.sh)
echo a >&3 # write to it
exec 3>&- # close fd 3.

but I am wondering if there is a less verbose way to do it. And to do without process substitution, instead of using pipes.