1

I have a simple code to ensure a script takes at least x seconds (500 here) on Ubuntu

t1=$(date +%s)
# script is here
t2=$(date +%s)
let "t = 500 - $t2 + $t1"
(( t = t>0 ? t : 1 ))
sleep $t

The code works perfectly, but I believe my coding is not efficient, and these three lines

t2=$(date +%s)
let "t = 500 - $t2 + $t1"
(( t = t>0 ? t : 1 ))

should be expressed in one single line. My question is how to improve the code.

5
  • Is this a Linux platform, or some UNIX? (More specifically, do you have GNU date?) – roaima Feb 4 '19 at 21:48
  • 2
    There is much to be said for not sacrificing readability for perceived efficiency. If you compress that all into an inscrutable one-liner running as a subshell expansion as a parameter for sleep, how are you going to unravel that when someone calls you at 4 in the morning the day after a most raucous party to debug it when it goes sideways? – DopeGhoti Feb 4 '19 at 21:53
  • 1
    You could lose the t and t2 entirely, but make sure your phone is switched off at 4am. sleep $( printf "%d\n" $(( 500 - ($(date +%s) - t1) )) | sed 's/^-.*/1/' ) – roaima Feb 4 '19 at 22:01
  • 1
    @roaima Sorry for being vague. I updated the question. I haven't got stuck anywhere. I just want to improve my code, as I believe it is not the efficient way. – Googlebot Feb 4 '19 at 22:01
  • @DopeGhoti very good point indeed. – Googlebot Feb 4 '19 at 22:02
1

Here is how I would do it using bash special parameter SECONDS:

#!/bin/bash                                                                                                                                                                                  

SECONDS=0
# script is here                                                                                                                                                                             
sleep "$(( 500 > SECONDS ? 500 - SECONDS : 1 ))"

Normally SECONDS returns time (in seconds) since the script has started, but one can assign any value to (re)set the timer.

1
  • 1
    Zeroing SECONDS doesn't hurt, and at least makes the initial value explicit, but since it tells the time since the script started you don't have to zero it. Unless the script does something else first, of course. – ilkkachu Feb 4 '19 at 22:25
4

What your script is, is non-portable (since you're using let and (( .. ))), confusing (since you're using both let and (( .. ))), lacking in documentation (there are no comments, and the variable names are non-descriptive), and marginally unsafe (since you haven't quoted the expansion of $t).

If you want a rewrite, here's mine:

#!/bin/sh

min_duration=500
t_start=$(date +%s)
# script is here
t_end=$(date +%s)
elapsed=$(( t_end - t_start ))

# sleep long enough to make sure 'min_duration' seconds has elapsed, 
# but at least 1 second
sleep "$(( elapsed < min_duration ? min_duration - elapsed : 1 ))"
5
  • 1
    Is there any practical difference between your : "$(( elapsed = t_end - t_start ))" and simply (( elapsed = t_end - t_start ))? – roaima Feb 4 '19 at 22:11
  • 1
    @roaima The exit status when the calculation results in a zero. It matters when running under set -e. Personally, I think I would have used elapsed=$(( ... )). Also notice that he's writing for /bin/sh, not bash. – Kusalananda Feb 4 '19 at 22:16
  • @roaima, I was just thinking about the portability point. At least dash and busybox don't support (( .. )). With Bash, I'd use (( .. )) since it looks less ugly to me. The exit status shouldn't matter (well, unless someone uses set -e, but let's not go there...) – ilkkachu Feb 4 '19 at 22:18
  • @Kusalananda, argh, of course with elapsed=$((..))... Thanks. – ilkkachu Feb 4 '19 at 22:19
  • @Kusalananda ah yes. Thankyou. – roaima Feb 5 '19 at 0:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.