Linux服务器如何批量创建用户?Shell脚本与权限自动化管理

给服务器加用户这件事,量小的时候怎么都行:useradd 敲一下、passwd 设个密码就完事。但一旦要一次建几十上百个账号——比如给学生开实训环境、给新入职的一批员工开权限、给外包团队开临时账号——手工敲就变成了事故温床:密码记混、uid 撞车、主目录权限漏设、离职的忘记删。

这篇文章讲怎么用 Shell 脚本把批量建用户这件事做得规范、可重复、可追溯。内容包括:用户与组的基础概念、useradd 的常用参数、读取名单批量创建、给 sudo 权限、以及到期自动失效和离职回收的处理。

文中的 uid/gid 区间、密码策略参数都是常见的经验值,可按企业规范调整。涉及权限授予和删除用户的部分会给出风险提示——给错 sudo 权限和误删用户都是很严重的问题,请务必看清再做。

一、先把概念理清楚

Linux 的用户管理围绕三个东西展开:用户(user)、组(group)、以及权限(permission)。理解它们的关系,是用对命令的前提。

每个用户有一个数字 uid,系统靠 uid 而不是用户名来判定身份。每个用户至少属于一个主组(primary group),还可以加入若干附加组(supplementary group)。文件权限则由属主、属组和其他人的读/写/执行位决定,配合 umask 决定新建文件的默认权限。

系统的用户和自建用户通常分开管理,靠 uid 区间区分:

用户类型 常见 uid 区间 说明 举例
系统用户 1-999(RHEL 系常见) 给服务进程用,不登录 nginx、mysql、sshd
普通用户 1000 起 真实登录用户 运维、开发账号
root 0 超级用户 root
nobody 65534 匿名/降权用 nobody

新增登录用户时,建议从 1000 以上开始分配,避免和系统用户撞号。uid 冲突会导致文件属主显示错乱、权限判断出错,是很隐蔽的故障。

二、useradd 关键参数

批量建用户之前,得先把 useradd 的参数摸熟。它和 adduser 是两个不同的东西:useradd 是底层命令、可精细控制;adduser 是很多发行版提供的友好封装,交互式、自动化时不方便。脚本里统一用 useradd。

参数 作用 示例
-m 创建主目录 useradd -m alice
-d 指定主目录路径 -d /data/home/alice
-s 指定登录 shell -s /bin/bash
-G 附加组(可多个,逗号分隔) -G docker,developers
-g 主组 -g developers
-u 指定 uid -u 1500
-c 备注/注释 -c "张三 研发部"
-e 账号过期日期 -e 2027-12-31
-r 创建系统用户 -r -s /sbin/nologin appsvc
-M 不创建主目录 -M guest1

有几个默认行为的坑要记住:useradd 默认不创建主目录,必须加 -m(Debian 系里 adduser 会默认创建,但 useradd 不会);默认 shell 可能是 /bin/sh,需要交互环境就得显式 -s /bin/bash;默认不设密码,账号建出来后是锁定状态,必须单独设密码或设密钥。

三、批量创建:从名单文件出发

最常见的形式是给一个用户名单文件,每行一个用户,批量创建并统一设初始密码。

先准备名单文件 users.csv:

# 用户名,主组,附加组,备注
alice,developers,docker,研发部-张三
bob,developers,docker,研发部-李四
carol,ops,sudo,运维部-王五
dave,test,,测试部-赵六
#!/usr/bin/env bash
# 批量创建用户脚本
set -uo pipefail

USERS_FILE="${1:-users.csv}"
DEFAULT_SHELL="/bin/bash"
UID_START=1500
LOG="/var/log/user_batch_add.log"

[ -r "$USERS_FILE" ] || { echo "名单文件不存在: $USERS_FILE"; exit 1; }

# 计算当前最大 uid,确定起始点
CUR_UID=$(getent passwd | awk -F: '$3>=1000 && $3<60000 {print $3}' | sort -n | tail -1)
UID_START=$(( ${CUR_UID:-1000} + 1 ))

