Duplicate, but still use stdout
Asked Answered
S

1

5

Is there some magic I can do with dup2 (or fcntl), so that I redirect stdout to a file (i.e., anything written to descriptor 1 would go to a file), but then if I used some other mechanism, it would go to the terminal output? So loosely:

  int original_stdout;
  // some magic to save the original stdout
  int fd;
  open(fd, ...);
  dup2(fd, 1);
  write(1, ...);  // goes to the file open on fd
  write(original_stdout, ...); // still goes to the terminal
Selfinterest answered 19/2, 2016 at 23:28 Comment(0)
B
9

A simple call to dup will perform the saving. Here is a working example:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  // error checking omitted for brevity
  int original_stdout = dup(1);                   // magic
  int fd = open("foo", O_WRONLY | O_CREAT);
  dup2(fd, 1);
  close(fd);                                      // not needed any more
  write(1, "hello foo\n", 10);                    // goes to the file open on fd
  write(original_stdout, "hello terminal\n", 15); // still goes to the terminal
  return 0;
}
Brickwork answered 19/2, 2016 at 23:33 Comment(13)
Might want to go with STDOUT_FILENO rather than the magic numbers.Heterochromous
@EOF The idea was to retain as much of the original code, which was using numeric constants. Also, in the context of Unix, the symbolic constant is equivalent to spelling out the number. Numeric references to file descriptors are regularly made from shells and scripting languages.Brickwork
Using the macro STDOUT_FILENO instead of 1 for the descriptor number would make the code much more readable; I think readability is extremely important. Regarding scripting languages, I disagree: for example awk and sed do not have any concept of file descriptors. gawk and mawk support special names /dev/stdin, /dev/stdout, and /dev/stderr (even in Windows). Those scripting languages that know about file descriptors, have inherited the concept (and largely interface) from C, and do have the named constants too (e.g. Perl, Python).Antennule
@NominalAnimal Readability is important, and so is education, especially in the context of the limited space of a SO answer. The provided snippet shows that the code runs unchanged with just the addition of dup. In time, the OP will learn about the symbolic constants, perhaps from these very comments.Brickwork
@Brickwork Thanks for a great answer. Didn't realize it was so simple. I thought dup2 would close stdout.Selfinterest
@Selfinterest dup2 does close the descriptor specified as its second argument (file descriptor 1), but not necessarily the underlying resource. One can think of file descriptors as handles to operating system resources, such as open files, TTYs, or network connections. open opens a file and returns a new handle; dup creates a new handles to an existing resource. The resources are reference-counted, and released only when the last handle is closed. So, calling dup(1) essentially protects existing stdout from being destroyed by dup2(fd, 1).Brickwork
@Brickwork Ah, brilliant. Thanks for the explanation. I wish dup's manpage explained the reference counting.Selfinterest
It is actually a bit more complicated than that. The kernel keeps file descriptions (reference-counted data structures, that include the current position for seekable files and devices), and an internal table of file descriptors for each process. Each entry in the file descriptor table refers to one file description. For userspace processes, file descriptor is a nonnegative integer, the kernel uses it as the index to the file descriptor table. dup() copies the reference to the file description to a new file descriptor table entry, and returns the new file descriptor. ...Antennule
dup2() replaces a specific file descriptor, by first closing the target file descriptor (removing the reference to the file description), then copying the reference to the file description from the source descriptor to the target descriptor. fork() copies the entire file descriptor table as-is to the new process. The man 2 dup and man 2 fork at the Linux manual pages online have further info on this.Antennule
@NominalAnimal That's equivalent to my previous comment, with "underlying resource" being called "file description" in your explanation. The terminology is irrelevant (and specific to kernel implementation), the notions are important.Brickwork
The mention of fork() is interesting, though, and part of reason why dup() documentation doesn't explain the reference counting. Namely, calling dup() is one way to get different handles to the same resource. The other is by forking, and then there are other ways to transfer file descriptors between processes. I believe this topic is covered in detail by "Advanced Programming in the Unix environment" by Richard Stevens.Brickwork
@user4815162342: I don't mean to imply you are wrong; I just thought the added information might be useful to someone. "file descriptor" and "file description" are the most common terms used for these, not specific to any kernel implementation. Searching for both, ie. "file descriptor" "file description" yields good background info links, so the terminology is actually relevant. As to descriptor passing (man 3 cmsg), it too is basically just a cross-process dup().Antennule
Thanks @user4815162342, helped me a lot with a different question. I wasn't quite sure about the close(fd);, but for that purpose, an extra open fd seems to be negligible in python, if I'm not missing anything. If I am - I'd appreciate your input :) The question at https://mcmap.net/q/2031682/-log-stderr-to-file-prefixed-with-datetime Thank you!Perfidious

© 2022 - 2024 — McMap. All rights reserved.