3

From C++, you can output to cout and cerr, which are handled by the file descriptors 1 and 2 respectively. Outside of the C++ program, I can then redirect this output wherever I want it (in this case, writing it two separate files):

$ my-program 1>output 2>errors;

Am I stuck with only file descriptors 1 and 2, or can I "create my own"? Let's say, I wanted a third output that saves debug information, or a fourth output that mails the administrator?

$ my-program 1>output 2>errors 3>>/logs/my-program.log 4>&1 | scripts/email-admin.sh;

Can I write to file descriptor's 3 and 4 within my program?

Eric Renouf
  • 13,950
  • 3
  • 45
  • 67
IQAndreas
  • 8,060
  • 8
  • 39
  • 74
  • Nice idea, but I don't think this is possible without changing things within the OS [and the shell and...] – deviantfan Sep 10 '15 at 00:10
  • Thank you, [**@Eric Renouf**](http://stackoverflow.com/users/4687135/eric-renouf) for the new title. – IQAndreas Sep 10 '15 at 00:12
  • 2
    @deviantfan What makes you think the OS treats the first three file descriptors specially? – David Schwartz Sep 10 '15 at 00:23
  • Related: [How to construct a c++ fstream from a POSIX file descriptor?](http://stackoverflow.com/questions/2746168/how-to-construct-a-c-fstream-from-a-posix-file-descriptor) – Roman Sep 10 '15 at 00:38
  • @DavidSchwartz I thought to much of Windows; there they indeed get special treatment. – deviantfan Sep 10 '15 at 08:54

2 Answers2

2

Opening all your files in a wrapper script is not usually a good design. When you want your program to be smarter, and able to close a big log file and start a new one, you'll need the logic in your program.

But to answer the actual question:

Yes, you can have the shell open whatever numbered file descriptors you like, for input, output or both. Having the parent open them before execve(2) is identical to what you'd get from opening them with code in the child process (at least on a POSIX system, where stdin/out/err are just like other file descriptors, and not special.) File descriptors can be marked as close-on-exec or not. Use open(2) with O_CLOEXEC, or after opening use fcntl(2) to set FD_CLOEXEC

They don't have to refer to regular files, either. Any of them can be ttys, pipes, block or character device files, sockets, or even directories. (There's no shell redirection syntax for opening directories, because you can only use readdir(3) on them, not read(2) or write(2).)

See this bash redirection tutorial. And just as a quick example of my own:

peter@tesla:~$ yes | sleep 60 4> >(cat) 5</etc/passwd 9>/dev/tcp/localhost/22 42<>/tmp/insecure_temp &
[1] 25644
peter@tesla:~$ ll /proc/$!/fd
total 0
lr-x------ 1 peter peter 64 Sep  9 21:31 0 -> pipe:[46370587]
lrwx------ 1 peter peter 64 Sep  9 21:31 1 -> /dev/pts/19
lrwx------ 1 peter peter 64 Sep  9 21:31 2 -> /dev/pts/19
l-wx------ 1 peter peter 64 Sep  9 21:31 4 -> pipe:[46372222]
lrwx------ 1 peter peter 64 Sep  9 21:31 42 -> /tmp/insecure_temp
lr-x------ 1 peter peter 64 Sep  9 21:31 5 -> /etc/passwd
l-wx------ 1 peter peter 64 Sep  9 21:31 63 -> pipe:[46372222]
lrwx------ 1 peter peter 64 Sep  9 21:31 9 -> socket:[46372228]

# note the rwx permissions: some are read-write, some are one-way

The >(cmd) process substitution syntax expands to a filename like /dev/fd/63. Using 4> >(cmd) opens that fd as fd 4, as well.

Redirecting stderr to a pipe takes some juggling of file descriptors, because there's no 2| cmd syntax. 2> >(cmd) works, but the cmd runs in the background:

peter@tesla:~$ (echo foo >&2 ) 2> >(wc)  # next prompt printed before wc output
peter@tesla:~$       1       1       4

peter@tesla:~$ ( echo foo >&2; ) 2>&1 | wc
      1       1       4
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
1

The usual way to handle something like sending debug information somewhere else would be to choose a logging system that writes directly to a file (rather than sending it to another virtual file descriptor, and then redirecting that file descriptor in bash to file).

One option would be to use mkfifo, and have your program write to the fifo as a file, then use some other means to direct the fifo to other locations.

Another option for the mail script would be to run the mail script as a subprocess from in the C++ program and write to its stdin with internal piping. Piping into sendmail is the traditional way for a program to send mail on a Unix system.

The best thing to do is to find libraries that handle the things that you want to do.

Examples

mkfifo

http://linux.die.net/man/3/mkfifo

You use the mkfifo command to create a special file, and then you can fopen and fprint* to it as usual. You can pass the file name to the program like any other.

The bash part looks something like this:

mkfifo mailfifo
yourprogram mailfifo &
cat mailfifo | scripts/email-admin.s

Pipe to subprocess

http://www.gnu.org/software/libc/manual/html_node/Pipe-to-a-Subprocess.html

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
zstewart
  • 2,093
  • 12
  • 24