0

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

#!/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 实现这一点?

4

3 回答 3

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
于 2021-12-11T03:42:56.777 回答
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
于 2021-12-10T22:59:15.180 回答
1

这是我的建议,基于上述答案。对于需要捕获 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

谢谢你们的帮助!

于 2021-12-13T13:06:16.010 回答