Shell Script 实用脚本

自动备份

  • 归档配置文件

利用tar命令归档数据,可以将整个目录归档到单个文件夹中。一般tar命令会显示一条警告信息,表明它删除了路径名开头的斜线,将路径从绝对路径名变成相对路径名,这样就可以将tar归档文件解压到文件系统中的任何地方了。

由于tar归档文件会消耗大量的磁盘空间,为了压缩文件需要加一个-z选项。它会将tar归档文件压缩成gzip格式的tar文件用.tar.gz或.tgz都行

$ tar -cf archive.tar /home/Christine/Project/*.*
tar: Removing leading '/' from member names    #想去除此警告,利用重定向
$ tar -cf archive.tar /home/Christine/Project/*.* 2>/dev/null

$ tar -zcf archive.tar.gz /home/Christine/Project/*.* 2>/dev/null

如果想备份多个目录可以利用一个配置文件解决,配置文件应该包含你希望进行归档的每个目录或文件。可以让脚本读取配置文件,然后将每个目录名加到归档列表中。

$ cat Files_To_Backup
/home/Christine/Project
/home/Christine/Downloads
/home/Does_not_exist
/home/Christine/Documents
$

要实现这一点,只需要使用read命令来读取该文件中的每一条记录就行了。不过不用像之前那样通过管道将cat命令的输出传给while循环(cat file | while read line),在这个脚本中我们使用exec命令来重定向标准输入(STDIN)。CONFIG_FILE(归档配置文件)

exec < $CONFIG_FILE    #输入管道从cat命令改为文件名
read FILE_NAME        #每次调用read都会从文件中读取一行文本

只要read命令在配置文件中发现还有记录可读,它就会在?变量中返回一个表示成功的退出状态码0。可以将它作为while循环的测试条件来读取配置文件中的所有记录。

while [ $? -eq 0 ]
do
    [...]
    read FILE_NAME
done

在while循环中,我们需要做两件事。首先,必须将目录名加到归档列表中。更重要的是要检查那个目录是否存在!。,否则就显示一条警告消息。

if [ -f $FILE_NAME -o -d $FILE_NAME ]    #-f-d判断是文件还是目录;or选项-o
then
    FILE_LIST="$FILE_LIST $FILE_NAME"    # 如果目录存在,它会被加入要归档目录列表FILE_LIST中
else                                    # 否则,显示一条警告消息
    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] #添加了变量FILE_NO。配置文件中哪行不正确或缺失
  • 存放位置

如果要对多个目录进行备份,最好还是创建一个集中归档仓库目录

$ mkdir /archive
$ mv Files_To_Backup /archive/

可以通过sudo命令或者创建一个用户组的方式,为需要在集中归档目录中创建文件的用户授权。可以创建一个特殊的用户组Archivers。

  • 创建按日归档的脚本

Daily_Archive脚本会自动在指定位置创建一个归档,使用当前日期来唯一标识该文件

DATE=$(date +%y%m%d)
FILE=archive$DATE.tar.gz
CONFIG_FILE=/archive/file_to_backup
DESTINATION=/archive/$FILE

将所有的内容结合在一起,Daily_Archive脚本内容如下

#!/bin/bash
DATE=$(date +%y%m%d)
FILE=archive$DATE.tar.gz
CONFIG_FILE=/archive/Files_To_Backup
DESTINATION=/archive/$FILE
######### Main Script #########################
if [ -f $CONFIG_FILE ]             #确保配置文件存在
then
    echo
else
    echo
    echo "$CONFIG_FILE does not exist."
    echo "Backup not completed due to missing Configuration File"
    echo
    exit
fi

FILE_NO=1
exec < $CONFIG_FILE 
read FILE_NAME 

while [ $? -eq 0 ] 
    do
    if [ -f $FILE_NAME -o -d $FILE_NAME ]   
    then
        FILE_LIST="$FILE_LIST $FILE_NAME"   
    else                                    
        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] 
    read FILE_NAME 
done

echo "Starting archive..."
echo

tar -czf $DESTINATION $FILE_LIST 2> /dev/null

echo "Archive completed"
echo "Resulting archive file is: $DESTINATION"
echo
exit
  • 创建按小时归档的脚本

在按小时备份文件时,使用date命令为每个tarball文件加入时间戳不再合适,不必将所有的归档文件都放到同一目录中,你可以为归档文件创建一个目录层级,如下所示,每月的目录中又包含与当月各天对应的目录

新目录设置好之后,将按小时归档的配置文件File_To_Backup移动到该目录中。mv Files_To_Backup /archive/hourly/

我们脚本必须自动创建对应每月和每天的目录,如果这些目录已经存在的话,脚本就会报错。而mkdir命令的-p选项,允许在单个命令中创建目录和子目录。并且就算目录已经存在,它也不会产生错误消息。

现在可以创建Hourly_Archive.sh脚本了,以下是前脚本的前半部分.一旦脚本Hourly_Archive.sh到了Main Script部分,就和Daily_Archive.sh脚本完全一样了。

#!/bin/bash
BASEDEST=/archive/hourly
DAY=$(date +%d)
MONTH=$(date +%m)
TIME=$(date +%k%m)
mkdir -p $BASEDEST/$MONTH/$DAY
CONFIG_FILE=/archive/hourly/Files_To_Backup
DESTINATION=$BASEDEST/$MONTH/$DAY/archive$TIME.tar.gz
######### Main Script #########################

管理用户账户

删除账户在管理账户工作中比较复杂。在删除账户时,至少需要4个步骤:

(1) 获得正确的待删除用户账户名;
(2) 杀死正在系统上运行的属于该账户的进程;
(3) 确认系统中属于该账户的所有文件;

(4) 删除该用户账户

  • 获取正确的账户名

获取待删除的用户账户的正确名称。可以用read命令获取账户名称 

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

利用case语句,通过给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
  • 创建函数获取正确的账户名

声明函数名get_answer。下一步,用unset命令清除脚本用户之前给出的答案

function get_answer {
unset ANSWER

这个脚本不会每次都问同一个问题,所以让我们创建两个新的变量LINE1和LINE2来处理问题,并不是每个问题都有两行要显示,有的只要一行。你可以用if结构解决这个问题。这个函数会测试LINE2是否为空,如果为空,则只用LINE1。

if [ -n "$LINE2" ]
then
    echo $LINE1
    echo -e $LINE2" \c"
else
    echo -e $LINE1" \c"
fi

最终,我们的函数需要通过清空LINE1和LINE2变量来清除一下自己

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 
        echo $LINE1
        echo -e $LINE2" \c"
    else
        echo -e $LINE1" \c"
    fi
    read -t 60 ANSWER
done
unset LINE1
unset LINE2
}

要问脚本用户删除哪个账户,你需要设置一些变量,然后调用get_answer函数。使用新函数让脚本代码清爽了许多

LINE1="Please enter the username of the user "
LINE2="account you wish to delete from system:"
get_answer
USER_ACCOUNT=$ANSWER
  • 验证输入的用户名

鉴于可能存在输入错误,应该验证一下输入的用户账户

LINE1="Is $USER_ACCOUNT the user account "
LINE2="you wish to delete from the system? [y/n]"
get_answer

在提出问题之后,脚本必须处理答案。变量ANSWER再次将脚本用户的回答带回问题中。如果用户回答了yes,就得到了要删除的正确用户账户,脚本也可以继续执行。

case $ANSWER in
y|Y|YES|yes|Yes|yEs|yeS|YEs|yES )
    ;;
*)
    echo
    echo "Because the account, $USER_ACCOUNT, is not "
    echo "the one you wish to delete, we are leaving the script..."
    echo
    exit
    ;;
esac

创建一个函数来处理这个任务,还要给case语句中加两个变量, EXIT_LINE1 和EXIT_LINE2

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
}

EXIT_LINE1="Because the account, $USER_ACCOUNT, is not "
EXIT_LINE2="the one you wish to delete, we are leaving the script..."
process_answer
  • 确定账户是否存在

核对一下这个用户账户在系统上是否真实存在。并将完整的账户记录显示给脚本用户,核对这是不是真的要删除的那个账户。要完成这些工作,需使用变量USER_ACCOUNT_RECORD,将它设成grep在/etc/passwd文件中查找该用户账户的输出。-w选项允许你对这个特定用户账户进行精确匹配。

USER_ACCOUNT_RECORD=$(cat /etc/passwd | grep -w $USER_ACCOUNT)

如果在/etc/passwd中没找到用户账户记录,必须通知脚本用户,然后退出脚本。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
  • 删除属于账户的进程

脚本可以用ps命令和-u选项来定位属于该账户的所有处于运行中的进程。可以将输出重定向到/dev/null,这样用户就看不到任何输出信息了。可以用ps命令的退出状态码和case结构来决定下一步做什么。

ps -u $USER_ACCOUNT >/dev/null
case $? in
1)         #没有属于该用户账户的进程在运行
    echo "There are no processes for this account currently running."
    echo
    ;;
0)        #问脚本用户是否要杀死这些进程
    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

接下来的任务对process_answer来说太复杂了。你需要嵌入另一个case语句来处理脚本用户的答案。case语句的第一部分看起来和process_answer函数很像。xargs命令可以构建并执行来自标准输入STDIN的命令。它非常适合用在管道的末尾处。xargs命令负责杀死PID所对应的进程。

这三条命令通过管道串联在了一起。ps命令生成了处于运行状态的用户进程列表,其中包括每个进程的PID。gawk命令将ps命令的标准输出(STDOUT)作为自己的STDIN,然后从中只提取出PID。xargs命令将gawk命令生成的每个PID作为STDIN,创建并执行kill命令,杀死用户所有的运行进程。

case $ANSWER in
y|Y|YES|yes|Yes|yEs|yeS|YEs|yES ) 
    echo
    echo "Killing off process(es)..."
    COMMAND_1="ps -u $USER_ACCOUNT --no-heading"    #收集当前处于运行状态、属于该用户账户的进程ID(PID)。
    COMMAND_3="xargs -d \\n /usr/bin/sudo /bin/kill -9"
    $COMMAND_1 | gawk '{print $1}' | $COMMAND_3 #gawk命令可以从ps命令输出中提取第一个字段PID。
    echo
    echo "Process(es) killed."
    ;;
*)
    echo
    echo "Will not kill the process(es)"
    echo
    ;;
esac
  • 查找属于账户的文件

要找到用户文件,你可以用find命令。find命令用-u选项查找整个文件系统,它能够准确查找到属于该用户的所有文件。该命令如下:

find / -user $USER_ACCOUNT > $REPORT_FILE
  • 删除账户

对删除系统中的用户账户慎之又慎总是好事。因此,你应该再问一次脚本用户是否真的想删除该账户:

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 $USER_ACCOUNT
  • 创建脚本

#!/bin/bash
function get_answer {
unset ANSWER
ASK_COUNT=0
while [ -z "$ANSWER" ]
do
    ASK_COUNT=$[ $ASK_COUNT + 1 ]
    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
    echo
    if [ -n "$LINE2" ]
    then 
        echo $LINE1
        echo -e $LINE2" \c"
    else 
    echo -e $LINE1" \c"
    fi
    read -t 60 ANSWER
done
unset LINE1
unset LINE2
}

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
}
############# Main Script ####################
# 首次获得删除账号
echo "Step #1 - Determine User Account name to Delete "
echo
LINE1="Please enter the username of the user "
LINE2="account you wish to delete from system:"
get_answer
USER_ACCOUNT=$ANSWER
# 重复确认
LINE1="Is $USER_ACCOUNT the user account "
LINE2="you wish to delete from the system? [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
#确认用户存在并获取删除用户信息
USER_ACCOUNT_RECORD=$(cat /etc/passwd | grep -w $USER_ACCOUNT)
if [ $? -eq 1 ]
then
    echo
    echo "Account, $USER_ACCOUNT, not found. "
    echo "Leaving the script..."
    echo
    exit
fi
echo
echo "I found this record:"
echo $USER_ACCOUNT_RECORD
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
# 用户进程处理
echo
echo "Step #2 - Find process on system belonging to user account"
echo
ps -u $USER_ACCOUNT >/dev/null #Are user processes running?
case $? in
1)
    echo "There are no processes for this account currently running."
    echo
    ;;
0) 
    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
    case $ANSWER in
    y|Y|YES|yes|Yes|yEs|yeS|YEs|yES )
        echo
        echo "Killing off process(es)..."
        COMMAND_1="ps -u $USER_ACCOUNT --no-heading"
        COMMAND_3="xargs -d \\n /usr/bin/sudo /bin/kill -9"
        $COMMAND_1 | gawk '{print $1}' | $COMMAND_3
        echo
        echo "Process(es) killed."
        ;;
    *) 
        echo
        echo "Will not kill the process(es)"
        echo
        ;;
    esac
    ;;
esac
# 产生用户所属文件报告
echo
echo "Step #3 - Find files on system belonging to user account"
echo
echo "Creating a report of all files owned by $USER_ACCOUNT."
echo
echo "It is recommended that you backup/archive these files,"
echo "and then do one of two things:"
echo " 1) Delete the files"
echo " 2) Change the files' ownership to a current user account."
echo
echo "Please wait. This may take a while..."
REPORT_DATE=$(date +%y%m%d)
REPORT_FILE=$USER_ACCOUNT"_Files_"$REPORT_DATE".rpt"
find / -user $USER_ACCOUNT > $REPORT_FILE 2>/dev/null
echo
echo "Report is complete."
echo "Name of report: $REPORT_FILE"
echo "Location of report: $(pwd)"
echo
# 移除用户
echo
echo "Step #4 - Remove user account"
echo
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 $USER_ACCOUNT
echo
echo "User account, $USER_ACCOUNT, has been removed"
echo
exit

监测磁盘空间

找出指定目录中磁盘空间使用量位居前十名的用户。它会生成一个以日期命名的报告,使得磁盘空间使用量可以监测。

  • 需要的功能

du命令能够显示出单个文件和目录的磁盘使用情况。-s选项用来总结目录一级的整体使用状况

$ du -s /home/*
4204 /home/Christine
56 /home/Consultant
52 /home/Development
4 /home/NoSuchUser
96 /home/Samantha
36 /home/Timothy
1024 /home/user1
$

-s选项能够很好地处理用户的$HOME目录,但如果我们要查看系统目录,这个列表很快就变得过于琐碎。这里,-S(大写的S)选项能更适合我们的目的,它为每个目录和子目录分别提供了总计信息

$ du -S /var/log/
4 /var/log/ppp
4 /var/log/sssd
3020 /var/log/sa
80 /var/log/prelink
4 /var/log/samba/old
4 /var/log/samba
4 /var/log/ntpstats
4 /var/log/cups
4392 /var/log/audit
420 /var/log/gdm
4 /var/log/httpd
152 /var/log/ConsoleKit
2976 /var/log/
$

由于我们感兴趣的是占用磁盘空间最多的目录,所以需要使用sort命令对du产生的输出进行排序

$ sudo du -S /var/log/ | sort -rn    #-n选项允许按数字排序。-r选项会先列出最大数字(逆序)
4392 /var/log/audit
3020 /var/log/sa
2976 /var/log/
420 /var/log/gdm
152 /var/log/ConsoleKit
80 /var/log/prelink
4 /var/log/sssd
4 /var/log/samba/old
4 /var/log/samba
4 /var/log/ppp
4 /var/log/ntpstats
4 /var/log/httpd
4 /var/log/cups
$

我们要关注的是磁盘用量的前10名用户,所以当到了第11行时,sed编辑器会删除列表的剩余部分。下一步是给列表中的每行加一个行号。使用sed的等号命令(=)来加入行号。要让行号和磁盘空间文本位于同一行,可以用N命令将文本行合并在一起。

sed '{11,$D; =}' |
sed 'N; s/\n/ /' |

现在可以用gawk命令清理输出了。sed编辑器的输出会通过管道输出到gawk命令,然后用printf函数打印出来。

gawk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'

在行号后面,我们加了一个冒号(:),还给输出的每行文本的字段间放了一个制表符。这样就能得到一个格式精致的磁盘空间用量前10名的用户列表。

$ sudo du -S /var/log/ |
> sort -rn |
> sed '{11,$D; =}' |
> sed 'N; s/\n/ /' |
> gawk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
[sudo] password for Christine:
1: 4396 /var/log/audit
2: 3024 /var/log/sa
3: 2976 /var/log/
4: 420 /var/log/gdm
5: 152 /var/log/ConsoleKit
6: 80 /var/log/prelink
7: 4 /var/log/sssd
8: 4 /var/log/samba/old
9: 4 /var/log/samba
10: 4 /var/log/ppp
$
  • 创建脚本

我们用一个叫作CHECK_DIRECTORIES的变量来完成多个指定目录创建报告任务。

CHECK_DIRECTORIES=" /var/log /home"

脚本使用for循环来对变量中列出的每个目录执行du命令。每次for循环都会遍历变量CHECK_DIRECTORIES中的值列表,它会将列表中的下一个值赋给DIR_CHECK变量。

for DIR_CHECK in $CHECK_DIRECTORIES
do
    [...]
    du -S $DIR_CHECK
    [...]
done

我们用date命令给报告的文件名加个日期戳。脚本用exec命令将它的输出重定向到加带日期戳的报告文件中。

DATE=$(date '+%m%d%y')
exec > disk_space_$DATE.rpt
#!/bin/bash

CHECK_DIRECTORIES=" /var/log /home"
############## 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" 
echo "for $CHECK_DIRECTORIES Directories"
for DIR_CHECK in $CHECK_DIRECTORIES 
do
    echo ""
    echo "The $DIR_CHECK Directory:" 
    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 
exit

猜你喜欢

转载自blog.csdn.net/linshuo1994/article/details/84280668