Redirecting stdout/stderr
File Descriptor 101
We can "dup" an IO descriptor (>&) but that's neither the same as having a second IO device nor making the one IO descriptor a reference to the other. The >& operator doesn't duplicate the stream but rather gets another file descriptor (chosen by you) to point at the same IO device as the original file descriptor was pointing at.
This is surprisingly subtle and most people come undone with the age-old mistake of getting the order of IO duplications wrong, "but they are duplicates!" is the cry. Think of file descriptors as water pipes and the IO devices as the buckets (for want of a better phrase) where they transport the streams of data to. When you duplicate an IO descriptor you are asking that your new IO descriptor point at the same IO device as the original. Think of this as pointing your new pipe at the same bucket as the first but it is not a case of lashing the two pipes together! While your new pipe is pointing at this bucket there is nothing to stop you pointing the original pipe at a different bucket. Until you say otherwise, you new pipe will remain pointing at the original bucket.
IO devices can, in the Unix model, be many different kinds of things. Files, obviously, noting that /dev/null is a file too, and then pipes, sockets (Unix domain and internet), terminal devices (the tty your keyboard and display/xterm are connected to) and other more exotic beasties.
A useful thing to know is that you inherit your IO descriptors from your parent: your stdout points at the same IO device as your parent. Until you decide to change it, of course.
A rather important thing to be aware of, given the two paragraphs above, is that you do not know and have few ways of finding out what IO devices you have inherited from your parent are. When you start you have no idea if your stdout is a terminal or a file or a socket. Should you know? Maybe in some particular cases. Should you care? Almost certainly not!
When you are running a shell in a terminal window you are started off with the shell's stdout directed to the terminal itself. Anything you run inherits its stdout from your shell and so its output will appear on the terminal.
So,
ls
will list the contents of the current directory to its stdout which was inherited from the shell whose stdout is the terminal. The contents of the directory are displayed in the terminal.
ls > file
Here, the shell has opened file and then redirected ls's stdout to the file rather than have ls inherit the shell's own stdout. When ls runs it is none-the-wiser about this change in its stdout and blindly prints the contents of the directory to its stdout which now appears in file rather than in the terminal.
(For the pedants, ls is one of the few programs that detects whether its stdout is a terminal or not a terminal and changes its style of output accordingly. Most programs simply output exactly the same. Assume, for the sake of argument, ls also outputs the same.)
So, let's repeat that, from ls's perspective, when it starts it has a stdout (water pipe) that is going somewhere (a bucket), it doesn't know where as it's not privy to how whomever started it diddled with its file descriptors but what it does know is that it has a stdout and can write to it.
So, back to "dup"'ing file descriptors. What's going on there?
Well, "dup"'ing file descriptors isn't doing anything with the file descriptor, that's a complete mis-nomer, what it's actually doing is pointing a second file descriptor wherever the first file descriptor was pointing.
exec 3>&1
After this line has run, file descriptor 3 (which we've plucked from nowhere -- how you choose one is another story) is now pointing wherever file descriptor 1 (aka stdout) was.
How is this helpful? Well, given that file descriptor 3 has nothing going into it, it does seem pretty useless but it's not. Given that you don't actually know, when your script is started, the actual IO device being used for your stdout, if you want to send your output somewhere else, temporarily, then file descriptor 3 is a handy pointer back to where stdout was originally.
For the rest of the script you can now redirect stdout (file descriptor 1) as you like and then at the end of you section, redirect stdout back to where it was originally (as the person who invoked you expects):
echo "to original stdout" exec 3>&1 exec >file echo "to file" echo "also to file without IO redirection clutter" exec 1>&3 echo "to original stdout again"
So, in the case above, we haven't a clue whether our stdout is going to a terminal a file or /dev/null but we don't care, we can send some stuff to the stdout our caller wants us to, we can temporarily output to file and then we can recover the original stdout again.
Document Actions