2013-06-30 70 views
28

我想執行一個腳本並讓它每x分鐘運行一次命令。如何定期運行bash命令?

任何資源學習bash腳本的任何一般建議可能是非常酷。我使用Linux進行個人開發工作,所以bash腳本對我來說並不完全陌生,我只是沒有從頭開始編寫自己的腳本。

+1

你看看'cron'嗎? – SheetJS

+3

至於你對資源的要求,bash-hacker維基的資源清單(http://wiki.bash-hackers.org/scripting/tutoriallist)有一個精心修剪,以避免指南不要小心避免經典的[陷阱](http://mywiki.wooledge.org/BashPitfalls)。 – kojiro

回答

53

如果要定期運行的命令,有3種方式:

  • 使用crontab命令前。 while true; do ./my_script.sh; sleep 60; done(不準確)使用systemd timer

cron

最佳的bash腳本的做法有些指針

  • http://mywiki.wooledge.org/BashFAQ
    使用循環像* * * * * command(運行每分鐘)

  • 指南:http://mywiki.wooledge.org/BashGuide
    裁判:http://www.gnu.org/software/bash/manual/bash.html
    http://wiki.bash-hackers.org/
    使用更多的報價!:http://www.grymoire.com/Unix/Quote.html
    腳本和更多:http://www.shelldorado.com/

  • +1

    Crontab在這裏是更好的選擇,尤其是如果你想運行這個「永遠」(很長一段時間)。 – DaGardner

    +3

    我不知道爲什麼這個答案被拒絕。 –

    +1

    好問題:) +1補償 – DaGardner

    2

    我要執行的腳本,並使其運行一個命令每{時間間隔}

    cronhttps://en.wikipedia.org/wiki/Cron)就是爲此目的而設計的。如果您運行man cronman crontab,您將找到有關如何使用它的說明。

    有關學習bash腳本的任何資源的任何一般建議可能非常酷。我使用Linux進行個人開發工作,所以bash腳本對我來說並不完全陌生,我只是沒有從頭開始編寫自己的腳本。

    如果您對bash的使用感覺很舒服,我建議您先閱讀bash的手冊頁(man bash) - 這裏有很多很酷的小技巧。

    16

    除@ sputnick的回答外,還有watch。從手冊頁:

    Execute a program periodically, showing output full screen 
    

    默認情況下,這是每2秒。例如,watch對於tail ing日誌很有用。

    +3

    重要的是要指出'watch'是非標準的,默認情況下在大多數非GNU系統(包括Mac)上都不存在。 – kojiro

    +6

    for tail'ing logs你最好使用'tail -f $ filename' – Francisco

    6

    MacOS的用戶:這裏是一個部分執行GNU watch命令的(作爲0.3.0版本)互動定期調用的主要視覺檢查:

    這是語法兼容與GNU版本一起使用,如果使用未實現的特性,則會失敗並顯示特定的錯誤消息。

    明顯侷限性:

    • 輸出不限於一個屏。
    • 不支持顯示輸出差異。
    • 不支持使用精確計時。
    • 彩色輸出是總是通過(--color暗示)。

    還實現了一些非標準的特性,如等待成功-E),以補充等待錯誤(-e)和顯示,以及最後調用的一天中的時間爲總時間的流逝至今。

    運行watch -h瞭解詳情。

    例子:

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

    源代碼(粘貼到名爲watch一個腳本文件,使其可執行文件,並將其放置在目錄中你$PATH;注意語法這裏強調壞了,但代碼作品):

    #!/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 <<EOF 
    
    SYNOPSIS 
        $THIS_NAME [-n seconds] [opts] cmd [arg ...] 
    
    DESCRIPTION 
        Executes a command periodically and displays its output for visual inspection. 
    
        NOTE: This is a PARTIAL implementation of the GNU \`watch\` command, for OS X. 
        Notably, the output is not limited to one screenful, and displaying 
        output differences and using precise timing are not supported. 
        Also, colored output is always passed through (--color is implied). 
        Unimplemented features are marked as [NOT IMPLEMENTED] below. 
        Conversely, features specific to this implementation are marked as [NONSTD]. 
        Reference version is GNU watch 0.3.0. 
    
        CMD may be a simple command with separately specified 
        arguments, if any, or a single string containing one or more 
        ;-separated commands (including arguments) - in the former case the command 
        is directly executed by bash, in the latter the string is passed to \`bash -c\`. 
        Note that GNU watch uses sh, not bash. 
        To use \`exec\` instead, specify -x (see below). 
    
        By default, CMD is re-invoked indefinitely; terminate with ^-C or 
        exit based on conditions: 
        -e, --errexit 
        exits once CMD indicates an error, i.e., returns a non-zero exit code. 
        -E, --okexit [NONSTD] 
        is the inverse of -e: runs until CMD returns exit code 0. 
    
        By default, all output is passed through; the following options modify this 
        behavior; note that suppressing output only relates to CMD's output, not the 
        messages output by this utility itself: 
        -q, --quiet [NONSTD] 
        suppresses stdout output from the command invoked; 
        -Q, --quiet-both [NONSTD] 
        suppresses both stdout and stderr output. 
    
        -l, --list [NONSTD] 
        list-style display; i.e., suppresses clearing of the screen 
        before every invocation of CMD. 
    
        -n secs, --interval secs 
        interval in seconds between the end of the previous invocation of CMD 
        and the next invocation - 2 seconds by default, fractional values permitted; 
        thus, the interval between successive invocations is the specified interval 
        *plus* the last CMD's invocation's execution duration. 
    
        -x, --exec 
        uses \`exec\` rather than bash to execute CMD; this requires 
        arguments to be passed to CMD to be specified as separate arguments 
        to this utility and prevents any shell expansions of these arguments 
        at invocation time. 
    
        -t, --no-title 
        suppresses the default title (header) that displays the interval, 
        and (NONSTD) a time stamp, the time elapsed so far, and the command executed. 
    
        -b, --beep 
        beeps on error (bell signal), i.e., when CMD reports a non-zero exit code. 
    
        -c, --color 
        IMPLIED AND ALWAYS ON: colored command output is invariably passed through. 
    
        -p, --precise [NOT IMPLEMENTED] 
    
        -d, --difference [NOT IMPLEMENTED] 
    
    EXAMPLES 
        # List files in home folder every second. 
        $THIS_NAME -n 1 ls ~ 
        # Wait until all *.lockfile files disappear from the current dir, checking every 2 secs. 
        $THIS_NAME -e 'ls *.lockfile' 
    
    EOF 
        exit 0 
    fi 
    
        # Make sure that we're running on OSX. 
    [[ $(uname) == 'Darwin' ]] || die "This script is designed to run on OS X only." 
    
    # Preprocess parameters: expand compressed options to individual options; e.g., '-ab' to '-a -b' 
    params=() decompressed=0 argsReached=0 
    for p in "[email protected]"; do 
        if [[ $argsReached -eq 0 && $p =~ ^-[a-zA-Z0-9]+$ ]]; then # compressed options? 
        decompressed=1 
        params+=(${p:0:2}) 
        for ((i = 2; i < ${#p}; i++)); do 
         params+=("-${p:$i:1}") 
        done 
        else 
        ((argsReached && ! decompressed)) && break 
        [[ $p == '--' || ${p:0:1} != '-' ]] && argsReached=1 
        params+=("$p") 
        fi 
    done 
    ((decompressed)) && set -- "${params[@]}"; unset params decompressed argsReached p # Replace "[email protected]" with the expanded parameter set. 
    
    # Option-parameters loop. 
    interval=2 # default interval 
    runUntilFailure=0 
    runUntilSuccess=0 
    quietStdOut=0 
    quietStdOutAndStdErr=0 
    dontClear=0 
    noHeader=0 
    beepOnErr=0 
    useExec=0 
    while (($#)); do 
        case "$1" in 
        --) # Explicit end-of-options marker. 
         shift # Move to next param and proceed with data-parameter analysis below. 
         break 
         ;; 
        -p|--precise|-d|--differences|--differences=*) 
         dieSyntax "Sadly, option $1 is NOT IMPLEMENTED." 
         ;; 
        -v|--version) 
         echo "$VERSION"; exit 0 
         ;; 
        -x|--exec) 
         useExec=1 
         ;; 
        -c|--color) 
         # a no-op: unlike the GNU version, we always - and invariably - pass color codes through. 
         ;; 
        -b|--beep) 
         beepOnErr=1 
         ;; 
        -l|--list) 
         dontClear=1 
         ;; 
        -e|--errexit) 
         runUntilFailure=1 
         ;; 
        -E|--okexit) 
         runUntilSuccess=1 
         ;; 
        -n|--interval) 
         shift; interval=$1; 
         errMsg="Please specify a positive number of seconds as the interval." 
         interval=$(bc <<<"$1") || dieSyntax "$errMsg" 
         ((1 == $(bc <<<"$interval > 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)]: [email protected]"$'\n' >&3 
        if ((useExec)); then 
        (exec "[email protected]") # 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. 
         "[email protected]" 
        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