What would be a consistent explanation of the mechanisms which result in a following bash session logs helpful in gaining understanding and making reliable prediction of outcomes for similar constructed commands possible:
~ $ cat f
abc123
~ $ cat g
cat: g: No such file or directory
~ $ cat f g 2>&1 >h
cat: g: No such file or directory
~ $ cat h
abc123
and
~ $ cmd >a 2>&1
~ $ cat a
Command 'cmd' not found, but there are 19 similar ones.
~ $ cmd 2>&1 >a
Command 'cmd' not found, but there are 19 similar ones.
~ $ cat a
~ $
and
~ $ echo abc > a; echo bcd > b; echo cde > c
~ $ cat a
abc
~ $ cat b
bcd
~ $ cat c
cde
~ $ cmd >a>b>c 2>&1
~ $ cat a
~ $ cat b
~ $ cat c
Command 'cmd' not found, but there are 19 similar ones.
?
Have you succeeded in correctly predicting the outcome of the commands above in advance? If yes, how did you arrive at the right prediction?



As I read through the comments, I will guess you become confused at how file descriptors are not files, but really pointers to handles to files.
The "mental model" of file descriptors is really explained in many places, even on wiki https://en.wikipedia.org/wiki/File_descriptor . File descriptor is a pointer to filetable of inode table (like a double pointer). Consider researching what is a file descriptor and how it works.
They are pointers. At
2>&1stderr is redirect to where stdout is pointing. Then>ais opening filea, creating entry in filetable, and redirecting stdout to that entry in filetable. Stderr is still pointing to the entry in filetable stdout was pointing at the time2>&1was created.The file descriptors are inherited from the parent process. Bash (or any parent process) file descriptors point to somewhere, when creating a child process all file descriptors are copied and point to the same somewhere.
Related:
man dup2,man clone,man fork,man exec. I really understood what isfdafter I understood howflockworks.Also, don't trust me! You can observe what a process is doing with
strace.Let's try! Let's assume we have the following input state at some program initialization, after the process has started and opened a
/tmp/afile with file descriptor3/First case:
Execute
dup2(1, 2). Nothing happens.2already points to/dev/tty.Then execute
dup2(3, 1). Below is the resulting connection.2still points to/dev/tty, but1was moved to/tmp/a.Second case:
Execute
dup2(3, 1), so we move1arrow to where3is pointing. Below is the result:Then execute
dup2(1, 2), so we move2to where1is pointing to.As you see, the result is different. In the
dup2(3, 1); dup2(1, 2)case, all12and3file descriptors point to/tmp/a. In contrast, in thedup2(1, 2); dup2(3, 1), the2is still pointing to/dev/tty.The images above are missing the "file table" pointers, because I am too lazy to draw it. It would be just
[0,1,2] -> readwrite -> /dev/ttyand3 -> read -> /tmp/a, for simplicity.Let's do also this:
The
2>&1will make stderr output on terminal. Then>h, so stdout goes toh. The content of filefwill be in fileh. The error message will be on terminal.The output of command
cmdwill go toa. Then stderr will also go toa.cmddoes not exists. Bash will spawn a child process, then try to executecmd. Bash will fail, so the Bash child temporary subshell will print an error message to filea.As above in the
2>&1 >hcase. Just the message does not come fromcat, but from the temporary subshell Bash spawns to execcmd.Is equal to
cmd >a >b >c 2>&1. First, stdout goes toa. Then file descriptor that references fileais closed, and stdout goes to another file descriptor coming from opening fileb. Thenbis closed, and stdout goes toc. Then stderr also goes toc. The error message aboutcmd: command not foundwill be inc, as the temporary subshell will print it tostderrwhich points tocnow. Because>open()s files withO_CREAT, there will be empty filesaandb.That was easy. Now me! Let's throw my examples. We have a function, that outputs
stdoutto stdout andstderrto stderr.Then we take the magic
time.timeis a shell built-in.timeoutputs the timing message from the parent shell, not from the child subshell Bash spawns when executing the command. This is in contrast to thecmd: command not foundmessage, which is outputted by the child subshell spawned to executecmd.timemessage is printed by the parent shell after the child exits.What is the output of the following commands? What output is outputted to stdout? And what output is on stderr?
output 3>&2 1>&2 2>&3output 3>&1 1>&2 2>&3var=$(output 3>&1 1>&2 2>&3); echo "VAR: $var"var=$(output 3>&1 1>&2 2>&3) 2>/dev/null; echo "VAR: $var"{ var=$(output 3>&1 1>&2 2>&3); } 2>/dev/null; echo "VAR: $var"time output >/dev/null 2>&1( time output 2>&1 ) >/dev/null{ time output 2>&1; } 2>&1 >/dev/nullvar=$( { time output 2>&1; } 3>&1 1>&2 2>&3 ); echo "TIME: $var"exec 3>&1; var=$( { time output >&3 2>&1; } 2>&1 ); echo "TIME: $var"var=$( { time output 2>&1; } 3>&1 1>&2 2>&3 ) 2>&1; echo "TIME: $var"{ var=$( { time output 2>&1; } 3>&1 1>&2 2>&3 ); } 2>&1; echo "TIME: $var"