3

I have a bash time keyword command which I cannot fully explain, but it works for me.

My Goal:

  1. to find the execution time of some python script, which takes a variable btw, from within a bash script, and capture that value.

  2. also to capture the output of the python script in the same variable, for success or failure analysis.

After reading around I ended up with this command, but I am not fully able to explain it.

ttime=$( (TIMEFORMAT="%U^"; time  /../myscript.py ${__myvar} 2>&1 )|& tr -d f)

Can you explain why I need the second command tr -d f in the pipe, for the time value to be appended to the output of the command,

The choice of 'tr -d f' was entirely arbitrary, and will not affect my script's output, std or err, but without it, or another command which acts on the scripts text output in some minor way, I see the return from the python without the time appended, why?

Edit:

The real question should have been why is the |& tr -d f . needed, and as Stephanie says it is the |& which is allowing the timings found in the sterr from the time keyword to be passed into the pipeline output

Solution now looks like:

ttime=$(TIMEFORMAT="%U^"; { time /../myscript.py ${__myvar} ; } 2>&1 )

Field Descriptor Tutorial:

http://wiki.bash-hackers.org/howto/redirection_tutorial

PuzzleTime
  • 33
  • 3

1 Answers1

4

In bash, like in ksh, time is a keyword (not builtin) that is used to time a pipeline (not only simple command, it can also time compound commands).

In time cmd 2> something, we're timing cmd 2> something and printing the output to stderr, but to the original stderr.

You need stderr redirected before the time construct is invoked. Which you do with your |& that redirects the stdout and stderr of the subshell time is run in, but a much simpler way to do it would be:

time=$(TIMEFORMAT="%U^"; { time cmd; } 2>&1)

That doesn't involve a subshell (here we use a command group instead) nor an extra command.

Note that with bash:

time=$(time (cmd) 2>&1)

happens to work by accident. I wouldn't rely on that as it might change in future versions and doesn't work in other shells that have a time keyword.

If you wanted only the timing output in $time (and not the command's stdout or stderr), you'd do:

{ time=$(TIMEFORMAT="%U^"; { time cmd 2>&3 3>&-; } 2>&1); } 3>&1
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • I do not think you answered the question - why is the time output only appended when the second command is in the pipeline. I tried it without the `tr`command and the |& but still did not capture both command output and time output. – PuzzleTime Nov 02 '17 at 13:51
  • @PuzzleTime, as I said (see edit for further clarification), it's the `|&` that redirects the fd where the timing is written that does the trick in your example. You need to redirect stderr **before** the `time ...` construct is run which `(time ...) |& ...` or `{ time ...; } 2>&1` do, but **not** `time cmd 2>&1`. – Stéphane Chazelas Nov 02 '17 at 16:18
  • I followed up on the use of field descriptors and their duplication, and noted credits to you doted around. I now see what the constructs really are here. Thank you. I am not really happy with the answer where you say 'happens to work by accident' though. Please explain what you mean, a side effect? in computing 'by accident' cannot be the right answer. – PuzzleTime Nov 06 '17 at 14:39
  • @PuzzleTime, it's an accident of implementation, not by design. IIRC there's a Q&A here that covers it. It's been discussed on the bash and/or austin-group-l mailing list as well. – Stéphane Chazelas Nov 06 '17 at 15:11