1

I am trying to output tee while writing to a custom file descriptor. Example:

exec 4>"/tmp/testfile.txt"; # open FD 4
tee -a >&4 <<< "Output this to stdout" # Write to open file
exec 4>&- # close FD

If I perform instead tee -a /proc/self/fd/4 <<< "Output this to stdout" I can see the output in my terminal / standard stdout.

I am using BASH 5.0.16(1)-release.

I tried doing a connection between FD 1 (stdout) and FD 4 but then my string wouldn´t be written to the file.

Thanks a lot!

EDIT: Please refer to @Stéphane Chazelas answer. My updates below may be true but way to complicated.

Update: I am getting closer I guess. When trying tee <<< "Output this to stdout" >(tee -a >&4) I can actually see the output in my terminal and it writes to the file but for some reason it overrides the content and doesn´t seem to append (-a).

Update 2: I feel like I´ve found my mistake now.

First: Using Bash Process Substitution I was able to output to stdout and write to my FD by using tee two times. I did this in this way:

exec 4>"/tmp/testfile.txt"; # open FD 4
tee >(tee -a >&4) <<< "Output this to stdout" # Write to open file
exec 4>&- # close FD

As mentioned before the file content is overwritten which is the second part of the answer.

Second: I´ve totally forgot about seek positions! If I open a file using exec 4>"..." the seek position (or pointer) is at the beginning of the file. In this case appending is like overwritting the file. To fix this the file descriptor needs to read the complete file before any action to set the current position to the end of the file. One solution is just using cat. Of course I also need to open the file for reading then (<>).

Finally: This works for me. It opens a FD for a file, read it´s content and append to it.

exec 4<>"/tmp/testfile.txt"; # open FD 4 read (<) and write (>)
cat <&4 >/dev/null; tee >(tee -a >&4) <<< "Output this to stdout" # read fd (>/dev/null to silent the output) for correct position and append to. Also output to stdout
exec 4>&- # close FD (this closes it completly, so no need for exec 4<&-)

KEEP IN MIND: This can actually be carefull if you use multiple processes which modify the file. If I read correctly it´s not a "true" append. Example: You did a cat and the position is 3. Someone else appends to the file two lines in the meantime. If you now append using your current known position the other two lines are lost.


I guess I´ve learned a bit about file reading in Unix systems again. Overall I might need to overthink my idea of opening a FD. If you know the file will be empty you are fine. But it can be risky with files which have already content and esp. when another process may write to it when your pointer is not up to date. I may explained some technical aspects wrong but it should be correct in some way.

0

2 Answers 2

2

If you want to write the same input to both fd 1 and 4, you'd use zsh and do something like:

cat <<< some-input >&1 >&4

(or just <<< some-input >&1 >&4 as cat happens to be the default $NULLCMD)

In zsh, and if the multios option is enabled (as it is by default), redirecting a fd (here 1 as > is short for 1>) several times for output causes that fd to be redirected to an internal teeing process that multiplexes the output to all the targets.

While <<< (also a zsh extension) is now supported by quite a few other shells (including bash), that multios feature is not.

On systems other than Linux and Cygwin, you could also do:

tee /dev/fd/4 <<< some-input

As on most systems (except those 2 mentioned above), opening /dev/fd/4 does the same as duplicating fd 4 (same as {fd}>&4).

On Linux and Cygwin however, that's not what happens. Instead, opening /dev/fd/4 (same as /proc/self/fd/4 there) works as if you were opening the file currently open on that fd anew from scratch.

So, if fd 4 points to a socket for instance, that won't work as you can't open() a socket, and if it's a regular file, opening it with > will cause the file to be truncated.

With tee -a /dev/fd/4 <<< some-input, you can avoid the truncation, but beware fd 4 will be left asis, so if you write data again to fd 4, it will likely overwrite what was output by tee. You'd need the fd 4 to have also been opened in append mode (which guarantees all writes are done at the end) to avoid it.

Doing:

tee >(cat >&4) <<< some-input

Also addresses the problem, but means shoving the data through an extra cat process.

1
  • Thanks for your explanation. This also seems to work just fine with name file descriptors, e.g. : exec {myFD}>>"/tmp/test"; tee -a "/dev/fd/${myFD}" <<< "my string". My problem was not knowing about exec >> and not using /dev/fd/XXX. I feel like 1. Using >> 2. Using tee -a /dev/fd/XXX and 3. Always close FD when it´s not needed anymore is the way to go.
    – kon
    Jul 24, 2020 at 16:24
1

> opens and truncates, >> opens for appending.

You therefore want:

exec 4>>"/tmp/testfile.txt";

tee -a would open files for appending, but you're not specifying any files to open so it has no effect . You might as well be using echo.

3
  • I´ve tried your suggestion but it doesn´t write to the file in this case. I did: exec 4>>"/tmp/testfile.txt"; echo "attach" >&4 but the file is unchanged. I did edit my comment in the mean time. Do you think I am correct about this?
    – kon
    Jul 24, 2020 at 15:44
  • 1
    That should work just fine. Is this the exact, actual code you're copy-pasting to a shell? Is /tmp/testfile.txt a regular file? Is /tmp on a local filesystem? Do you have the file open in other shells where you're testing other solutions against the same file? Jul 24, 2020 at 15:50
  • You are correct. I´ve might missed something. Opened a "clean" terminal, did create a new file and tried it again. It works! Thanks a lot! Also the "seeking" problem doesn´t seem to happen. I feel like my conclusion was wrong and this seems such a simple but great solution!
    – kon
    Jul 24, 2020 at 16:03

You must log in to answer this question.

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