不管你负责的是商业环境的Linux系统还是家用环境的,丢失数据都是一场灾难。为了防止
这种倒霉事,最好是定时进行备份(或者是归档)。
但是好想法和实用性经常是两回事。制定一个存储重要文件的备份计划绝非易事。这时候
shell脚本通常能够助你一臂之力。
本节将会演示两种使用shell脚本备份Linux系统数据的方法。
归档数据文件
如果你正在用Linux系统作为一个重要项目的平台,可以创建一个shell脚本来自动获取特定
目录的快照。在配置文件中指定所涉及的目录,这样一来,在项目发生变化时,你就可以做出对
应的修改。这有助于避免把时间耗在恢复主归档文件上。
本节将会介绍如何创建自动化shell脚本来获取指定目录的快照并保留旧数据的归档。
需要的功能
Linux中归档数据的主要工具是 tar 命令(参见第4章)。 tar 命令可以将整个目录归档到单个
文件中。下面的例子是用 tar 命令来创建工作目录归档文件。
$ tar -cf archive.tar /home/Christine/Project/*.*
tar: Removing leading '/' from member names
$
$ ls -l archive.tar
-rw-rw-r--. 1 Christine Christine 51200 Aug 27 10:51 archive.tar
$
tar 命令会显示一条警告消息,表明它删除了路径名开头的斜线,将路径从绝对路径名变成
相对路径名(参见第3章)。这样就可以将 tar 归档文件解压到文件系统中的任何地方了。你很可
能不想在脚本中出现这条消息。这种情况可以通过将 STDERR 重定向到 /dev/null 文件(参见第
15章)实现。
$ tar -cf archive.tar /home/Christine/Project/*.* 2>/dev/null
$
$ ls -l archive.tar
-rw-rw-r--. 1 Christine Christine 51200 Aug 27 10:53 archive.tar
$
由于 tar 归档文件会消耗大量的磁盘空间,最好能够压缩一下该文件。这只需要加一个 -z 选
项就行了。它会将 tar 归档文件压缩成gzip格式的tar文件,这种文件也叫作tarball。别忘了使用恰
当的文件扩展名来表示这是个tarball,用.tar.gz或.tgz都行。下面的例子创建了项目目录的tarball。
$ tar -zcf archive.tar.gz /home/Christine/Project/*.* 2>/dev/null
$
$ ls -l archive.tar.gz
-rw-rw-r--. 1 Christine Christine 3331 Aug 27 10:53 archive.tar.gz
$
现在你已经完成了归档脚本的主要部分。
你不需要为待备份的新目录或文件修改或编写新的归档脚本,而是可以借助于配置文件。配
置文件应该包含你希望进行归档的每个目录或文件。
$ cat Files_To_Backup
/home/Christine/Project
/home/Christine/Downloads
/home/Does_not_exist
/home/Christine/Documents
$
说明 如果你使用的是带图形化桌面的Linux发行版,那么归档整个HOME目录时要注意。尽管 这个想法很有吸引力,但HOME目录含有很多跟图形化桌面有关的配置文件和临时文
件。它会生成一个比你想象中大很多的归档文件。选择一个用来存储工作文件的子目录,
然后在归档配置文件中加入那个子目录。
可以让脚本读取配置文件,然后将每个目录名加到归档列表中。要实现这一点,只需要使用
read 命令(参见第14章)来读取该文件中的每一条记录就行了。不过不用像之前那样(参见第
13章)通过管道将 cat 命令的输出传给 while 循环,在这个脚本中我们使用 exec 命令(参见第14
章)来重定向标准输入( STDIN ),用法如下。
exec < $CONFIG_FILE
read FILE_NAME
注意,我们为归档配置文件使用了一个变量, CONFIG_FILE 。配置文件中每一条记录都会被
读入。只要 read 命令在配置文件中发现还有记录可读,它就会在 ? 变量中(参见第11章)返回一
个表示成功的退出状态码 0 。可以将它作为 while 循环的测试条件来读取配置文件中的所有记录。
while [ $? -eq 0 ]
do
[...]
read FILE_NAME
done
一旦 read 命令到了配置文件的末尾,它就会返回一个非零状态码。这时脚本会退出 while
循环。
在 while 循环中,我们需要做两件事。首先,必须将目录名加到归档列表中。更重要的是要
检查那个目录是否存在!很可能你从文件系统中删除了一个目录却忘了更新归档配置文件。可以
用一个简单的 if 语句来检查目录存在与否(参见第12章)。如果目录存在,它会被加入要归档目
录列表 FILE_LIST 中,否则就显示一条警告消息。 if 语句如下。
if [ -f $FILE_NAME -o -d $FILE_NAME ]
then
# If file exists, add its name to the list.
FILE_LIST="$FILE_LIST $FILE_NAME"
else
# If file doesn't exist, issue warning
echo
echo "$FILE_NAME, does not exist."
echo "Obviously, I will not include it in this archive."
echo "It is listed on line $FILE_NO of the config file."
echo "Continuing to build archive list..."
echo
fi
#
FILE_NO=$[$FILE_NO + 1] # Increase Line/File number by one.
由于归档配置文件中的记录可以是文件名,也可以是目录名,所以 if 语句会用 -f 选项和 -d
选项测试两者是否存在。 or 选项 -o 考虑到了,在测试文件或目录的存在性时,只要其中一个测
试为真,那么整个 if 语句就成立。
为了在跟踪不存在的目录和文件上提供一点额外帮助,我们添加了变量 FILE_NO 。这样,这
个脚本就可以告诉你在归档配置文件中哪行中含有不正确或缺失的文件或目录。
创建逐日归档文件的存放位置
如果你只是备份少量文件,那么将这些归档文件放在你的个人目录中就行了。但如果要对多
个目录进行备份,最好还是创建一个集中归档仓库目录。
$ sudo mkdir /archive
[sudo] password for Christine:
$
$ ls -ld /archive
drwxr-xr-x. 2 root root 4096 Aug 27 14:10 /archive
$
创建好集中归档目录后,你需要授予某些用户访问权限。如果忘记了这一点,在该目录下创
建文件时就会出错。
$ mv Files_To_Backup /archive/
mv: cannot move 'Files_To_Backup' to
'/archive/Files_To_Backup': Permission denied
$
可以通过 sudo 命令或者创建一个用户组的方式,为需要在集中归档目录中创建文件的用户
授权。可以创建一个特殊的用户组Archivers。
$ sudo groupadd Archivers
$
$ sudo chgrp Archivers /archive
$
$ ls -ld /archive
drwxr-xr-x. 2 root Archivers 4096 Aug 27 14:10 /archive
$
$ sudo usermod -aG Archivers Christine
[sudo] password for Christine:
$
$ sudo chmod 775 /archive
$
$ ls -ld /archive
drwxrwxr-x. 2 root Archivers 4096 Aug 27 14:10 /archive
$
将用户添加到Archivers组后,用户必须先登出然后再登入,才能使组成员关系生效。现在只
要是该组的成员,无需超级用户权限就可以在目录中创建文件了。
$ mv Files_To_Backup /archive/
$
$ ls /archive
Files_To_Backup
$
记住,Archivers组的所有用户都可以在归档目录中添加和删除文件。为了避免组用户删除他
人的归档文件,最好还是把目录的粘滞位加上。
现在你已经有足够的信息来编写脚本了。下一节将讲解如何创建按日归档的脚本。
创建按日归档的脚本
Daily_Archive脚本会自动在指定位置创建一个归档,使用当前日期来唯一标识该文件。下面
是脚本中的对应部分的代码。
DATE=$(date +%y%m%d)
#
# Set Archive File Name
#
FILE=archive$DATE.tar.gz
#
# Set Configuration and Destination File
#
CONFIG_FILE=/archive/Files_To_Backup
DESTINATION=/archive/$FILE
#
DESTINATION 变量会将归档文件的全路径名加上去。 CONFIG_FILE 变量指向含有待归档目
录信息的归档配置文件。如果需要,二者都可以很方便地改成备用目录和文件。
说明 如果你刚开始编写脚本,那么在面对一个完整的脚本代码时(你马上就会看到),要养成
通读整个脚本的习惯。试着理解内在的逻辑和脚本的控制流程。对于不理解的脚本语法
或某些片段,就重新去阅读书中相关的章节。这种习惯能够帮助你非常快速地习得脚本
编写技巧。
将所有的内容结合在一起,Daily_Archive脚本内容如下。
#!/bin/bash
#
# Daily_Archive - Archive designated files & directories
########################################################
#
# Gather Current Date
#
DATE=$(date +%y%m%d)
#
# Set Archive File Name
#
FILE=archive$DATE.tar.gz
#
# Set Configuration and Destination File
#
CONFIG_FILE=/archive/Files_To_Backup
DESTINATION=/archive/$FILE
#
######### Main Script #########################
#
# Check Backup Config file exists
#
if [ -f $CONFIG_FILE ] # Make sure the config file still exists.
then # If it exists, do nothing but continue on.
echo
else # If it doesn't exist, issue error & exit script.
echo
echo "$CONFIG_FILE does not exist."
echo "Backup not completed due to missing Configuration File"
echo
exit
fi
#
# Build the names of all the files to backup
#
FILE_NO=1 # Start on Line 1 of Config File.
exec < $CONFIG_FILE # Redirect Std Input to name of Config File
#
read FILE_NAME # Read 1st record
#
while [ $? -eq 0 ] # Create list of files to backup.
do
# Make sure the file or directory exists.
if [ -f $FILE_NAME -o -d $FILE_NAME ]
then
# If file exists, add its name to the list.
FILE_LIST="$FILE_LIST $FILE_NAME"
else
# If file doesn't exist, issue warning
echo
echo "$FILE_NAME, does not exist."
echo "Obviously, I will not include it in this archive."
echo "It is listed on line $FILE_NO of the config file."
echo "Continuing to build archive list..."
echo
fi
#
FILE_NO=$[$FILE_NO + 1] # Increase Line/File number by one.
read FILE_NAME # Read next record.
done
#
#######################################
#
# Backup the files and Compress Archive
#
echo "Starting archive..."
echo
#
tar -czf $DESTINATION $FILE_LIST 2> /dev/null
#
echo "Archive completed"
echo "Resulting archive file is: $DESTINATION"
echo
#
exit
运行按日归档的脚本
在测试脚本之前,别忘了修改脚本文件的权限(参见第11章)。必须赋予文件属主可执行权
限( x )才能够运行脚本。
$ ls -l Daily_Archive.sh
-rw-rw-r--. 1 Christine Christine 1994 Aug 28 15:58 Daily_Archive.sh
$
$ chmod u+x Daily_Archive.sh
$
$ ls -l Daily_Archive.sh
-rwxrw-r--. 1 Christine Christine 1994 Aug 28 15:58 Daily_Archive.sh
$
测试Daily_Archive脚本非常简单。
$ ./Daily_Archive.sh
/home/Does_not_exist, does not exist.
Obviously, I will not include it in this archive.
It is listed on line 3 of the config file.
Continuing to build archive list...
Starting archive...
Archive completed
Resulting archive file is: /archive/archive140828.tar.gz
$ ls /archive
archive140828.tar.gz Files_To_Backup
$
你会看到这个脚本发现了一个不存在的目录:/home/Does_not_exist。脚本能够告诉你这个错
误的行在配置文件中的行号,然后继续创建列表和归档数据。现在数据已经稳妥地归档到了tarball
文件中。
创建按小时归档的脚本
如果你是在文件更改很频繁的高容量生产环境中,那么按日归档可能不够用。如果要将归档
频率提高到每小时一次,你还要考虑另一个因素。
在按小时备份文件时,如果依然使用 date 命令为每个tarball文件加入时间戳,事情很快就会
变得丑陋不堪。筛选一个含有如下文件名的目录会很乏味:
archive010211110233.tar.gz
不必将所有的归档文件都放到同一目录中,你可以为归档文件创建一个目录层级。
image.png
这个归档目录包含了与一年中的各个月份对应的目录,将月的序号作为目录名。而每月的目
录中又包含与当月各天对应的目录(用天的序号作为目录名)。这样你只用给每个归档文件加上
时间戳,然后将它们放到与月日对应的目录中就行了。
首先,必须创建新目录/archive/hourly,并设置适当的权限。之前我们说过,Archivers组有权
在目录中创建归档文件。因此,这个新创建的目录也得修改它的属组以及组权限。
$ sudo mkdir /archive/hourly
[sudo] password for Christine:
$
$ sudo chgrp Archivers /archive/hourly
$
$ ls -ld /archive/hourly/
drwxr-xr-x. 2 root Archivers 4096 Sep 2 09:24 /archive/hourly/
$
$ sudo chmod 775 /archive/hourly
$
$ ls -ld /archive/hourly
drwxrwxr-x. 2 root Archivers 4096 Sep 2 09:24 /archive/hourly
$
新目录设置好之后,将按小时归档的配置文件File_To_Backup移动到该目录中。
$ cat Files_To_Backup
/usr/local/Production/Machine_Errors
/home/Development/Simulation_Logs
$
$ mv Files_To_Backup /archive/hourly/
$
现在,还有个新问题要解决。这个脚本必须自动创建对应每月和每天的目录,如果这些目录
已经存在的话,脚本就会报错。这可不是我们想要的结果!
如果仔细查看 mkdir 命令的命令行选项的话(参见第3章),会发现有一个 -p 命令行选项。这
个选项允许在单个命令中创建目录和子目录。另外,额外的福利是:就算目录已经存在,它也不
会产生错误消息。这正是我们的脚本中所需要的!
现在可以创建Hourly_Archive.sh脚本了。以下是前脚本的前半部分。
#!/bin/bash
#
# Hourly_Archive - Every hour create an archive
#########################################################
#
# Set Configuration File
#
CONFIG_FILE=/archive/hourly/Files_To_Backup
#
# Set Base Archive Destination Location
#
BASEDEST=/archive/hourly
#
# Gather Current Day, Month & Time
#
DAY=$(date +%d)
MONTH=$(date +%m)
TIME=$(date +%k%M)
#
# Create Archive Destination Directory
#
mkdir -p $BASEDEST/$MONTH/$DAY
#
# Build Archive Destination File Name
#
DESTINATION=$BASEDEST/$MONTH/$DAY/archive$TIME.tar.gz
#
########## Main Script ####################
[...]
一旦脚本Hourly_Archive.sh到了Main Script部分,就和Daily_Archive.sh脚本完全一样了。大
部分工作都已经完成。
Hourly_Archive.sh会从 date 命令提取天和月,以及用来唯一标识归档文件的时间戳。然后它
用这个信息创建与当天对应的目录(如果已经存在的话,就安静地退出)。最后,这个脚本用 tar
命令创建归档文件并将它压缩成一个tarball。
运行按小时归档的脚本
跟Daily_Archive.sh脚本一样,在将Hourly_Archive.sh脚本放到cron表中之前最好先测试一下。
脚本运行之前必须修改好权限。另外,通过 date 命令检查小时和分钟。知道了当前的时和分才
能够验证最终归档文件名的正确性。
$ chmod u+x Hourly_Archive.sh
$
$ date +%k%M
1011
$
$ ./Hourly_Archive.sh
Starting archive...
Archive completed
Resulting archive file is: /archive/hourly/09/02/archive1011.tar.gz
$
$ ls /archive/hourly/09/02/
archive1011.tar.gz
$
这个脚本第一次运行很正常,创建了相应的月和天的目录,随后生成的归档文件名也没问题。
注意,归档文件名archive1011.tar.gz中包含了对应的小时(10)和分钟(11)。
说明 如果你当天运行 Hourly_Archive.sh 脚本,那么当小时数是单个数字时,归档文件名中只会
出现3个数字。例如运行脚本的时间是1:15am,那么归档文件名就是 archive115.tar.gz 。如
果你希望文件名中总是保留4位数字,可以将脚本行 TIME=(date +%k%M) 修改成 TIME=(date +%k0%M) 。在 %k 后加入数字0后,所有的单数字小时数都会被加入一个前
导数字0,填充成两位数字。因此, archive115.tar.gz 就变成了 archive0115.tar.gz 。
为了进行充分的测试,我们再次运行脚本,看看当目录/archive/hourly/09/02/已存在的时候会
不会出现问题。
$ date +%k%M
1017
$
$ ./Hourly_Archive.sh
Starting archive...
Archive completed
Resulting archive file is: /archive/hourly/09/02/archive1017.tar.gz
$ ls /archive/hourly/09/02/
archive1011.tar.gz archive1017.tar.gz
$
没有问题!这个脚本仍正常运行,并创建了第二个归档文件。现在可以把它放到cron表中了