《Linux命令行与shell脚本编程大全》第二十四章:编写简单的脚本实用工具
原创- 2023-12-21 15:19:23
- 991
本篇目录
一、归档
归档数据文件
如果你正在用Linux系统作为一个重要项目的平台,可以创建一个shell脚本来自动获取特定目录的快照。在配置文件中指定所涉及的目录,这样一来,在项目发生变化时,你就可以做出对应的修改。1.需要的功能
tar命令可以将整个目录归档到单个文件中。
tar命令会显示一条警告消息,表明它删除了路径名开头的斜线,将路径从绝对路径名变成相对路径名。
你很可能不想在脚本中出现这条消息。这种情况可以通过将STDERR重定向到/dev/null文件
由于tar归档文件会消耗大量的磁盘空间,最好能够压缩一下该文件。这只需要加一个-z选项就行了。
它会将tar归档文件压缩成gzip格式的tar文件,这种文件也叫作tarball。
别忘了使用恰当的文件扩展名来表示这是个tarball,用.tar.gz或.tgz都行。
你不需要为待备份的新目录或文件修改或编写新的归档脚本,而是可以借助于配置文件。配置文件应该包含你希望进行归档的每个目录或文件。
$ cat Files_To_Backup /home/Christine/Project /home/Christine/Downloads /home/Does_not_exist /home/Christine/Documents可以让脚本读取配置文件,然后将每个目录名加到归档列表中。使用exec命令来重定向标准输入(STDIN),用法如下。
exec < $CONFIG_FILE
read FILE_NAME
我们为归档配置文件使用了一个变量,CONFIG_FILE。配置文件中每一条记录都会被读入。
只要read命令在配置文件中发现还有记录可读,它就会在?变量中返回一个表示成功的退出状态码0。
可以将它作为while循环的测试条件来读取配置文件中的所有记录。
while [ $? -eq 0 ] do [...] read FILE_NAME done一旦read命令到了配置文件的末尾,它就会返回一个非零状态码。这时脚本会退出while循环。
在while循环中,我们需要做两件事。首先,必须将目录名加到归档列表中。更重要的是要检查那个目录是否存在!很可能你从文件系统中删除了一个目录却忘了更新归档配置文件。
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语句就成立。
2.创建逐日归档文件的存放位置
如果要对多个目录进行备份,最好还是创建一个集中归档仓库目录。可以通过sudo命令或者创建一个用户组的方式,为需要在集中归档目录中创建文件的用户授权。
sudo groupadd Archivers sudo chgrp Archivers /archive ls -ld /archive sudo usermod -aG Archivers Christine sudo chmod 775 /archive ls -ld /archive将用户添加到Archivers组后,用户必须先登出然后再登入,才能使组成员关系生效。现在只要是该组的成员,无需超级用户权限就可以在目录中创建文件了。
记住,Archivers组的所有用户都可以在归档目录中添加和删除文件。为了避免组用户删除他人的归档文件,最好还是把目录的粘滞位加上。
3.创建按日归档的脚本
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/$FILEDESTINATION变量会将归档文件的全路径名加上去。CONFIG_FILE变量指向含有待归档目录信息的归档配置文件。如果需要,二者都可以很方便地改成备用目录和文件。
下面是完整的代码
#!/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
4.运行按日归档的脚本
必须赋予文件属主可执行权限(x)才能够运行脚本。chmod u+x daily_archive.sh ./daily_archive.sh这样就可以运行脚本了
5.创建按小时归档的脚本
不必将所有的归档文件都放到同一目录中,你可以为归档文件创建一个目录层级。这个归档目录包含了与一年中的各个月份对应的目录,将月的序号作为目录名。而每月的目录中又包含与当月各天对应的目录(用天的序号作为目录名)。这样你只用给每个归档文件加上时间戳,然后将它们放到与月日对应的目录中就行了。
二、管理用户账户
管理用户账户绝不仅仅是添加、修改和删除账户,你还得考虑安全问题、保留工作的需求以及对账户的精确管理。1.需要的功能
在删除账户时,至少需要4个步骤:- 获得正确的待删除用户账户名;
- 杀死正在系统上运行的属于该账户的进程;
- 确认系统中属于该账户的所有文件;
- 删除该用户账户。
1.1.获取正确的账户名
账户删除过程中的第一步最重要:获取待删除的用户账户的正确名称。可以在read命令中用-t选项,在超时退出之前给用户60秒的时间回答问题。echo "Please enter the username of the user " echo -e "account you wish to delete from system: \c" read -t 60 ANSWER用一个while循环加-z选项来测试ANSWER变量是否为空。在脚本第一次进入while循环时,ANSWER变量的内容为空,用来给该变量赋值的提问位于循环的底部。
while [ -z "$ANSWER" ] do [...] echo "Please enter the username of the user " echo -e "account you wish to delete from system: \c" read -t 60 ANSWER done通过给ASK_COUNT变量增值,可以设定不同的消息来回应脚本用户。
case $ASK_COUNT in 2) echo echo "Please answer the question." echo ;; 3) echo echo "One last try...please answer the question." echo ;; 4) echo echo "Since you refuse to answer the question..." echo "exiting program." echo exit ;; esac
1.2. 创建函数获取正确的账户名
你要做的第一件事是声明函数名get_answer。下一步,用unset命令清除脚本用户之前给出的答案。function get_answer { # unset ANSWER ASK_COUNT=0 # while [ -z "$ANSWER" ] do ASK_COUNT=$[ $ASK_COUNT + 1 ] # case $ASK_COUNT in 2) echo esac # echo if [ -n "$LINE2" ] then #Print 2 lines echo $LINE1 echo -e $LINE2" \c" else #Print 1 line echo -e $LINE1" \c" fi # read -t 60 ANSWER done # unset LINE1 unset LINE2 # } #End of get_answer function使用新函数让脚本代码清爽了许多。
LINE1="Is $USER_ACCOUNT the user account " LINE2="you wish to delete from the system? [y/n]"
1.3.验证输入的用户名
可以用case语句来处理答案。case语句部分必须精心编码,这样它才会检查yes的多种输入方式。这个脚本有时需要处理很多次用户的yes/no回答。因此,创建一个函数来处理这个任务是有意义的。
function process_answer { # case $ANSWER in y|Y|YES|yes|Yes|yEs|yeS|YEs|yES ) ;; *) echo echo $EXIT_LINE1 echo $EXIT_LINE2 echo exit ;; esac # unset EXIT_LINE1 unset EXIT_LINE2 # } #End of process_answer function
1.4.确定账户是否存在
现在最好核对一下这个用户账户在系统上是否真实存在。还有,最好将完整的账户记录显示给脚本用户,核对这是不是真的要删除的那个账户。使用变量USER_ACCOUNT_RECORD,将它设成grep在/etc/passwd文件中查找该用户账户的输出。-w选项允许你对这个特定用户账户进行精确匹配。
USER_ACCOUNT_RECORD=$(cat /etc/passwd | grep -w $USER_ACCOUNT)
grep命令的退出状态码可以在这里帮到我们。如果没找到这条账户记录,?变量会被设成1。
if [ $? -eq 1 ] then echo echo "Account, $USER_ACCOUNT, not found. " echo "Leaving the script..." echo exit fi
如果找到了这条记录,你仍然需要验证这个脚本用户是不是正确的账户。
echo "I found this record:" echo $USER_ACCOUNT_RECORD echo # LINE1="Is this the correct User Account? [y/n]" get_answer EXIT_LINE1="Because the account, $USER_ACCOUNT, is not" EXIT_LINE2="the one you wish to delete, we are leaving the script..." process_answer
1.5.删除属于账户的进程
查找用户进程较为简单。这里脚本可以用ps命令(参见第4章)和-u选项来定位属于该账户的所有处于运行中的进程。ps -u $USER_ACCOUNT >/dev/null
可以用ps命令的退出状态码和case结构来决定下一步做什么。
case $? in 1) # No processes running for this User Account # echo "There are no processes for this account currently running." echo ;; 0) # Processes running for this User Account. # Ask Script User if wants us to kill the processes. # echo "$USER_ACCOUNT has the following processes running: " echo ps -u $USER_ACCOUNT # LINE1="Would you like me to kill the process(es)? [y/n]" get_answer # [...] esac如果ps命令的退出状态码返回了1,那么表明系统上没有属于该用户账户的进程在运行。但如果退出状态码返回了0,那么系统上有属于该账户的进程在运行。
杀死用户进程,需要三条命令,收集pid
COMMAND_1="ps -u $USER_ACCOUNT --no-heading"
提取PID
gawk '{print $1}'
第三条命令是xargs,该命令可以构建并执行来自标准输入STDIN的命令。它非常适合用在管道的末尾处。xargs命令负责杀死PID所对应的进程。
COMMAND_3="xargs -d \\n /usr/bin/sudo /bin/kill -9"
选项-d指明使用什么样的分隔符。换句话说,既然
xargs命令接收多个项作为输入,那么各个项之间要怎么区分呢?在这里,\n(换行符)被作为各项的分隔符。
当每个PID发送给xargs时,它将PID作为单个项来处理。又因为xargs命令被赋给了一个变量,所以\n中的反斜杠(\)必须再加上另一个反斜杠(\)进行转义。
在处理PID时,xargs命令需要使用命令的完整路径名。sudo命令和kill命令用于杀死用户账户的运行进程。另外还注意到kill命令使用了信号-9。
通过管道符链接:
$COMMAND_1 | gawk '{print $1}' | $COMMAND_3
用于杀死用户账户所有的运行进程的完整的case语句如下所示。
case $ANSWER in y|Y|YES|yes|Yes|yEs|yeS|YEs|yES ) # If user answers "yes", #kill User Account processes. echo echo "Killing off process(es)..." # # List user processes running code in variable, COMMAND_1 COMMAND_1="ps -u $USER_ACCOUNT --no-heading" # # Create command to kill proccess in variable, COMMAND_3 COMMAND_3="xargs -d \\n /usr/bin/sudo /bin/kill -9" # # Kill processes via piping commands together $COMMAND_1 | gawk '{print $1}' | $COMMAND_3 # echo echo "Process(es) killed." ;;
1.6.查找属于账户的文件
要找到用户文件,你可以用find命令。find命令用-u选项查找整个文件系统,它能够准确查找到属于该用户的所有文件。该命令如下:find / -user $USER_ACCOUNT > $REPORT_FILE
1.7.删除账户
对删除系统中的用户账户慎之又慎总是好事。因此,你应该再问一次脚本用户是否真的想删除该账户:LINE1="Remove $User_Account's account from system? [y/n]" get_answer # EXIT_LINE1="Since you do not wish to remove the user account," EXIT_LINE2="$USER_ACCOUNT at this time, exiting the script..." process_answer从系统中真正地删除该用户账户。这里用到了userdel命令
userdel $USER_ACCOUNT
2.创建脚本
完整脚本:由于脚本太长,参见Linux命令行与shell脚本编程大全.第3版 (布鲁姆,布雷斯纳汉)的24.2.2章节三、监测磁盘空间
这个shell脚本工具会帮你找出指定目录中磁盘空间使用量位居前十名的用户。它会生成一个以日期命名的报告,使得磁盘空间使用量可以监测。1.需要的功能
你要用到的第一个工具是du命令,-s选项用来总结目录一级的整体使用状况。
-S(大写的S)选项,它为每个目录和子目录分别提供了总计信息。这样你就能快速地定位问题的根源。
由于我们感兴趣的是占用磁盘空间最多的目录,所以需要使用sort命令对du产生的输出进行排序
-n选项允许按数字排序。-r选项会先列出最大数字(逆序)。
sed编辑器可以让这个列表更容易读懂。我们要关注的是磁盘用量的前10名用户,所以当到了第11行时,sed会删除列表的剩余部分。
下一步是给列表中的每行加一个行号。
sed '{11,$D; =}' | sed 'N; s/\n/ /' |可以用gawk命令清理输出了,sed编辑器的输出会通过管道输出到gawk命令,然后用printf函数打印出来。
gawk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
完整的命令如下:
2.创建脚本
为 了 节 省 时 间 和 精 力 , 这 个 脚 本 会 为 多 个 指 定 目 录 创 建 报 告 。 我 们 用 一 个 叫 作CHECK_DIRECTORIES的变量来完成这一任务脚本使用for循环来对变量中列出的每个目录执行du命令。
每次for循环都会遍历变量CHECK_DIRECTORIES中的值列表,它会将列表中的下一个值赋给DIR_CHECK变量
为了方便识别,我们用date命令给报告的文件名加个日期戳。脚本用exec命令将它的输出重定向到加带日期戳的报告文件中。
为了生成格式精致的报告,这个脚本会用echo命令来输出一些报告标题。
完整脚本如下:
#!/bin/bash # # Big_Users - Find big disk space users in various directories ############################################################### # Parameters for Script # CHECK_DIRECTORIES=" /var/log /home" #Directories to check # ############## Main Script ################################# # DATE=$(date '+%m%d%y') #Date for report file # exec > disk_space_$DATE.rpt #Make report file STDOUT # echo "Top Ten Disk Space Usage" #Report header echo "for $CHECK_DIRECTORIES Directories" # for DIR_CHECK in $CHECK_DIRECTORIES #Loop to du directories do echo "" echo "The $DIR_CHECK Directory:" #Directory header # # Create a listing of top ten disk space users in this dir du -S $DIR_CHECK 2>/dev/null | sort -rn | sed '{11,$D; =}' | sed 'N; s/\n/ /' | gawk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}' # done #End of loop # exit这个简单的shell脚本会为你选择的每个目录创建一个包含日期戳的磁盘空间用量前10名的用户报告。
3.运行脚本
报告内容
现在你可以让这个脚本在需要时自动运行了,可以用cron表来实现。