Question

I have this:

$ time 2>&1 sh 2>&1 -c "dd if=/dev/zero of=ddfile bs=512 count=125 && sync" > file

125+0 records in

125+0 records out

64000 bytes (64 kB) copied, 0.000617678 s, 104 MB/s

real 0m0.133s

user 0m0.000s

sys 0m0.020s

$ cat file

file is empty.

Question: I understand both dd and time are sending their o/p to stderr. But since I redirected stderr to stdout for time and sh (which is the parent process of dd) and also sent stdout to go to file I expect things to be written to file. Why did that not happen?

Was it helpful?

Solution 2

First, with some exceptions, redirections generally occur at the point they are written. They are removed from the command-and-argument sequence; only the remaining, non-redirection, words participate. Thus:

3>&1 foo 1>&2 arg1 2>&3 arg2 3>&- arg3

runs foo arg1 arg2 arg3 with stdout and stderr swapped, because:

  1. 3>&1 makes a copy (more precisely, a dup2) of fd 1 (stdout) on fd 3
  2. 1>&2 makes a copy of fd 2 (stderr) on fd 1 (so now stdout and stderr both go wherever they were going)
  3. 2>&3 makes a copy of fd 3 (saved original stdout) on fd 2 (stderr)
  4. 3>&- closes fd 3.

(The notable exception is that piped output "happens first", even though the pipe symbol is at the end of the simple-command part.)

Second, as pje noted, time is a bash built-in. time foo runs the command, then prints the time summary to bash's stderr. The time keyword is effectively removed first, then the remaining command sequence is handled as usual. (This applies even if the command is a pipeline: time times the entire pipeline.)

In this case, the command sequence is one simple command, with redirections:

2>&1 sh 2>&1 -c ... > file

Each redirection happens at the point it is written, and the remaining sequence is:

sh -c ...

The redirections are:

  1. send stderr to wherever stdout is currently going
  2. send stderr to stdout, again (this has no new effect)
  3. send stdout to file.

so sh -c is run with its stderr going to your stdout, and its stdout going to file file. As you note, dd prints its output to (its) stderr, which is sh -c ...'s stderr, which is your stdout.

If you run:

time 2>file dd if=/dev/zero of=ddfile bs=512 count=125

you will get time's stderr output on your stderr (e.g., screen), and dd's stderr on file file. (This happens no matter how far right you slide the 2>file part, as long as it remains part of the simple-command.)

If you try:

2>file time ...

you will find time's output redirected, but this defeats the built-in time entirely, running /usr/bin/time instead. To get bash's built-in to fire, time has to be up front. You can make a sub-shell:

(time ...) 2>file

or a sub-block as pje illustrated:

{ time ...; } 2>file

(the syntax is clumsier with the sub-block as white space and/or semicolons are needed, but it avoids a fork system call).

OTHER TIPS

If you want to redirect the output of time and its sub-commands, you could group them as a code block and redirect its stderr to the file:

$ { time sh -c "dd if=/dev/zero of=ddfile bs=512 count=125 && sync"; } 2> file

...which produces:

$ cat file
125+0 records in
125+0 records out
64000 bytes (64 kB) copied, 0.000617678 s, 104 MB/s

real        0m0.133s
user        0m0.000s
sys         0m0.020s
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top