8

How can one measure individual calls to bash functions from inside the bash file.

I have a program that I call using the command

eclipse -b col_solve.pl -e "myPred"

This call outputs some information, the last of which is SUCCESS or FAIL. I am writing a script that is called on a bunch of files in a directory, and for each of these files, outputs

  • The name
  • The status (SUCCESS or FAIL)
  • and the (user) time it took to execute .

This is the code that I know works:

I use this to get the status (retrieving the last word in the output):

stat=
get_stat ( ){
    stat=$(awk '{print $NF}' <<< $1);
}

I use this to call the program :

run_eclipse_on ( ){
    get_stat "$(eclipse -b col_solve.pl -e "run(\"$1\")" )";
}

The problematic code is the following:

for i in `ls $1` ;  #where $1 is the directory containing the files
do
    tps=$(/usr/bin/time -f %U      \ #to get just the user time
         [run_eclipse_on $1/$i] ); # HERE it is! 
    echo $i::$stat::::$tps;  # gives, for ex: file_name::SUCCESS::::0.20
done

The culprit line is the one where the function is called. I tried surrounding it with `, {, [, $(, ' and ". Nothing worked...

Is it even possible...?

4
  • This is very very very overcomplicated for shell scripting. Just use a pipeline. You should be able to stuff all of this in a single command; you don't need functions at all.
    – Wildcard
    May 10, 2016 at 18:12
  • I cut the relevant part of the script, but I use these functions at several places in the script (I have a 5 or so if-elif-elif ... and they nearly all use these two functions.
    – ZakC
    May 10, 2016 at 21:40
  • Fair enough. You should still check out the Bash guide, particularly the section on case switches.
    – Wildcard
    May 10, 2016 at 21:43
  • Also, if you're going to rely heavily on command substitution for passing text back from functions et. al., you definitely need to learn about quoting your variables.
    – Wildcard
    May 10, 2016 at 21:44

3 Answers 3

13

Use the time keyword instead of the external command. Using the keyword allows you to run time on any shell command, including function calls, not just on running a program. You can control the output format to some extent through the TIMEFORMAT variable.

TIMEFORMAT=%2U
time run_eclipse_on …
echo "$i::$stat"

The time output gets printed on its own line, though. Bash allows a trick: you can change TIMEFORMAT during the command, so you can stuff more things in there.

time { run_eclipse_on …; TIMEFORMAT="${i//%/%%}::${stat//%/%%}::%2U"; }

The output from time is printed to standard error. If you need it on standard output, just redirect with 2>&1. That will also redirect whatever the command printed on stderr, however. To preserve stderr, you can do some file descriptor shuffling.

{ time { {
      run_eclipse_on …;
      TIMEFORMAT=$stat::%2U;
    } 2>&3; } 2>&1; } 3>&2
4
  • Thank you ! Can you please explain what the ">&3 2>&4 ..." mean? Or, if not, can you briefly point me to where I can understand these?
    – ZakC
    May 11, 2016 at 7:59
  • I tried tps=$({ time run_eclipse_on $1 $2/$i >&3 2>&4; } 2>&1) 3>&1 4>&2; echo "$i::$stat::::$tps"; , but it fails with "bad file descriptor". But if I only write tps=$({ time run_eclipse_on $1 $2/$i; } ) ;, it runs, although it does not print the stat. I understand that >$n are redirections, but what is the scheme these redirections are following ? Is it a standard way to redirect ? Is it specific to this code ?
    – ZakC
    May 11, 2016 at 8:25
  • [UPDATE] I found a way of doing what I wanted but it's ugly: tps=$({ time run_eclipse_on $1 $2/$i >mytemp 4>&2 2>&4; } 2>&1) 4>&2; stat=$(<mytemp); echo "$i::$stat::$tps"; And rm mytemp afterwards...(It works also with no "2" and "4" redirection, but I kept them because, for some, the time was printed on a new line and using awk to remove any undesired new lines didn't work)
    – ZakC
    May 11, 2016 at 9:19
  • 1
    @ZakC I'd messed up the file descriptors, but anyway what I wrote fundamentally didn't work because the stat variable was set in a subshell and used in the parent. I've edited my answer to provide a different solution. May 11, 2016 at 11:03
0

It sounds like you want something like this:

#!/bin/bash

for f in "$1"/*; do
  time eclipse -b col_solve.pl -e "$f" | tail -n 1
done

Don't use functions in shell scripts unless you actually need them. The benefit of using shell scripting at all is that you can easily orchestrate other tools. Let the tools do the work, not the shell. Just use the shell to string together the other tools.

Further reading:

1
  • 4
    Using a function here is perfectly fine (especially if the code presented here is a simplification of the real code). May 10, 2016 at 22:52
-2

While not the same as "user time", if elapsed time will suffice, another option is to save the start and end times and compute the time elapsed in your function call. The timer function listed here makes this easier.

Reproducing the function here for easier reference:

# Elapsed time.  Usage:
#
#   t=$(timer)
#   ... # do something
#   printf 'Elapsed time: %s\n' $(timer $t)
#      ===> Elapsed time: 0:01:12
#
#
#####################################################################
# If called with no arguments a new timer is returned.
# If called with arguments the first is used as a timer
# value and the elapsed time is returned in the form HH:MM:SS.
#
function timer()
{
    if [[ $# -eq 0 ]]; then
        echo $(date '+%s')
    else
        local  stime=$1
        etime=$(date '+%s')
        if [[ -z "$stime" ]]; then stime=$etime; fi
        dt=$((etime - stime))
        ds=$((dt % 60))
        dm=$(((dt / 60) % 60))
        dh=$((dt / 3600))
        printf '%d:%02d:%02d' $dh $dm $ds
    fi
}

In your case, you would include this function and then:

for i in *
do
    t=$(timer)
    run_eclipse_on $i
    elapsed=$(timer $t)
    echo $i::$stat::::$elapsed  # gives, for ex: file_name::SUCCESS::::0:00:03
done
2
  • Don't parse ls. Also, you're doing it wrong; single quotes are not backticks.
    – Wildcard
    May 10, 2016 at 18:09
  • 1
    It's better. But you still need to quote your variables. Also, I see that you got that function from an internet article, but it's really shameful that they published it as-is; for instance, there is no reason whatsoever to use echo $(date '+%s') instead of simply date +%s (and those characters are not special to the shell so no reason to quote them).
    – Wildcard
    May 10, 2016 at 20:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .