0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

シンプルなバックアップシェルスクリプト

Posted at

はじめに

もともとGit管理されているリポジトリがあったわけだが、引継ぎ時点で100以上のunstagedなログやらプログラムのバックアップがあった。これから先もこうしていくと気が重くなる。とりあえず、だれでも使えそうな(Gitとは無関係の)バックアップスクリプトを作ってみた。

のを放置していていたので、とりあえずどっかに貼っとかないと永遠に忘れると思い、メモ掲載。

書式

書式: backup.sh [オプション....] [対象バックアップファイル]
オプション:
 -q 静止モード。コミットメッセージは quiet modeと書かれる
 -d デバッグモード。処理内容を確認できる。
 -l  履歴を見る
 -b  自動モード。何も出力せず、コミットメッセージにはバックアップ日時が書かれる。
 -p [mesg] -bと併用の際、日時の手前に書く

ユースケース

  • crontabbackup.sh -b -p 'auto' path/to/file.cpp とか書いて自動バックアップ
  • 重要なバックアップはメッセージ書く

image.png

履歴をみたいときは、 -l する

image.png

直近の履歴と同じハッシュならバックアップしない

mysrv 11:25:48 > ./backup.sh backup.sh
INFO:: ハッシュ値照合中... [backup.sh.20221122d.bk]
INFO:: バックアップファイルと同じハッシュのファイルなため、バックアップを実施しません。
mysrv 11:26:27 >

履歴データは単純CSVなので適当にいじれる。
image.png

スクリプト本体

#!/bin/bash -eu
###############################################
# File based backup
# Author: Anya-tokugawa
cat << COMMENT > /dev/null
機能:
 - 日時+アルファベット順+アルファベット文字数でバックアップ

例:
ver1   hoge.sh.20210103a.bk
ver2   hoge.sh.20210105a.bk
ver3   hoge.sh.20210105b.bk
ver4   hoge.sh.20210105c.bk
...
       hoge.sh.20210105z.bk
       hoge.sh.20210105aa.bk
       hoge.sh.20210105ab.bk
...
       hoge.sh.20210105at.bk
       hoge.sh.20210106a.bk
...
latest hoge.sh

 - 一日最大, a-zzzzまで可能。1ファイル475254回可能。一秒間に5回バックアップ可能。


TODO:
 - バックアップデータに関するハッシュリポジトリを作る。
 - バックアップデータ自体はリポジトリに格納しリンクだけする?
 - 同じハッシュのファイルを集約
 - コミットメッセージの実装(済)
   - 削除されたバックアップファイルの自動検出
   - 複数行のコミットメッセージをつくる。
   - backupDirにしてHashのファイルを作る。
 - 直前とのdiff/colordiffの実装

COMMENT

INFO=1
AUTO=0
DEBUG=0
VIEW=0
mesgPrefix=""
##############################
# リポジトリ設定
INFOFILE=".backup.csv"
if [[ ! -f "$INFOFILE" ]];then
  echo -n '' > "$INFOFILE"
fi
##############################
_LOG(){
 if [[ "$1" == "ERROR" ]];then
   shift
   if [[ $INFO -ne 0 ]];then
    echo "ERROR:: $*"
   fi
   exit 1
 fi
 if [[ "$1" == "INFO" ]] && [[ $INFO -eq 1 ]];then
   shift
   echo "INFO:: $*"
 elif [[ "$1" == "DEBUG" ]] && [[ $DEBUG -eq 1 ]];then
   shift
   echo "DEBUG:: $*"
fi
}

# 19990101a方式のバージョンソート

_sort(){
while read -r line
do
  echo "${#line} $line"
done < /dev/stdin  | sort -n | cut -d " " -f 2-
}


# バックアップ処理
_backup(){

  if [[ $INFO -eq 1 ]] && [[ $AUTO -eq 0 ]];then
    rsync -z --progress "$1" "$2"
  else
    rsync -qz --progress "$1" "$2"
  fi

  status_code=$?

  if [[ $status_code -eq 0 ]];then
    _LOG DEBUG 書き込み禁止化
    chmod a-w "$2"

    mesg=""
    if [[ $INFO -eq 1 ]] && [[ $AUTO -eq 0 ]];then
      echo -n "メッセージ: "
      read -r mesg
    elif [[ $AUTO -eq 1 ]];then
      if [[ "$mesgPrefix" == "" ]];then
        mesg="$(date)"
      else
        mesg="$mesgPrefix $(date)"
      fi
    else
      mesg="quit-mode"
    fi

    chmod +w $INFOFILE

    # baseFileName, VersionStr,HASH, mesg
    echo "${1},${3},${4},${mesg}" >> "$INFOFILE"

    chmod -w $INFOFILE

  fi
}

_usage(){
  echo "書式: $0 [オプション....] [対象バックアップファイル]"
  echo "オプション:"
  echo " -q 静止モード。コミットメッセージは quiet modeと書かれる"
  echo " -d デバッグモード。処理内容を確認できる。"
  echo " -l  履歴を見る"
  echo " -b  自動モード。何も出力せず、コミットメッセージにはバックアップ日時が書かれる。"
  echo " -p [mesg] -bと併用の際、日時の手前に書く。"
}



if [[ $# -lt 1 ]];then
 _usage
 exit 1
fi

NOW="$1"
if [[ $# -gt 1 ]];
then
  while [[ $# -ne 0 ]];
  do
    NOW="$1"
    case "$NOW" in
      "-d") DEBUG=1;;
      "-l") VIEW=1;;
      "-q") INFO=0;;
      "-b") AUTO=1; INFO=0; DEBUG=0;;
      "-p") mesgPrefix="$2";;
    esac
    shift
  done
fi
BackupFile="$NOW"

_LOG DEBUG ファイルの存在チェック
if [[ ! -f "${BackupFile}" ]];then
  _LOG ERROR 対象バックアップファイルが存在しません。
fi
_LOG DEBUG バックアップファイルか
if [[ "${BackupFile}" =~ .*\.bk ]];then
  _LOG ERROR バックアップファイルはバックアップできません。
fi



if [[ $VIEW -eq 1 ]];then
  HEADER="バックアップファイル,バージョン,SHA1SUMハッシュ値,メッセージ"
  cat <(echo "$HEADER") <(grep "^${BackupFile}," $INFOFILE | tail -n $(( $(tput lines) -5 )) ) | column -ts,
  exit 0
fi



_LOG DEBUG 最終バージョンファイルのチェック
LatestVersionFile="$( /bin/ls -1  | grep "^${BackupFile}" | grep -P '\.[0-9][0-9][0-9][0-9][0|1][0-9][1|2|3][0-9][a-z]*?\.bk$' | _sort | tail -1)"
_LOG DEBUG "最終バージョンファイル: $LatestVersionFile"

_LOG DEBUG 最終バージョンのチェック
LatestVersion="$(echo "$LatestVersionFile" | grep -oP '[0-9][0-9][0-9][0-9][0|1][0-9][1|2|3][0-9][a-z]*?\.bk$' |sed 's;\.bk;;g')"
_LOG DEBUG "最終バージョン: $LatestVersion"

_LOG DEBUG 最終バージョン日時のチェック
LatestVersionDate="$(echo "$LatestVersion" | cut -c 1-8 )"
_LOG DEBUG "最終バージョン日時: $LatestVersionDate"

_LOG DEBUG 最終バージョン文字のチェック
LatestVersionChar="$(echo "$LatestVersion" | cut -c 9-  )"
_LOG DEBUG "最終バージョン文字: $LatestVersionChar"

_LOG DEBUG 最終バージョン文字の文字数のチェック
LatestVersionCharNum="$(echo -n "$LatestVersionChar" | wc -c )"
_LOG DEBUG "最終バージョン文字の文字数: $LatestVersionCharNum"

_LOG DEBUG 本日日時のチェック
Today=$(date +'%Y%m%d')
_LOG DEBUG "日付: $Today"

if [[ "$LatestVersionFile" == "" ]];then
  _LOG INFO バックアップファイルが存在しませんでした。初期バックアップを実施します。
  bk="${BackupFile}.${Today}a.bk"
  _backup "${BackupFile}" "$bk" "${Today}a" "$(sha1sum "$BackupFile" | awk '{print $1}' )"
  exit 0
fi


_LOG INFO "ハッシュ値照合中... [$LatestVersionFile]"
bkHash="$(sha1sum "$LatestVersionFile" | awk '{print $1}')"
nowHash="$(sha1sum "$BackupFile" | awk '{print $1}' )"
_LOG DEBUG "最終バックアップファイルのハッシュ値: $bkHash"
_LOG DEBUG "対象バックアップファイルのハッシュ値: $nowHash"

_LOG DEBUG ハッシュ比較
if [[ "$bkHash" == "$nowHash" ]];then
  _LOG INFO バックアップファイルと同じハッシュのファイルなため、バックアップを実施しません。
  exit 1
fi

_LOG DEBUG バックアップ日時を比較
if [[ "$Today" != "$LatestVersionDate" ]];then
  _LOG INFO 本日最初のバックアップを実施します。
  bk="${BackupFile}.${Today}a.bk"
  _backup "${BackupFile}" "$bk" "${Today}a" "$nowHash"
  exit 0
fi

_LOG DEBUG 最終バージョン文字の数よりバックアップ文字列を生成します。
CharVers=()
case ${LatestVersionCharNum} in
  "1") CharVers=( $(echo {a..z}) );;
  "2") CharVers=( $(echo {a..z}{a..z}) );;
  "3") CharVers=( $(echo {a..z}{a..z}{a..z} ) );;
  "4") CharVers=( $(echo {a..z}{a..z}{a..z}{a..z}) );;
  *) _LOG ERROR "Error: NotSupportSubVersion: $LatestVersionChar"
esac

_LOG DEBUG 最終バージョン文字がその文字数における最後のバージョン文字かチェックします。
FirstFlag=0
case ${LatestVersionChar} in
  "z") CharVers=( $(echo {a..z}{a..z}) ); FirstFlag=1;;
  "zz") CharVers=( $(echo {a..z}{a..z}{a..z} ) ); FirstFlag=1;;
  "zzz") CharVers=( $(echo {a..z}{a..z}{a..z}{a..z}) ); FirstFlag=1;;
esac

nextflag=0
NewVersionChar=""


if [[ $FirstFlag -eq 1 ]];then
  _LOG DEBUG 最終バージョン文字がその文字数における最後のバージョン文字列だったため、文字数を拡張しました。
  NewVersionChar="${CharVers[0]}"
else
  _LOG DEBUG 最終バージョン文字がその文字数における最後のバージョン文字列ではないため、次の文字列を検索します。
  for s in "${CharVers[@]}"
  do
    if [[ ${nextflag} -eq 1 ]];then
      _LOG DEBUG "次のバージョン文字列を取得しました。[$s]"
      NewVersionChar="$s"
      break
    elif [[ "$s" == "$LatestVersionChar" ]];then
      _LOG DEBUG "現在のバージョン文字列と一致するものがありました。[$s]"
      nextflag=1
      continue
    fi
  done
fi

_LOG DEBUG 次のバージョン文字列が取得できたか
if [[ "$NewVersionChar" != "" ]];then
  _LOG INFO 継続バックアップを開始します。
  bk="${BackupFile}.${LatestVersionDate}${NewVersionChar}.bk"
  _backup "${BackupFile}" "$bk" "${LatestVersionDate}${NewVersionChar}" "$nowHash"
fi

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?