4

I have command producing output belonging to different commands.

s=$(time dd if=<source> of=/dev/null bs=<number> count=<number> 2>&1)

$s will contain output only from dd command. How can I have another variable which will contain the output from time?

yart
  • 7,515
  • 12
  • 37
  • 37
  • 2
    http://stackoverflow.com/q/3928430/469220 – Vlad Nov 18 '13 at 15:25
  • You can also use `/usr/bin/time` or `/bin/time` instead of the shell built-in. That's a regular command that follows the regular rules of redirection. The `bash` built-in `time` is slightly weird, as you've discovered. – Jonathan Leffler Nov 18 '13 at 15:53
  • @Jonathan, thank you for comment. I can use only built-in command. – yart Nov 18 '13 at 15:56
  • See also [`bash` script write executing time in a file](http://stackoverflow.com/questions/13176611/bash-script-write-executing-time-in-a-file). – Jonathan Leffler Nov 18 '13 at 15:58

3 Answers3

3

Try the following:

s=$({ time dd ... > /dev/null;} 2>&1)

Chepners answer gave me inspiration for the arguably better:

s=$(exec 2>&1; time dd ... > /dev/null)

$() is already a subshell, so no need to create an extra subshell. Putting exec 2>&1; before the command redirects the stdout of the entire subshell, putting it after the command would make it part of the command to time and hence is applied only to the command passed to time. Omitting the exec and semicolon will try to execute the system time binary intead of the builtin, and error out if that does not exist. Omitting just the exec but keeping the semicolon will not work as that would cause only newly forked processes to be redirected and the time builtin will not be a new process.

Oh and by the way, the > /dev/null does not dump the output of time itself to /dev/null because it too is part of the command that time executes and is not applied to time itself.

wich
  • 16,709
  • 6
  • 47
  • 72
  • @JonathanLeffler,`s=$( exec 2>&1; time dd if=something of=/dev/null )` would be indeed a more predictable behaviour ;-) – thom Nov 18 '13 at 16:10
  • Quietly ignore me...I mistyped what you specified. – Jonathan Leffler Nov 18 '13 at 16:12
  • @which, I have tried your command, 's=$(2>&1 time > /dev/null) and it returns, -sh: time: command not found. I have GNU bash, version 4.1.5. – yart Nov 18 '13 at 16:20
  • @wich, without `exec` it doesn't work in my bash shell. I seem to remember that a lone `2>&1` redirects the output of a previous command which is not there when a fresh shell starts. So what is going to happen is a bit unpredictable and seems to differ between shell versions. However, `exec 2>&1` at the begin of a shell is well documented as "global" redirection, hence my comment. :-) – thom Nov 18 '13 at 17:20
  • @yart indeed, it seems this syntax does not work anymore on Bash 4. Or actually it might be that without the semicolon bash is trying to call a system binary instead of the builtin, I'm not 100 sure. Anyway, adding a semicolon between the redirect and time fixes it. I updated the answer. – wich Nov 18 '13 at 17:32
  • @thom the difference is that `2>&1` only affects newly forked processes and `exec 2>&1` effects the shell globally. In this case it's a bit tricky, without the semicolon it's calling the system `time` binary so it is a newly forked process and thus is redirected. After adding the semicolon it is actually using the builtin and thus does require the `exec`. Answer updated. – wich Nov 18 '13 at 17:37
  • @wich: Thanks, explains a lot, without exec but with semicolon -> doesn't work. with exec with semicolon -> internal time command. without exec without semicolon -> /usr/bin/time. Indeed tricky :-) – thom Nov 18 '13 at 20:42
1

Hello I just find out the answer.

The link from Vlad in comments gave me direction.

I can use subshells.

My command could be specified

s=$((time dd if=<source> of=/dev/null bs=<number> count=<number> 2>&1 | tail -n1) 2>&1)

Then I can have $s variable containing all data and I can have array and get values.

yart
  • 7,515
  • 12
  • 37
  • 37
1

The bash built-in time is a special built-in that writes to standard error of the current shell, so you cannot simply redirect its output. Instead, you would need to run it in a subshell whose standard error has already been redirected. This causes the "real" command to also run in a subshell, so you can't simply assign its output to variable, as that variable will go away with the subshell. Try this:

s=$( exec 2>time.txt; time echo foo )
t=$(< time.txt)
chepner
  • 497,756
  • 71
  • 530
  • 681