How can I make a bash command run periodically?

前端 未结 8 2010
执笔经年
执笔经年 2020-12-07 20:26

I want to execute a script and have it run a command every x minutes.

Also any general advice on any resources for learning bash scripting could be really cool. I us

8条回答
  •  半阙折子戏
    2020-12-07 20:37

    macOS users: here's a partial implementation of the GNU watch command (as of version 0.3.0) for interactive periodic invocations for primarily visual inspection:

    It is syntax-compatible with the GNU version and fails with a specific error message if an unimplemented feature is used.

    Notable limitations:

    • The output is not limited to one screenful.
    • Displaying output differences is not supported.
    • Using precise timing is not supported.
    • Colored output is always passed through (--color is implied).

    Also implements a few non-standard features, such as waiting for success (-E) to complement waiting for error (-e) and showing the time of day of the last invocation as well as the total time elapsed so far.

    Run watch -h for details.

    Examples:

    watch -n 1 ls # list current dir every second
    watch -e 'ls *.lockfile' # list lock files and exit once none exist anymore.
    

    Source code (paste into a script file named watch, make it executable, and place in a directory in your $PATH; note that syntax highlighting here is broken, but the code works):

    #!/usr/bin/env bash
    
    THIS_NAME=$(basename "$BASH_SOURCE")
    
    VERSION='0.1'
    
    # Helper function for exiting with error message due to runtime error.
    #   die [errMsg [exitCode]]
    # Default error message states context and indicates that execution is aborted. Default exit code is 1.
    # Prefix for context is always prepended.
    # Note: An error message is *always* printed; if you just want to exit with a specific code silently, use `exit n` directly.
    die() {
      echo "$THIS_NAME: ERROR: ${1:-"ABORTING due to unexpected error."}" 1>&2
      exit ${2:-1} # Note: If the argument is non-numeric, the shell prints a warning and uses exit code 255.
    }
    
    # Helper function for exiting with error message due to invalid parameters.
    #   dieSyntax [errMsg]
    # Default error message is provided, as is prefix and suffix; exit code is always 2.
    dieSyntax() {
      echo "$THIS_NAME: PARAMETER ERROR: ${1:-"Invalid parameter(s) specified."} Use -h for help." 1>&2
      exit 2
    }
    
    # Get the elapsed time since the specified epoch time in format HH:MM:SS.
    # Granularity: whole seconds.
    # Example:
    #   tsStart=$(date +'%s')
    #   ...
    #   getElapsedTime $tsStart 
    getElapsedTime() {
      date -j -u -f '%s' $(( $(date +'%s') - $1 ))  +'%H:%M:%S' 
    }
    
    # Command-line help.
    if [[ "$1" == '--help' || "$1" == '-h' ]]; then
      cat < 0") )) || dieSyntax "$errMsg"
          [[ $interval == *.* ]] || interval+='.0'
          ;;
        -t|--no-title)
          noHeader=1
          ;;
        -q|--quiet)
          quietStdOut=1
          ;;
        -Q|--quiet-both)
          quietStdOutAndStdErr=1
          ;;
        -?|--?*) # An unrecognized switch.
          dieSyntax "Unrecognized option: '$1'. To force interpretation as non-option, precede with '--'."
          ;;
        *)  # 1st data parameter reached; proceed with *argument* analysis below.
          break
          ;;
      esac
      shift
    done
    
    # Make sure we have at least a command name
    [[ -n "$1" ]] || dieSyntax "Too few parameters specified."
    
    # Suppress output streams, if requested.
    # Duplicate stdout and stderr first.
    # This allows us to produce output to stdout (>&3) and stderr (>&4) even when suppressed.
    exec 3<&1 4<&2  
    if (( quietStdOutAndStdErr )); then
      exec &> /dev/null
    elif (( quietStdOut )); then
      exec 1> /dev/null
    fi
    
    # Set an exit trap to ensure that the duplicated file descriptors are closed.
    trap 'exec 3>&- 4>&-' EXIT
    
    # Start loop with periodic invocation.
    # Note: We use `eval` so that compound commands - e.g. 'ls; bash --version' - can be passed.
    tsStart=$(date +'%s')
    while :; do
      (( dontClear )) || clear
      (( noHeader )) || echo "Every ${interval}s. [$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)]: $@"$'\n' >&3
      if (( useExec )); then
        (exec "$@")  # run in *subshell*, otherwise *this* script will be replaced by the process invoked
      else
        if [[ $* == *' '* ]]; then
          # A single argument with interior spaces was provided -> we must use `bash -c` to evaluate it properly.
          bash -c "$*"
        else
          # A command name only or a command name + arguments were specified as separate arguments -> let bash run it directly.
          "$@"
        fi
      fi
      ec=$?
      (( ec != 0 && beepOnErr )) && printf '\a'
      (( ec == 0 && runUntilSuccess )) && { echo $'\n'"[$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)] Exiting as requested: exit code 0 reported." >&3; exit 0; }
      (( ec != 0 && runUntilFailure )) && { echo $'\n'"[$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)] Exiting as requested: non-zero exit code ($ec) reported." >&3; exit 0; }
      sleep $interval
    done
    

提交回复
热议问题