Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I’ve been playing around with trying to make a timeout using just bash builtins, motivated by the fact that my Mac doesn’t have the timeout command.

I haven’t quite been able to do it using _only_ builtins, but if you allow the sleep command (which has been standardised since the first version of POSIX, so it should be available pretty much anywhere that makes any sort of attempt to be POSIX compliant), then this seems ok:

  # TIMEOUT SYSTEM
  #
  # Defines a timeout function:
  #
  # Usage: timeout <num_seconds> <command>
  #
  # which runs <command> after <num_seconds> have elapsed, if the script
  # has not exited by then.

  _alarm() {
      local timeout=$1

      # Spawn a subshell that sleeps for $timeout seconds
      # and then sends us SIGALRM
      (
          sleep "$timeout"
          kill -ALRM $$
      ) &

      # If this shell exits before the timeout has fired,
      # clean up by killing the subshell
      subshell_pid=$!
      trap _cleanup EXIT
  }

  _cleanup() {
      if [ -n "$subshell_pid" ]
      then
          kill "$subshell_pid"
      fi
  }

  timeout() {
      local timeout=$1
      local command=$2

      trap "$command" ALRM
      _alarm "$timeout"
  }

  # MAIN PROGRAM

  times_up() {
      echo 'TIME OUT!'
      subshell_pid=
      exit 1
  }

  timeout 10 times_up

  for i in {1..20}
  do
      sleep 1
      echo $i
  done


Here is my take on this from 12 years ago after following the advice of a stack overflow post:

https://github.com/gentoo/genkernel/commit/a21728ae287e988a1...

With that (minus the gen_die() line unless you copy that helper function too), you can do:

  doSomething() {
      for i in {1..20}
      do
          sleep 1
          echo $i
      done
  }
  if ! call_func_timeout doSomething 10; then
      echo 'TIME OUT!'
      exit 1
  fi
Similarly to you, I only used shell builtins, plus the sleep command. The genkernel code is run by busybox ash, so the script had to be POSIX conformant. Note that both your script and my example script reimplementing your script with my code from 12 years ago, use {1..20}, which I believe is a bashism and is not POSIX conformant, but that is fine for your use case.

My innovation over the stack overflow post was to have the exit status return true when the timeout did not trigger and false when the time out did trigger, so that error handling could be done inline in the main script (even if that error handling is just printing a message and exiting). I felt that made code using this easy to read.


I've written something 13 years ago using `read -t` https://github.com/kahing/bin/blob/master/timeout.sh


Wow, that's a cool trick. So it _can_ be done using only builtins! Thanks for sharing.


    <command> & sleep <timeout>; kill -SIGALRM %1


That's ok, but %1 will refer to the wrong job if there's already a background job running. You could use %% instead, but that will refer to the wrong job if <command> terminates before the timeout.


This always sleep for timeout rather than terminating when the command terminates.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: