转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4577034
之前写过 linux下简单的备份的脚本 , 最开始一直用着, 后来觉得有必要改进下它了, 不管是从操作方式上还是工作方式上。有这样的想法是因为一次备份引起的。 我经历过磁盘损坏的痛苦, 花了1500元才勉强将数据拯救回来, 于是导致我对备份要求更加高了, 我期望尽量每周备份, 期望备份的目的地是当前系统的完整镜像,也就是说我能够从备份盘启动,且启动后
系统的操作方法以及文件和当前的一模一样,每周我只需要增量备份当前的修改的备份盘,这样就安全了很多。于是就有了下面的备份脚本(我不喜欢造*,但是在linux下没有现成的适合我的)
NOTE: 当前还没有加入自动镜像系统,所以如果想镜像系统,那么可以手动这样操作, 准备一块大于当前系统盘的移动盘,
分区,注意分区的结构尽量和系统的一模一样, 然后格式化,文件系统也尽量相同, 然后就可以备份了 备份的时候主要有些
目录需要跳过,比如sys dev proc等等,需要跳过的目录可以在backup程序了面设置!
这是脚本程序:
点击(此处)折叠或打开
- #!/bin/bash
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License as
- # published by the Free Software Foundation; either version 2 of
- # the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- # Author: rongp
- # email: rongpmcu#gmail.com
- # Date: 2014/10/26
- # 备份程序
- # 特点:
- # 主要支持unix类系统下, 支持符号链接
- # 支持增量备份
- # 支持网络备份(由于基于rsync, 很容易加入该功能,但暂时没加入)
- # 文件名支持空格 但是不能出现含有@#@的文件名
- # 支持备份每次的更新,方便用于人工操作失误后的修复
- # 支持添加规则用于剔除某些文件 格式参考rsync的PATTERN部分
- SHELL=/bin/bash
- backup_cfg_path=/etc
- backup_cfg=$backup_cfg_path/backup.cfg
- db_path=
- db_pathname=
- inc_path=
- XECHO=1
- _help()
- {
- echo -e "$0 [option]\n"\
- "\tOption:\n"\
- "\t-h show this help.\n"\
- "\t-i perform the installation, and you should use this option\n"\
- "\t before using the backup to do something else.\n"\
- "\t-u perform the un-installation.\n"
- }
- help()
- {
- echo -e "Command action\n"\
- "\th show this help.\n"\
- "\ta add any file that you want to backup to the database.\n"\
- "\td delete any file that you no longer want from the database.\n"\
- "\tb start backup.\n"\
- "\tbf assume \"yes\" as answer to all prompts and run non-interactively.\n"\
- "\tn perform a trial backup with no changes made.\n"\
- "\tp print the file record.\n"\
- "\tc do some configurations, such as, modifying the path to the\n"\
- "\t database or to the incremental backup directory.\n"\
- "\ts show the current configuration.\n"\
- "\ti perform the installation, and you should use this option\n"\
- "\t before using the backup to do something else.\n"\
- "\tu perform the un-installation.\n"\
- "\tq quit"
- }
- color_echo()
- {
- case "$1" in
- g)
- shift
- echo -e "\033[32m"$@"\033[0m"
- ;;
- gn)
- shift
- echo -e -n "\033[32m"$@"\033[0m"
- ;;
- r)
- shift
- echo -e "\033[31m"$@"\033[0m"
- ;;
- y)
- shift
- echo -e "\033[33m"$@"\033[0m"
- ;;
- yn)
- shift
- echo -e -n "\033[33m"$@"\033[0m"
- ;;
- *)
- shift
- echo $@
- ;;
- esac
- }
- XECHO()
- {
- if [ "$XECHO" = 1 ]; then
- echo $@
- fi
- }
- check_src_dst()
- {
- if ! test -e "$1" || ! test -e "$2"; then
- color_echo r "$1" or "$2" does not ignore
- return 2
- fi
- local src_part1=`df "$1" | cut -d ' ' -f 1`
- local src_part2=`df "$2" | cut -d ' ' -f 1`
- local nsrc_inode=`ls -lid "$1" | cut -d ' ' -f 1`
- local ndst_inode=`ls -lid "$2" | cut -d ' ' -f 1`
- XECHO nsrc_inode:$nsrc_inode ndst_inode:$ndst_inode
- if [ "$src_part1" != "$src_part2" ]; then
- return 1
- fi
- if [ "$nsrc_inode" = "$ndst_inode" ]; then
- color_echo r "$src is equivalent to $dst. ignore it!"
- return 2
- fi
- if [ ! -e $db_pathname ]; then
- return 1
- fi
- while read -r tsrc tdst tex_src;
- do
- tsrc="${tsrc//@#@/ }"
- tdst="${tdst//@#@/ }"
- tex_src="${tex_src//@#@/ }"
- XECHO tsrc:"$tsrc" tdst:"$tdst"
- osrc_inode=`ls -lid "$tsrc" | cut -d ' ' -f 1`
- odst_inode=`ls -lid "$tdst" | cut -d ' ' -f 1`
- XECHO osrc_inode:$osrc_inode odst_inode:$odst_inode
- if [ "$nsrc_inode" = "$osrc_inode" -a "$ndst_inode" = "$odst_inode" ]; then
- if [ ${1:((${#1}-1))} = '/' -a ${tsrc:((${#tsrc}-1))} != '/' ] \
- || [ ${1:((${#1}-1))} != '/' -a ${tsrc:((${#tsrc}-1))} = '/' ]; then #/home and /home/ is very
- echo -n "";
- else
- return 0
- fi
- fi
- done < $db_pathname
- return 1
- }
- extract_src_dst()
- {
- XECHO "extract src dst from $1"
- src="${1%#*}"
- dst="${1#$src}"
- dst="${dst#\#}"
- XECHO "src: $src"
- XECHO "dst: $dst"
- if [ "$src" = "" -o "$dst" = "" ]; then
- return 1
- else
- return 0
- fi
- }
- fix_path()
- {
- local srcpath="$1"
- if [ "${srcpath:0:1}" = '/' ]; then
- echo $srcpath
- elif [ "${srcpath:0:2}" = './' ]; then
- echo `pwd`/${srcpath:2}
- else
- echo `pwd`/$srcpath
- fi
- }
- insert_new_item()
- {
- XECHO add item src:"$1" dst:"$2" exclude:"$3"
- tmp1="${1// /@#@}"
- tmp2="${2// /@#@}"
- tmp3="${3// /@#@}"
- echo "$tmp1" "$tmp2" "$tmp3" >> $db_pathname
- return $?
- }
- parse_item()
- {
- if ! extract_src_dst "$1"; then
- color_echo r "src:$src or dst:$dst is illegal!"
- return 1
- fi
- src=`fix_path "$src"`
- dst=`fix_path "$dst"`
- XECHO after fixed, src:"$src"
- XECHO after fixed, src:"$dst"
- return 0
- }
- do_add()
- {
- local item
- color_echo g "Enter the mode of adding files! Some patterns are available, as follows:"
- color_echo g "eg: /home/#/tmp/ means we want to backup the whole things which "
- color_echo g "are under home directory to /tmp directory."
- color_echo g "eg: /home/#/tmp/:/etc/#/tmp/ means we want to backup the whole "
- color_echo g "things which are under the home directory and the /etc/ directory "
- color_echo g "to the /tmp directory, you can append any item with ':'."
- color_echo r "Note: /home and /home/ are quite different, because /home just means "
- color_echo r "/home itself while /home/ means the whole contents of /home."
- read -p "Please type in file items: " items
- items="`echo "$items" | sed "s/'//g"`"
- flag=0
- while [ $flag = 0 ];
- do
- item=${items%%:*}
- items=${items#$item:}
- ex_src=""
- if [ "$items" = "$item" ]; then
- flag=1
- fi
- if parse_item "$item"; then
- check_src_dst "$src" "$dst"
- ret=$?
- if [ "$ret" = 0 ]; then
- color_echo y "Warning! ""$src#$dst"" is already existed! do not re-submit!"
- continue
- elif [ "$ret" = 2 ]; then
- continue
- fi
- read -p "Would you like to add some excluding conditions to $src: (y/n)[n] " yn
- if [ "$yn" = y ]; then
- color_echo r "Note: this is an expert mode, and we don't check your pattern"
- color_echo r "is valid or not. Some patterns are available, as follows:"
- color_echo r "eg: if your src directory is /home, and your want to exclude"
- color_echo r "the directory /home/rongp, then you should type in \"rongp\"."
- color_echo r "eg: if your src directory is /home, and your want to exclude"
- color_echo r "the directory /home/rongp and /home/test, then you should"
- color_echo r "type in \"rongp:test\", and you can append any item with ':' ."
- read -p "Please type in paths to the excluding files: " exitem
- ex_src="$exitem"
- fi
- if insert_new_item "$src" "$dst" "$ex_src"; then
- echo ""$src"#"$dst" add successed!"
- else
- echo ""$src"#"$dst" add failed!"
- fi
- else
- read -p "skip it? Yy/Nn:[n] " yn
- if [ "$yn" = "y" -o "$yn" = "Y" ]; then
- continue
- fi
- return 1
- fi
- done
- return 0
- }
- get_choices()
- {
- local total_line=`wc -l $db_pathname | cut -d ' ' -f 1`
- select_tab=
- color_echo g "Enter the mode of "$1"! some patterns are available, as follows:"
- color_echo g "eg: 1-3 means select no 1 2 3 item"
- color_echo g "eg: 1:3:5 means select no 1 3 5 item"
- color_echo g "you can append any no with ':' or '-', but don't mix use it."
- color_echo g "no 0 means select all."
- do_print
- read -p "Please type in the number: " NO
- if [ "${NO/-/ }" != "$NO" ]; then
- num_tab=(${NO//-/ })
- [ ${#num_tab[@]} -gt 2 ] && \
- echo "Select failed, argument $NO is illegal!" && return 1
- num0=${num_tab[0]}
- num1=${num_tab[1]}
- XECHO num0:$num0 num1:$num1
- if [ -z "${num0//[0-9]/}" -a "$num0" -le "$total_line" -a "$num0" -gt "0" ]\
- && [ -z "${num1//[0-9]/}" -a "$num1" -le "$total_line" -a "$num1" -gt "0" ]\
- && [ "$num0" -lt "$num1" ];
- then
- select_tab=(`seq $num0 $num1`)
- else
- echo "Select failed, argument $NO is illegal!" && return 1
- fi
- elif [ "${NO/:/ }" != "$NO" ]; then
- for num in ${NO//:/ }
- do
- if [ -z "${num//[0-9]/}" ]&&[ "$num" -le "$total_line" ]\
- &&[ "$num" -gt "0" ]; then
- continue
- else
- echo "Select failed, argument $num is illegal!" && return 1
- fi
- done
- j=0
- for i in ${NO//:/ }
- do
- select_tab[j]=$i
- ((j++))
- done
- else
- if [ "$NO" = 0 ]; then
- select_tab=(`seq 1 $total_line`)
- elif [ -z "${NO//[0-9]/}" ]&&[ "$NO" -le "$total_line" ]\
- &&[ "$NO" -gt "0" ]; then
- select_tab[0]=${NO}
- else
- echo "Select failed, argument $NO is illegal!" && return 1
- fi
- fi
- return 0
- }
- do_del()
- {
- if ! get_choices "deleting files"; then
- return 1
- fi
- local total_num=${#select_tab[@]}
- if [ "$total_num" = 1 ]; then
- nums=${select_tab[0]}d
- elif [ "$total_num" = 2 ]; then
- nums=${select_tab[0]},${select_tab[1]}d
- else
- for ((i=0; i<$total_num; ++i))
- do
- nums+="${select_tab[i]}d;"
- done
- fi
- sed -i "$nums" $db_pathname >/dev/null 2>&1
- [ "$?" = 0 ] && echo "$NO delete successed!" || echo "$NO delete failed, delete failed!"
- }
- do_print()
- {
- [ ! -s $db_pathname ] && color_echo y "Warning, no record found!" && return 1
- echo " no source destination action"
- cat -n $db_pathname | sed 's/@#@/ /g'
- }
- check_in_select_tab()
- {
- local i
- for ((i=0; i<${#select_tab[@]}; ++i))
- do
- XECHO $1:select_tab[$i]:${select_tab[i]}
- if [ "${select_tab[i]}" = "$1" ]; then
- return 0
- fi
- done
- return 1
- }
- do_backup()
- {
- local ex_file=`mktemp`
- local fake="${1/fake/-n}"
- local yes="${1/yes/y}"
- [ ! -f "$db_pathname" ] && color_echo r "$db_pathname does not exist!" && return 1
- if ! get_choices "backup"; then
- return 1
- fi
- local i=0
- local k=0
- while read -r src dst ex_src;
- do
- if check_in_select_tab $((i+1)); then
- XECHO "$i in select table"
- src="${src//@#@/ }"
- dst="${dst//@#@/ }"
- XECHO src:$src dst:$dst ex_src:$ex_src ex_file:$ex_file
- src_tab[k]="$src"
- dst_tab[k]="$dst"
- ex_src_tab[k]="$ex_src"
- ((k++))
- fi
- ((i++))
- done < $db_pathname
- for ((j=0; j<$k; ++j))
- do
- echo src:${src_tab[j]} dst:${dst_tab[j]} ex_src:${ex_src_tab[j]}
- src="${src_tab[j]}"
- dst="${dst_tab[j]}"
- ex_src="${ex_src_tab[j]}"
- echo "$ex_src" | awk -F ':' '{for (i=1;i<=NF;++i)print $i}' | sed 's/@#@/ /g' > $ex_file
- if [ "$src" = "/" ]; then
- tmpsrc=$(blkid `mount | grep "/ " | cut -d ' ' -f 1` | awk -F "\"" '{print $2}')
- else
- tmpsrc="$src"
- fi
- if [ "$dst" = "/" ]; then
- tmpdst=$(blkid `mount | grep "/ " | cut -d ' ' -f 1` | awk -F "\"" '{print $2}')
- else
- tmpdst="$dst"
- fi
- color_echo g "We will start backup from "
- color_echo r "$src"
- color_echo g to
- color_echo r "$dst"
- color_echo g "with excluding file or directory"
- color_echo r "${ex_src//@#@/ }"
- color_echo gn "continue or not? y/n: "
- if [ "$yes" = y ]; then
- yn=y
- else
- read yn
- fi
- if [ "$yn" = y ]; then
- echo Start backup "$src" to "$dst" with excluding file or directory "$ex_src"
- (
- flock -x 200
- rsync -avzrtopg $fake --progress --delete --exclude-from=$ex_file \
- "$src" "$dst" --backup --backup-dir=$inc_path/$(date +%Y-%m-%d_%H:%M:%S_$(basename ${tmpsrc})_$(basename $tmpdst))
- ) 200>/var/lock/abc
- echo Backup $src to $dst with excluding file or directory $ex_src
- else
- echo Skip backup $src to $dst with excluding file or directory $
- fi
- done
- }
- get_answer()
- {
- local ret
- if [ "$4" != "" ]; then
- tmpbackup="$4"
- else
- tmpbackup=backup.cfg.bak
- fi
- while :
- do
- read -p "Type in $1 path of the backup(default is $2, q for exit): " ans_tmp
- if [ "$ans_tmp" = q ]; then
- ret=1
- break
- elif [ "$ans_tmp" != "" ]; then
- if [ ! -d "$ans_tmp" ]; then
- echo "$1: $ans_tmp is invalid!"
- read -p "Would you like to create it now? y/n [y]: " yn
- if [ "$yn" = y ]; then
- mkdir -p $ans_tmp
- else
- continue
- fi
- fi
- sed -i "s,$3.*,$3$ans_tmp,g" $tmpbackup
- ret=$?
- break
- else
- ans_tmp="$2"
- ret=0
- break
- fi
- done
- return $ret
- }
- already_install()
- {
- if load_cfg $backup_cfg s; then
- XECHO "already install"
- return 0 #has install
- fi
- return 1
- }
- do_install()
- {
- color_echo g start install
- if already_install; then
- color_echo y "We check that you have already installed, you should"
- color_echo yn "uninstall first, would you want to uninstall it first?y/n[n] "
- read yn
- if [ "$yn" != y ]; then
- color_echo g install
- color_echo r install
- return 1
- else
- do_uninstall
- fi
- fi
- cp -f backup.cfg backup.cfg.bak
- load_cfg backup.cfg.bak s
- if [ "$?" = 1 ]; then
- exit
- fi
- if ! get_answer "executable file backup" "$bin_path" "INSTALL_PATH=";then
- color_echo g install
- color_echo r install
- return 1
- fi
- install_path=$ans_tmp
- color_echo g install path is $install_path
- if ! get_answer "database" "$db_path" "DB_PATH=";then
- color_echo g install
- color_echo r install
- return 1
- fi
- db_path=$ans_tmp
- color_echo g database path is $db_path
- if ! get_answer "incremental backup" "$inc_path" "INCREMENTAL_BACKUP_PATH=";then
- color_echo g install
- color_echo r install
- return 1
- fi
- inc_path=$ans_tmp
- color_echo g incremental backup path is $inc_path
- echo
- who=`whoami`
- cp backup $install_path
- color_echo g install backup to $install_path
- ret=$?
- mv backup.cfg.bak $backup_cfg
- color_echo g install $backup_cfg
- ret=$((ret+$?))
- mkdir -p $db_path
- color_echo g install $db_path
- ret=$((ret+$?))
- mkdir -p $inc_path
- color_echo g install $inc_path
- ret=$((ret+$?))
- ln -s $db_path $inc_path/db
- color_echo g install $inc_path/db
- ret=$((ret+$?))
- color_echo g install
- if [ $ret -gt 0 ]; then
- color_echo r install
- [ -e $bin_path/backup ] && rm_print $bin_path/backup
- [ -e $backup_cfg ] && rm_print $backup_cfg
- [ -e $inc_path/db ] && rm_print $inc_path/db && rm_print -rf $inc_path
- [ -e $db_pathname ] && rm_print $db_pathname
- rm_print -d $db_path
- return 1
- fi
- echo
- echo
- color_echo y "The installation work is done, and you can remove this package now!"
- color_echo y "Note: you should put the executable file \"backup\""
- color_echo y "into \$PATH and you need to get \"root\" privileges to execute it."
- color_echo y "for example, you can execute it like this in ubuntu: sudo backup"
- return 0
- }
- rm_print()
- {
- color_echo g remove $@
- eval rm $@
- }
- do_uninstall()
- {
- XECHO "Perform the un-installation."
- color_echo g perform the un-installation...
- if ! load_cfg $backup_cfg; then
- color_echo g uninstall
- fi
- [ -e $bin_path/backup ] && rm_print $bin_path/backup
- [ -e $backup_cfg ] && rm_print $backup_cfg
- [ -e $inc_path/db ] && rm_print $inc_path/db && rm_print -rf $inc_path
- [ -e $db_pathname ] && rm_print $db_pathname
- rm_print -d $db_path
- color_echo g uninstall
- color_echo g uninstall
- }
- load_cfg()
- {
- if [ ! -e "$1" ]; then
- [ "$2" != "s" ] && color_echo r "Error, we can't find the configure file $1, exit now!"
- return 1
- fi
- bin_path=`sed -n 's/INSTALL_PATH=\(.*\)/\1/p' $1`
- db_path=`sed -n 's/DB_PATH=\(.*\)/\1/p' $1`
- db_pathname=$db_path/backup.db
- inc_path=`sed -n 's/INCREMENTAL_BACKUP_PATH=\(.*\)/\1/p' $1`
- if [ ! -d "$inc_path" ]; then
- [ "$2" != "s" ] && color_echo r Load configuration file your should
- [ "$2" != "s" ] && color_echo r check the directory $db_pathname is valid or
- return 2
- fi
- XECHO database path is $db_path
- XECHO database file path is $db_pathname
- XECHO incremental backup path is $inc_path
- return 0
- }
- show_configure()
- {
- color_echo g executable backup is in $bin_path
- color_echo g database directory is in $db_path
- color_echo g incremental backup directory is in $inc_path
- }
- do_modify_inc_backup_path()
- {
- if ! get_answer "incremental backup" "$inc_path" \
- "INCREMENTAL_BACKUP_PATH=" $backup_cfg;then
- return 1
- fi
- inc_path=$ans_tmp
- XECHO incremental backup is $inc_path
- return 0
- }
- do_configure()
- {
- color_echo g [1] modify incremental backup path
- color_echo g [2] ...
- read -p "Please type in the no which you are expecting to: " no
- if [ "$no" = 1 ]; then
- do_modify_inc_backup_path
- else
- color_echo r Unsupported
- fi
- }
- backup_start()
- {
- if ! load_cfg $backup_cfg; then
- exit
- fi
- while :
- do
- read -p "Command (h for help): " cmd
- case "$cmd" in
- a)
- do_add
- ;;
- d)
- do_del
- ;;
- p)
- do_print
- ;;
- c)
- do_configure
- ;;
- b)
- do_backup
- ;;
- bf)
- do_backup yes
- ;;
- n)
- do_backup fake
- ;;
- s)
- show_configure
- ;;
- i)
- do_install
- ;;
- u)
- do_uninstall
- exit
- ;;
- q)
- break
- ;;
- h | *)
- help
- ;;
- esac
- done
- }
- username=`echo $USER`
- if [ "$username" != root ]; then
- color_echo r "Error, you need to have \"root\" privileges to execute this program."
- exit
- fi
- if [ "$1" = "-i" ]; then
- if ! do_install; then
- color_echo y "Sorry, We can't continue any more. Exit now!"
- fi
- exit
- elif [ "$1" = "-u" ]; then
- do_uninstall
- exit
- elif [ "$1" = "-h" ]; then
- _help
- exit
- fi
- if [ ! -e $backup_cfg ]; then
- color_echo r "$backup_cfg does not exist! "
- read -p "You need to install the backup first. perform the installation? y/n?[y]: " yn
- if [ "$yn" != n ]; then
- do_install
- else
- echo Sorry, we can\'t continue any more. Exit
- fi
- exit
- fi
- backup_start
这是配置文件
点击(此处)折叠或打开
- #############################
- ######
- #############################
- AUTHOR=rongp
- EMAIL=rongpmcu@gmail.com
- VERSION=1.0
- INSTALL_PATH=/usr/bin/
- DB_PATH=/var/lib/backup/
- INCREMENTAL_BACKUP_PATH=/var/lib/backup/incr_backup
git路径:git@bitbucket.org:rongpmcu/backup-script-shell.git
【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.