linuxshell程式設計實戰-編寫簡單的指令碼實用工具

weixin_34249678發表於2018-07-21

不管你負責的是商業環境的Linux系統還是家用環境的,丟失資料都是一場災難。為了防止
這種倒黴事,最好是定時進行備份(或者是歸檔)。
但是好想法和實用性經常是兩回事。制定一個儲存重要檔案的備份計劃絕非易事。這時候
shell指令碼通常能夠助你一臂之力。
本節將會演示兩種使用shell指令碼備份Linux系統資料的方法。
歸檔資料檔案
如果你正在用Linux系統作為一個重要專案的平臺,可以建立一個shell指令碼來自動獲取特定
目錄的快照。在配置檔案中指定所涉及的目錄,這樣一來,在專案發生變化時,你就可以做出對
應的修改。這有助於避免把時間耗在恢復主歸檔檔案上。
本節將會介紹如何建立自動化shell指令碼來獲取指定目錄的快照並保留舊資料的歸檔。

  1. 需要的功能
    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 。這樣,這
個指令碼就可以告訴你在歸檔配置檔案中哪行中含有不正確或缺失的檔案或目錄。

  1. 建立逐日歸檔檔案的存放位置
    如果你只是備份少量檔案,那麼將這些歸檔檔案放在你的個人目錄中就行了。但如果要對多
    個目錄進行備份,最好還是建立一個集中歸檔倉庫目錄。
$ 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組的所有使用者都可以在歸檔目錄中新增和刪除檔案。為了避免組使用者刪除他
人的歸檔檔案,最好還是把目錄的粘滯位加上。
現在你已經有足夠的資訊來編寫指令碼了。下一節將講解如何建立按日歸檔的指令碼。

  1. 建立按日歸檔的指令碼
    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
  1. 執行按日歸檔的指令碼
    在測試指令碼之前,別忘了修改指令碼檔案的許可權(參見第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
檔案中。

  1. 建立按小時歸檔的指令碼
    如果你是在檔案更改很頻繁的高容量生產環境中,那麼按日歸檔可能不夠用。如果要將歸檔
    頻率提高到每小時一次,你還要考慮另一個因素。
    在按小時備份檔案時,如果依然使用 date 命令為每個tarball檔案加入時間戳,事情很快就會
    變得醜陋不堪。篩選一個含有如下檔名的目錄會很乏味:
archive010211110233.tar.gz

不必將所有的歸檔檔案都放到同一目錄中,你可以為歸檔檔案建立一個目錄層級。


468490-3fc95560cb5d95d2.png
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。

  1. 執行按小時歸檔的指令碼
    跟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表中了

相關文章