echo "[$(date '+%F %T')] 开始批量建用户,起始 uid=$UID_START" | tee -a "$LOG"

while IFS=',' read -r uname pgroup sgroup comment; do
  # 跳过空行与注释
  [ -z "$uname" ] && continue
  case "$uname" in \#*) continue;; esac

  # 已存在则跳过
  if id "$uname" >/dev/null 2>&1; then
    echo "已存在,跳过: $uname" | tee -a "$LOG"
    continue
  fi

  # 主组不存在则先建
  getent group "$pgroup" >/dev/null || groupadd "$pgroup"

  # 组装附加组参数
  G_OPT=""
  [ -n "$sgroup" ] && G_OPT="-G $sgroup"

  useradd -m -s "$DEFAULT_SHELL" -u "$UID_START" \
          -g "$pgroup" $G_OPT -c "$comment" "$uname"

  if [ $? -eq 0 ]; then
    # 生成随机初始密码并强制首次登录修改
    PASS=$(tr -dc 'A-Za-z0-9@#%' 

这段脚本里几个细节值得说明。uid 动态分配:先算出当前最大 uid 再往上加,避免固定写死 1500 撞号。主组自动创建:getent group 判断组是否存在,不存在先 groupadd,否则 useradd 会失败。chage -d 0:把密码最后修改日期设为 0,效果是用户下次登录必须改密码,这是发放初始密码的标准做法。幂等:已存在的用户直接跳过,脚本可以重复跑而不出错,这对自动化很重要。

初始密码用 /dev/urandom 随机生成,比手写"123456"安全得多。但要注意脚本日志里打印了明文密码,这个日志文件的权限必须收紧。

# 保护含初始密码的日志
sudo chmod 600 /var/log/user_batch_add.log
sudo chown root:root /var/log/user_batch_add.log

四、批量授予 sudo 权限

建完用户,下一步常是授权。授权有两种粒度:全量 sudo(能执行任何命令)和受限 sudo(只能执行指定命令)。能用受限的就别用全量,这是最小权限原则。

# 方式一:把用户加入 wheel 组(RHEL系)或 sudo 组(Debian/Ubuntu)
# RHEL/CentOS/Rocky 的 sudoers 里 wheel 组默认有全部权限
usermod -aG wheel alice
# Debian/Ubuntu 的 sudo 组默认有全部权限
usermod -aG sudo alice

# 方式二:用 sudoers.d 精细授权(推荐,不动主文件)
# 给 ops 组的人只开放服务管理和日志查看
sudo tee /etc/sudoers.d/ops-limited >/dev/null <<'EOF'
%ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, \
     /usr/bin/systemctl status *, /usr/bin/journalctl -u nginx*
EOF
sudo chmod 440 /etc/sudoers.d/ops-limited

改 sudoers 极其危险:一旦语法写错,可能导致无法使用 sudo,严重时连 root 都进不去。务必用 visudo 或 visudo -c 校验,并且永远不要直接编辑 /etc/sudoers 主文件,而是往 /etc/sudoers.d/ 放独立文件。

# 校验语法(必须做)
sudo visudo -c
# 校验特定文件
sudo visudo -cf /etc/sudoers.d/ops-limited

NOPASSWD 会让该命令免密执行,方便自动化但降低安全性。只在确实需要脚本化调用的场景使用,普通人工操作建议保留密码验证。

五、账号有效期与离职回收

账号管理不只"建",还要"收"。给临时账号设到期时间,能避免被人遗忘的"僵尸账号"成为安全隐患。

# 建号时设过期(-e 日期 或 -e 天数)
useradd -m -s /bin/bash -e 2027-12-31 temp01     # 到 2027-12-31 失效

# 给已有账号设置到期
chage -E 2027-12-31 temp01
# 查看账号密码/过期信息
chage -l temp01

# 密码有效期策略(经验值:90 天强制改密)
chage -M 90 -m 1 -W 7 alice
# -M 最长有效期  -m 最短使用期  -W 过期前提醒天数

到期后账号会失效,无法登录,但文件和数据还在,需要时再处理。这比"用密码锁住"更明确。

离职或临时任务结束时回收账号,要先禁用、再观察、最后删除,不要一上来就删:

# 第一步:锁定账号(禁止登录,但保留文件和进程归属)
sudo usermod -L alice          # 锁定密码
sudo usermod -s /sbin/nologin alice   # 禁止交互登录
sudo pkill -u alice            # 踢掉该用户所有进程(谨慎!会中断其运行的服务)

# 第二步:观察一段时间(比如 30 天),确认没有业务依赖

# 第三步:确认无误后再删除
sudo userdel -r alice          # -r 连主目录和邮件一并删除
# 批量回收:从名单文件批量禁用
while read -r u; do
  id "$u" >/dev/null 2>&1 && { usermod -L "$u"; usermod -s /sbin/nologin "$u"; echo "已禁用 $u"; }
done < retire_list.txt

六、风险提示与回滚

删除用户是不可逆操作。userdel -r 会删掉该用户的主目录,如果目录里有业务数据(比如某个服务以该用户身份运行、数据放在其主目录下),删了就没了。删除前务必:先 ls -la /home/alice 和 find / -user alice 看看有没有重要文件;先做备份或用 usermod -L 禁用观察一段时间。

uid/gid 冲突会让文件属主显示成数字。如果发现某目录的属主变成一串数字(如 1005),说明对应的 uid 已被删除,需要 chown 修正属主。

改 sudoers 导致 sudo 失效时,如果还能用 root 直接登录(或通过单用户模式/恢复控制台),用 visudo 修正即可;如果连 root 都受限,需要进入救援模式挂载根分区修复。这是为什么容器和云主机都建议保留一个能用控制台登录的 root 或运维账号。

回滚方式:批量创建脚本的产物是系统账号,回滚即按上面的流程禁用或删除对应用户;名单文件和审计日志要保留,便于核对。脚本本身放在 /usr/local/bin/ 并留一份 .bak 备份。

常见问题

Q1:useradd 建完用户登录不了,密码也没设,为什么?
useradd 默认不设密码,账号是锁定状态;同时它默认也不创建主目录、shell 可能是 /bin/sh。要能正常登录,通常需要 -m -s /bin/bash 加 chpasswd 设密码。用 passwd -S 用户名 可以查看账号密码状态(L 表示锁定,P 表示已设密码)。

Q2:怎么让新用户第一次登录必须改密码?
用 chage -d 0 用户名,把"密码最后修改日期"设为 0,系统会判定密码已过期,下次登录强制修改。这是发初始密码的标准做法。

Q3:给用户加了组,为什么权限没生效?
用户组变更需要重新登录才生效,因为组信息在登录时写入会话。临时想生效可用 newgrp 组名 切到新组,或让用户重新登录。脚本里对已登录用户的权限变更要注意这一点。

Q4:userdel 和 userdel -r 有什么区别?
userdel 只删用户记录,主目录保留;userdel -r 连主目录和邮件一起删。默认建议先用不带 -r 的,确认主目录里没有要保留的数据后再手动清理,或者直接保留目录只删账号。

Q5:批量脚本如何保证安全、不出错?
三条:一是幂等,已存在的用户跳过,可重复执行;二是先禁用、观察、再删除,给"反悔"留时间;三是所有操作写日志,日志文件权限收紧到 600。校验收尾用 pwck 检查用户数据库一致性。

总结

  • 批量建用户的核心是"名单驱动 + 幂等脚本",用 uid 动态分配避免撞号,已存在则跳过。
  • useradd 默认不建主目录、不设密码、shell 可能不是 bash,脚本里要显式指定 -m -s /bin/bash。
  • 初始密码用随机生成并配合 chage -d 0 强制首登改密;含密码的日志必须限制权限。
  • 授权遵循最小权限原则,优先用 /etc/sudoers.d/ 受限授权;改 sudoers 必须 visudo -c 校验,切勿直接编辑主文件。
  • 账号要能"建"也要能"收":用 chage -E 设有效期,离职先禁用观察再删除,删除前核查数据与文件归属。