You got a few very good answers already. Let me stress though that there are two different concepts involved here, the understanding of which helps tremendously:
Background: File descriptor vs. file table
Your file descriptor is just a number 0...n, which is the index in the file descriptor table in your process. By convention, STDIN=0, STDOUT=1, STDERR=2 (note that the terms
STDIN etc. here are just symbols/macros used by convention in some programming languages and man pages, there is not an actual "object" called STDIN; for the purpose of this discussion, STDIN is 0, etc.).
That file descriptor table in itself does not contain any information whatsoever about what the actual file is. Instead, it contains a pointer to a different file table; the latter contains information about an actual physical file (or block device, or pipe, or whatever else Linux can address via the file mechanism) and more information (i.e., whether it's for reading or writing).
So when you use
< in your shell, you simply replace the pointer of the respective file descriptor to point to something else. The syntax
2>&1 simply points descriptor 2 to wherever 1 points.
> file.txt simply opens
file.txt for writing and lets STDOUT (file decsriptor 1) point to that.
There are other goodies, e.g.
2>(xxx) (i.e.: create a new process running
xxx, create a pipe, connect file descriptor 0 of the new process to the reading end of the pipe and connect the file descriptor 2 of the original process to the writing end of the pipe).
This is also the basis for "file handle magic" in other software than your shell. For example, you could, in your Perl script,
duplicate the STDOUT file descriptor into another (temporary) one, then re-open STDOUT to a newly created temporary file. From this point on, all STDOUT output from your own Perl script and all
system() calls of that script will end up in that temporary file. When done, you can re-
dup your STDOUT to the temporary descriptor you saved it to, and presto, all is as before. You can even write to that temporary descriptor meanwhile, so while your actual STDOUT output goes to the temporary file, you can still actually output stuff to the real STDOUT (commonly, the user).
To apply the background information given above to your question:
In what order does the shell execute commands and stream redirection?
Left to right.
<command> > file.txt 2>&1
fork off a new process.
file.txt and store its pointer in the file descriptor 1 (STDOUT).
- Point STDERR (file descriptor 2) to whatever the fd 1 points to right now (which again is the already opened
file.txt of course).
This apparently redirects stderr to stdout first, and then the resulting stdout is redirected to file.txt.
This would make sense if there were only one table, but as explained above there are two. File descriptors are not pointing to each other recursively, it makes no sense to think "redirect STDERR to STDOUT". The correct thought is "point STDERR to wherever STDOUT points". If you change STDOUT later, STDERR stays where it is, it does not magically go along with further changes to STDOUT.