【问题标题】:Bash script checkpointsBash 脚本检查点
【发布时间】:2022-01-15 12:46:39
【问题描述】:

我正在开发一个大脚本,它的骨架如下所示:

#!/bin/bash

load_variables()

function_1()
function_2()
function_3()
[...]
function_n()
  1. 在每次起飞期间,用户标志首先加载到load_variables() 函数中。
  2. 然后脚本继续执行function_1() => function_2() => [...] => function_n()

我需要实现将存储在log.txt 中的检查点。
假设该脚本已在 function_2() 处停止或崩溃。
我想在每个函数启动之前保存进度,将其存储在log.txt 中,当我再次重新运行脚本时,我想load_variables() 然后跳转到存储在log.txt 中的崩溃点/检查点。

如何使用 bash 实现这一点?

【问题讨论】:

    标签: bash checkpointing


    【解决方案1】:

    我想在每个函数启动之前保存进度,将其存储在 log.txt 中,当我再次重新运行脚本时,我想 load_variables() 然后跳转到存储在 log.txt 中的崩溃点/检查点.

    做到这一点。但是你不能在 bash 脚本中“跳转”——而不是“跳转”,只是跳过你可以跟踪的已经运行的函数。基本上,在伪代码中:

    load_variables() {
        if [[ -e log.txt ]]; then
            . log.txt
        fi
    }
    
    already_run_functions=()
    checkpoint() {
       # if the function was already run
       if "$1" in already_run_functions[@]; then
            # skip this function
            return
       fi
       {
           # save state with function name
           declare -f
           declare -p
       } > log.txt
       # run it
       if ! "$@"; then
          # handle errors
          exit 1
       fi
       already_run_functions+=("$1")
    }
    
    load_variables
    checkpoint function1
    checkpoint function2
    checkpoint function3
    

    总的来说,它是shell,很简单。使用构建系统会更好。 Simple Make 足以跟踪多个 shell 脚本的依赖关系并并行化工作。在每个任务之后将结果存储在文件中,并将函数分发到多个文件中。


    举个真实的例子:

    rm -f log.txt
    
    script() {
    
    load_variables="
        if [[ -e log.txt ]]; then
            . log.txt
            cd \"\$PWD\"
        else
           already_run_functions=()
        fi
    "
    
    
    oneof() {
      local i
      for i in "${@:2}"; do
       if [[ "$1" = "$i" ]]; then
           return 0
       fi
      done
      return 1
    }
    checkpoint() {
       # if the function was already run
       if oneof "$1" "${already_run_functions[@]}"; then
            # skip this function
            return
       fi
       {
           # save state
           declare -f
           declare -p $(compgen -v | grep -Ev '^(BASH.*|EUID|FUNCNAME|GROUPS|PPID|SHELLOPTS|UID|SHELL|SHLVL|USER|TERM|RANDOM|PIPESTATUS|LINENO|COLUMN|LC_.*|LANG)$')
       } > log.txt
       # run it
       if ! "$@"; then
          # handle errors
         echo "checkpoint: $1 failed"
          exit 1
       fi
       already_run_functions+=("$1")
    }
    
    function1() {
     echo function1
    }
    
    function2() {
     echo function2
     if [[ -e file ]]; then
        return 1
     fi
    }
    
    function3() { 
      echo function3
    }
    
    eval "$load_variables"
    checkpoint function1
    checkpoint function2
    checkpoint function3
    
    }
    
    touch file
    ( script )
    rm file
    ( script )
    

    输出:

    function1
    function2
    checkpoint: function2 failed
    function2
    function3
    

    【讨论】:

      【解决方案2】:

      这是一个带有陷阱的示例。 使用陷阱和 $LINENO 保存函数名称错误

      如果一切正常,日志文件将被删除

      #!/bin/bash
      
      trap 'clear_log' EXIT
      trap 'log_checkpoint $LINENO' ERR
      
      CHECKLOG=checkpoints.log
      
      clear_log() {
          if [ $? -eq 0 ] ; then
              if [ -f "$CHECKLOG" ]; then
                  rm "$CHECKLOG"
              fi
          fi
      }
      
      log_checkpoint() {
        func=$(sed -n $1p $0)
        echo "Error on line $1: $func"
        echo "$func" > $CHECKLOG
        exit 1
      }
      
      retry(){
        [ ! -f $CHECKLOG ] && return 0
        if grep -q "$1" "$CHECKLOG"; then
                echo retry "$1"; rm "$CHECKLOG"; return 0
        else
                echo skip "$1"; return 1
        fi
      }
      
      func1(){
        retry ${FUNCNAME[0]} || return 0
        echo hello | grep hello
      }
      
      func2(){
        retry ${FUNCNAME[0]} || return 0
        echo hello |grep hello
      }
      
      func3(){
        retry ${FUNCNAME[0]} || return 0
        echo hello | grep foo
      }
      
      func1
      func2
      func3
      
      exit 0
      

      【讨论】:

      • 谢谢,我一定会用这个选项。圣诞快乐!
      【解决方案3】:

      这是我的建议,基于上述答案。 对于需要捕获 CTRL + C 和除错误之外的其他崩溃的人来说,它可能会有所帮助:

      #!/bin/bash
      
      ### Catch crash in trap and save the function name in anchor.log
      trap 'echo $anchor > anchor.log && exit 1' SIGINT
      trap 'echo $anchor > anchor.log && exit 1' SIGHUP
      trap 'echo $anchor > anchor.log && exit 1' SIGKILL
      trap 'echo $anchor > anchor.log && exit 1' SIGTERM
      ### ---
      
      
      clear_log() {
          ### REMOVING LOG IF PROGRAM EXIT NORMALLY
          if [ -f "anchor.log" ]; then
              rm "anchor.log"
          fi
      }
      
      anchor_check(){
          ### RETRY FUNCTION IF IT'S NOT IN anchor.log
          anchor=$1
          # Check if function name is inside "anchor.log"
          [ ! -f "anchor.log" ] && return 0
          if grep -q "$1" "anchor.log"; then
              rm "anchor.log"; return 0
          else
              return 1
          fi
      }
      
      
      ### EACH FUNCTION START WITH anchor_check 
      function_1() {
          anchor_check "${FUNCNAME[0]}" || return 0
          echo "test $anchor"
          sleep 2
      }
      
      function_2() {
          anchor_check "${FUNCNAME[0]}" || return 0
          echo "test $anchor"
          sleep 2
      }
      
      function_3() {
          anchor_check "${FUNCNAME[0]}" || return 0
          echo "test $anchor"
      }
      
      
      function_1
      function_2
      function_3
      
      clear_log
      

      谢谢大家的帮助!

      【讨论】:

        猜你喜欢
        • 2018-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-02
        相关资源
        最近更新 更多