第18章 脚本范例
18.1 批量添加用户脚本
用户管理是Linux系统维护的工作之一,其中涉及用户添加、删除等简单操作。但是如果需要一次性添加几十个还甚至上百个用户,这时候再简单地重复性使用useradd进行添加就非常低效了。现在来分析一下要完成这个工作的场景和必要的实现方式。
首先,需要一个包含所有需添加用户的用户名和密码的文本文件,该文件以行为单位,每行是一条用户信息,用户名和密码之间使用特定的分隔符分开,可以是空格、Tab键或冒号等(不同的分隔符只是脚本处理时的方法不同,没有本质区别)。为了让编写该脚本的过程遇到更多的问题,这里特意选择使用空格作为分隔符,根据这个方案写出的文本文档如下所示:
- [root@localhost ~]# cat addusers.txt username001 password001
username002 password002
username003 password003
username004 password004
username005 password005
username006 password006
现在我们面临的是如何根据这个文件添加用户了。想一想,如果是让你根据这个文本来手工添加用户,你会如何操作?大概首先想到的是从第一行开始操作,第一行的用户名是username001,密码是password001,然后使用useradd username001增加用户,再使用passwd username001给用户设置密码,以此类推,直到完成最后一个用户的添加操作——注意这是一个循环,所以需要使用循环来让系统做这件事情。问题又来了,Shell中的循环有很多种,有for循环、while循环、until循环,应该使用哪一种循环呢?
使用for循环和while循环都可以较方便地按行读取,但是笔者更倾向于使用while循环,因为while循环在按行读取时有着天然的优势。先看一下使用for循环按行读取脚本的情况,以及运行结果。
- #for
循环按行读取脚本
[root@localhost ~]# cat useradd_for01.sh #!/bin/bash
COUNT=0
for LINES in `cat addusers.txt`
do
echo $LINES
let COUNT+=1
done
echo
echo "$0 looped $COUNT times"
#for
循环按行读取脚本运行结果
[root@localhost ~]# bash useradd_for01.sh username001
password001
username002
password002
username003
password003
username004
password004
username005
password005
username006
password006
useradd_for01.sh looped 12 times
从上面的脚本运行输出可以看出,该脚本实际上并没有做到“按行读取”,因为它实际上循环了12次!不是应该循环6次吗,怎么会是12次呢?这是因为addusers.txt文件中的用户名和密码是使用空格隔开的,而for循环在读取文件时,任何空白字符都可以作为其读取的分隔符,所以它其实循环了12次。
来看看while循环的按行读取脚本的情况,以及运行结果。
- #while 循环按行读取脚本
[root@localhost ~]# cat useradd_while01.sh #!/bin/bash
COUNT=0
while read LINES
do
echo $LINES
let COUNT+=1
done < addusers.txt echo
echo "$0 looped $COUNT times"
#while
循环按行读取脚本运行结果
[root@localhost ~]# bash useradd_while01.sh username001 password001
username002 password002
username003 password003
username004 password004
username005 password005
username006 password006
useradd_while01.sh looped 6 times
从脚本运行结果来看,while的按行读取确实没有问题,因为while使用的是换行符作为行标记。如果一开始我们决定使用其他符号作为分隔符(比如冒号),那for循环也能胜任该项工作,但是这样读者就不会注意到这个问题了。这也是前面笔者特意选择空格作为分隔符的原因。
接下来需要使用字符工具对每行进行加工:从每行中分拆出用户名和密码。每行空格前的部分是用户名,空格后的部分为密码。只需要使用cut命令就能很简单地分拆开来了,把useradd_while01.sh升级成下面的内容,并运行。查看结果,确认脚本正确地截取了需要的信息。
- [root@localhost ~]# cat useradd_while02.sh #!/bin/bash
while read LINES
do
USERNAME=`echo $LINES | cut -f1 -d' '`
PASSWORD=`echo $LINES | cut -f2 -d' '`
#
测试打印截取结果
echo -n "USERNAME:$USERNAME PASSWORD:$PASSWORD"
echo
done < addusers.txt #
脚本成功截取了用户名和密码
[root@localhost ~]# bash useradd_while02.sh USERNAME:username001 PASSWORD:password001
USERNAME:username002 PASSWORD:password002
USERNAME:username003 PASSWORD:password003
USERNAME:username004 PASSWORD:password004
USERNAME:username005 PASSWORD:password005
USERNAME:username006 PASSWORD:password006
现在既然可以成功地截取到每行的用户名、密码,那么事情就变得简单了:在新增用户时,只需要先使用useradd USERNAME命令、然后使用passwd USERNAME命令就可以了。
不过这里有个问题,还记得passwd命令是怎么运行的吗——它要求管理员手工输入密码。有没有办法能直接将密码当作一个参数传给passwd命令呢?答案是肯定的:运行man passwd。查看该命令的用法可以发现,通过使用—stdin参数,可以使用管道将密码传给passwd命令。
- --stdin This option is used to indicate that passwd should read the new password from standard input, which can be a pipe.
至此该脚本的基本框架就分析完了,最后将脚本修改为如下内容,运行并观察结果。
- [root@localhost ~]# cat useradd_while03.sh #!/bin/bash
while read LINES
do
USERNAME=`echo $LINES | cut -f1 -d' '`
PASSWORD=`echo $LINES | cut -f2 -d' '`
useradd $USERNAME
echo $PASSWORD | passwd --stdin $USERNAME
done < addusers.txt #
脚本运行结果
[root@localhost ~]# bash useradd_while03.sh Changing password for user username001.
passwd: all authentication tokens updated successfully.
Changing password for user username002.
passwd: all authentication tokens updated successfully.
Changing password for user username003.
passwd: all authentication tokens updated successfully.
Changing password for user username004.
passwd: all authentication tokens updated successfully.
Changing password for user username005.
passwd: all authentication tokens updated successfully.
Changing password for user username006.
passwd: all authentication tokens updated successfully.
虽然说这个脚本现在已经“可以工作”了,但还不是那么完美。如果你再运行一遍该脚本,会发现新增用户是失败的(因为之前运行过一次后,在运行时用户已经存在),但是却又成功地“修改”了用户的密码,这是很危险的。所以需要在脚本中增加用户判断的环节:如果用户已存在,则跳过不做任何修改,否则增加用户。另外,在可能的情况下,所有非Shell内建命令都建议使用全路径,以避免由于环境变量的问题造成的command not found。最后,脚本主体要尽量少使用常量,所以需要在脚本的开头多定义变量。按上面的要求完成脚本修改,最终形式如下:
- [root@localhost ~]# cat useradd_while04.sh #!/bin/bash
USERS_INFO=/root/addusers.txt USERADD=/usr/sbin/useradd PASSWD=/usr/bin/passwd CUT=/bin/cut
while read LINES
do
USERNAME=`echo $LINES | $CUT -f1 -d' '`
PASSWORD=`echo $LINES | $CUT -f2 -d' '`
$USERADD $USERNAME
if [ $? -ne 0 ]; then echo "$USERNAME exists, skip set password"
else
echo $PASSWORD | $PASSWD --stdin $USERNAME
fi
done < $USERS_INFO
读者可以在此基础上增加更多的功能和判断使其变得更为完美。