I have a slight modification of @Tiago Lopo's answer that can handle commands with multiple arguments. I've also tested TauPan's solution, but it does not work if you use it multiple times in a script, while Tiago's does.
function timeout_cmd { 
  local arr
  local cmd
  local timeout
  arr=( "$@" )
  # timeout: first arg
  # cmd: the other args
  timeout="${arr[0]}"
  cmd=( "${arr[@]:1}" )
  ( 
    eval "${cmd[@]}" &
    child=$!
    echo "child: $child"
    trap -- "" SIGTERM 
    (       
      sleep "$timeout"
      kill "$child" 2> /dev/null 
    ) &     
    wait "$child"
  )
}
Here's a fully functional script thant you can use to test the function above:
$ ./test_timeout.sh -h
Usage:
  test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
  test_timeout.sh -h
Test timeout_cmd function.
Options:
  -n              Dry run, do not actually sleep. 
  -r REPEAT       Reapeat everything multiple times [default: 1].
  -s SLEEP_TIME   Sleep for SLEEP_TIME seconds [default: 5].
  -t TIMEOUT      Timeout after TIMEOUT seconds [default: no timeout].
For example you cnal launch like this:
$ ./test_timeout.sh -r 2 -s 5 -t 3
Try no: 1
  - Set timeout to: 3
child: 2540
    -> retval: 143
    -> The command timed out
Try no: 2
  - Set timeout to: 3
child: 2593
    -> retval: 143
    -> The command timed out
Done!
#!/usr/bin/env bash
#shellcheck disable=SC2128
SOURCED=false && [ "$0" = "$BASH_SOURCE" ] || SOURCED=true
if ! $SOURCED; then
  set -euo pipefail
  IFS=$'\n\t'
fi
#################### helpers
function check_posint() {
  local re='^[0-9]+$'
  local mynum="$1"
  local option="$2"
  if ! [[ "$mynum" =~ $re ]] ; then
     (echo -n "Error in option '$option': " >&2)
     (echo "must be a positive integer, got $mynum." >&2)
     exit 1
  fi
  if ! [ "$mynum" -gt 0 ] ; then
     (echo "Error in option '$option': must be positive, got $mynum." >&2)
     exit 1
  fi
}
#################### end: helpers
#################### usage
function short_usage() {
  (>&2 echo \
"Usage:
  test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
  test_timeout.sh -h"
  )
}
function usage() {
  (>&2 short_usage )
  (>&2 echo \
"
Test timeout_cmd function.
Options:
  -n              Dry run, do not actually sleep. 
  -r REPEAT       Reapeat everything multiple times [default: 1].
  -s SLEEP_TIME   Sleep for SLEEP_TIME seconds [default: 5].
  -t TIMEOUT      Timeout after TIMEOUT seconds [default: no timeout].
")
}
#################### end: usage
help_flag=false
dryrun_flag=false
SLEEP_TIME=5
TIMEOUT=-1
REPEAT=1
while getopts ":hnr:s:t:" opt; do
  case $opt in
    h)
      help_flag=true
      ;;    
    n)
      dryrun_flag=true
      ;;
    r)
      check_posint "$OPTARG" '-r'
      REPEAT="$OPTARG"
      ;;
    s)
      check_posint "$OPTARG" '-s'
      SLEEP_TIME="$OPTARG"
      ;;
    t)
      check_posint "$OPTARG" '-t'
      TIMEOUT="$OPTARG"
      ;;
    \?)
      (>&2 echo "Error. Invalid option: -$OPTARG.")
      (>&2 echo "Try -h to get help")
      short_usage
      exit 1
      ;;
    :)
      (>&2 echo "Error.Option -$OPTARG requires an argument.")
      (>&2 echo "Try -h to get help")
      short_usage
      exit 1
      ;;
  esac
done
if $help_flag; then
  usage
  exit 0
fi
#################### utils
if $dryrun_flag; then
  function wrap_run() {
    ( echo -en "[dry run]\\t" )
    ( echo "$@" )
  }
else
  function wrap_run() { "$@"; }
fi
# Execute a shell function with timeout
# https://stackoverflow.com/a/24416732/2377454
function timeout_cmd { 
  local arr
  local cmd
  local timeout
  arr=( "$@" )
  # timeout: first arg
  # cmd: the other args
  timeout="${arr[0]}"
  cmd=( "${arr[@]:1}" )
  ( 
    eval "${cmd[@]}" &
    child=$!
    echo "child: $child"
    trap -- "" SIGTERM 
    (       
      sleep "$timeout"
      kill "$child" 2> /dev/null 
    ) &     
    wait "$child"
  )
}
####################
function sleep_func() {
  local secs
  local waitsec
  waitsec=1
  secs=$(($1))
  while [ "$secs" -gt 0 ]; do
   echo -ne "$secs\033[0K\r"
   sleep "$waitsec"
   secs=$((secs-waitsec))
  done
}
command=("wrap_run" \
         "sleep_func" "${SLEEP_TIME}"
         )
for i in $(seq 1 "$REPEAT"); do
  echo "Try no: $i"
  if [ "$TIMEOUT" -gt 0 ]; then
    echo "  - Set timeout to: $TIMEOUT"
    set +e
    timeout_cmd "$TIMEOUT" "${command[@]}"
    retval="$?"
    set -e
    echo "    -> retval: $retval"
    # check if (retval % 128) == SIGTERM (== 15)
    if [[ "$((retval % 128))" -eq 15 ]]; then
      echo "    -> The command timed out"
    fi
  else
    echo "  - No timeout"
    "${command[@]}"
    retval="$?"
  fi
done
echo "Done!"
exit 0