Reputation: 24768
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?
Upvotes: 1
Views: 625
Reputation: 489093
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:
3>&1
makes a copy (more precisely, a dup2
) of fd 1 (stdout) on fd 31>&2
makes a copy of fd 2 (stderr) on fd 1 (so now stdout and stderr both go wherever they were going)2>&3
makes a copy of fd 3 (saved original stdout) on fd 2 (stderr)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:
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).
Upvotes: 3
Reputation: 22757
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
Upvotes: 4