自动化用户与用户组配置
使用 useradd、chage 和 sudoers 片段管理批量创建、修改和审计账户。
自动化用户与用户组配置 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
为什么要自动化用户配置?
使用 useradd 一次管理一个用户,对于少量账户来说完全可行;但在企业环境中,通常需要同时为数十或数百名用户完成入职配置。手动执行命令容易出错、缺乏一致性,也无法进行审计。
Bash 脚本可以帮助您:
- 每次都使用标准化设置(Shell、主目录、密码策略)创建用户
- 读取包含新员工信息的 CSV 或文本文件,并在一次运行中完成配置
- 记录每项操作,从而为合规性提供审计轨迹
- 与配置管理流水线(Ansible、Chef、Jenkins)集成
本课将从头开始讲解如何构建生产级用户配置脚本,内容涵盖 useradd、chage、usermod、组管理、sudoers 插入文件以及运行后的审计。
读取批量用户列表
批量配置所采用的规范输入格式是带分隔符的文本文件——每行一条记录。典型的 CSV 可能如下所示:
username,full_name,group,shell
alice,Alice Smith,developers,/bin/bash
bob,Bob Jones,ops,/bin/zsh请在 while 循环中使用 IFS 和 read 安全地解析该文件。使用 tail -n +2 跳过标题行,可以使逻辑更加清晰。
关键的防御性实践:
- 去除每个字段开头和结尾的空白字符
- 跳过空行以及以
#开头的注释行 - 在调用任何系统命令之前,验证必填字段不为空
#!/usr/bin/env bash
# parse_users.sh — safely read a CSV of users
set -euo pipefail
USER_FILE="${1:-users.csv}"
[[ -f "$USER_FILE" ]] || { echo "ERROR: $USER_FILE not found"; exit 1; }
tail -n +2 "$USER_FILE" | while IFS=',' read -r username full_name group shell; do
# trim whitespace
username="${username// /}"
[[ -z "$username" || "$username" == \#* ]] && continue
echo "Parsed -> user=$username group=$group shell=$shell"
done使用 useradd 创建用户
useradd 是底层工具,会写入 /etc/passwd、/etc/shadow 和 /etc/group。用于编写脚本的最重要选项包括:
-m— 创建主目录-s— 设置登录 Shell-c— GECOS 注释字段(全名)-G— 附加组(以逗号分隔)-e— 账户过期日期(YYYY-MM-DD)
调用 useradd 之前,请始终先使用 id 检查用户是否已存在;对已有用户运行该命令会返回退出代码 9,并打印错误信息,从而可能使日志变得杂乱。
注意: useradd 需要 root 权限。请在脚本开头加入权限检查。
#!/usr/bin/env bash
# create_user.sh — idempotent single-user creation
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
FULL_NAME="${2:-}"
GROUP="${3:-staff}"
SHELL="${4:-/bin/bash}"
if id "$USERNAME" &>/dev/null; then
echo "[SKIP] User $USERNAME already exists"
else
useradd \
--create-home \
--shell "$SHELL" \
--comment "$FULL_NAME" \
--groups "$GROUP" \
"$USERNAME"
echo "[OK] Created $USERNAME"
fi安全地设置初始密码
绝不要在脚本中硬编码密码。批量配置时,有两种安全方法:
- 生成随机初始密码:使用
openssl rand或/dev/urandom生成密码,显示一次,并强制用户在首次登录时修改密码 - 设置预先哈希的密码:使用
usermod -p配置 SHA-512 哈希值,使明文密码不会出现在进程列表中
chpasswd 是脚本化设置密码的推荐工具——它从标准输入读取 username:password 对,因此密码不会出现在命令行参数中(不会通过 ps 显示)。
设置密码后,请使用 chage -d 0 强制用户在下次登录时立即重置密码。
#!/usr/bin/env bash
# set_temp_password.sh
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
# Generate a 16-char random password (alphanumeric only)
TMP_PASS=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)
# Set password via chpasswd (password never in argv)
echo "${USERNAME}:${TMP_PASS}" | chpasswd
# Force password change on next login
chage -d 0 "$USERNAME"
echo "[OK] Temporary password for $USERNAME: $TMP_PASS"
echo "[OK] User must change password on first login"使用 chage 管理密码有效期
chage(change age)用于控制存储在 /etc/shadow 中的密码有效期策略。企业安全策略通常要求:
- 密码最长有效期(例如 90 天)
- 密码再次修改前必须经过的最短天数
- 过期前的警告期限
- 自上次使用密码后,账户因不活动而锁定的时间
chage 的关键选项:
-M <days>— 密码最长有效期-m <days>— 密码最短有效期-W <days>— 过期前的警告天数-I <days>— 账户锁定前的不活动天数-E <date>— 账户的绝对过期日期-l— 列出用户当前设置
#!/usr/bin/env bash
# apply_password_policy.sh — enforce org-wide ageing policy
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
# Policy constants
MAX_AGE=90
MIN_AGE=1
WARN_DAYS=14
INACTIVE_DAYS=30
apply_policy() {
local user="$1"
chage \
-M "$MAX_AGE" \
-m "$MIN_AGE" \
-W "$WARN_DAYS" \
-I "$INACTIVE_DAYS" \
"$user"
echo "[OK] Policy applied to $user"
}
# Apply to all non-system users (UID >= 1000)
awk -F: '$3 >= 1000 && $3 < 65534 { print $1 }' /etc/passwd | while read -r user; do
apply_policy "$user"
done批量管理组
组是控制资源访问权限的主要机制。配置脚本必须确保所需的组在将用户加入其中之前已经存在——useradd -G nonexistent 会失败。
请通过检查退出代码来幂等地使用 groupadd:如果组已经存在,它会返回 9。使用 getent group <name> 是一种可移植且易读的替代方式,无需 grep /etc/group。
gpasswd -a 会将用户添加到组中,而不会替换现有成员关系(不同于会替换附加组列表的 usermod -G)。
#!/usr/bin/env bash
# ensure_groups.sh — create groups if missing, then add users
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
REQUIRED_GROUPS=(developers ops security auditors)
for grp in "${REQUIRED_GROUPS[@]}"; do
if getent group "$grp" &>/dev/null; then
echo "[SKIP] Group $grp already exists"
else
groupadd "$grp"
echo "[OK] Created group $grp"
fi
done
# Safely add a user to a group (append, don't replace)
add_to_group() {
local user="$1" group="$2"
gpasswd -a "$user" "$group" 2>/dev/null && echo "[OK] $user -> $group"
}完整的批量配置脚本
将所有内容整合起来:一个脚本读取 CSV,创建用户和组,设置密码策略,记录每项操作,并以平稳的方式处理错误,而不会停止整个批次。
下面脚本中的重要设计决策:
- 带时间戳的
LOG_FILE会记录所有操作,供审计使用 - 单个用户的错误会被记录,但不会中止循环(
|| log_error) - 脚本具有幂等性——部分失败后可以安全地重新运行
- 所有输出都会通过
tee同时发送到终端和日志文件
#!/usr/bin/env bash
# bulk_provision.sh — production user provisioning
set -uo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USER_FILE="${1:-users.csv}"
LOG_FILE="/var/log/user_provision_$(date +%F).log"
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"; }
err() { log "ERROR: $*"; }
log "=== Provisioning started from $USER_FILE ==="
tail -n +2 "$USER_FILE" | while IFS=',' read -r username fullname group shell; do
username="${username// /}"
[[ -z "$username" || "$username" == \#* ]] && continue
group="${group:-staff}"
shell="${shell:-/bin/bash}"
# Ensure group exists
getent group "$group" &>/dev/null || groupadd "$group"
# Create user idempotently
if id "$username" &>/dev/null; then
log "[SKIP] $username exists"
else
useradd -m -s "$shell" -c "$fullname" -G "$group" "$username" || { err "useradd failed for $username"; continue; }
TMP="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 14)"
echo "${username}:${TMP}" | chpasswd
chage -M 90 -m 1 -W 14 -I 30 -d 0 "$username"
log "[OK] $username created (group=$group) tmp_pass=$TMP"
fi
done
log "=== Provisioning complete ==="sudoers 插入片段管理
直接编辑 /etc/sudoers 很危险——语法错误会使所有人无法使用 sudo。安全的方法是使用 插入文件,将它们放在 /etc/sudoers.d/ 中,并在放置到目标位置之前,使用 visudo -c -f 验证每个文件。
sudoers 片段的最佳实践:
- 按照授予权限的团队或角色命名文件(例如
10-developers、20-ops) - 使用基于组的规则(
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl),而不是针对单个用户的行 - 始终将权限设置为
0440,所有者设置为root:root - 使用
visudo -c验证——出现任何语法错误时,它都会以非零状态退出
#!/usr/bin/env bash
# write_sudoers_fragment.sh
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
FRAGMENT_NAME="${1:-10-developers}"
SUDOERS_DIR="/etc/sudoers.d"
TMP_FILE="$(mktemp)"
# Write the fragment to a temp file first
cat > "$TMP_FILE" << 'EOF'
# Developers: restart services and view journals without full root
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/journalctl
%ops ALL=(ALL) NOPASSWD: ALL
EOF
# Validate BEFORE installing
if visudo -c -f "$TMP_FILE"; then
install -m 0440 -o root -g root "$TMP_FILE" "${SUDOERS_DIR}/${FRAGMENT_NAME}"
echo "[OK] Installed ${SUDOERS_DIR}/${FRAGMENT_NAME}"
else
echo "[ERROR] sudoers syntax check failed — fragment NOT installed"
rm -f "$TMP_FILE"
exit 1
fi
rm -f "$TMP_FILE"审计现有账户
完成配置后(以及按照定期计划),您应检查用户数据库中是否存在异常:
- UID 0 账户——除 root 之外,任何 UID 为 0 的账户都是严重的安全问题
- 无密码账户——
/etc/shadow中密码字段为空或为!的条目 - 已过期但仍处于活动状态的账户——可以批量解析
chage -l的输出 - 有 Shell 但没有主目录的用户——会导致登录失败的配置错误
使用 mail 将结构化报告发送给安全团队,或将报告追加到受监控的日志路径中,都很简单。
#!/usr/bin/env bash
# audit_users.sh — produce a security-relevant user report
set -uo pipefail
REPORT="/var/log/user_audit_$(date +%F).txt"
echo "=== User Audit Report $(date) ===" > "$REPORT"
echo "" >> "$REPORT"
echo "--- Accounts with UID 0 (should be root only) ---" >> "$REPORT"
awk -F: '$3 == 0 { print $1 }' /etc/passwd >> "$REPORT"
echo "" >> "$REPORT"
echo "--- Accounts with empty password field ---" >> "$REPORT"
awk -F: '($2 == "" || $2 == "!") && $3 >= 1000 { print $1 }' /etc/shadow 2>/dev/null >> "$REPORT" || echo " (requires root)" >> "$REPORT"
echo "" >> "$REPORT"
echo "--- Normal users (UID 1000-60000) ---" >> "$REPORT"
awk -F: '$3 >= 1000 && $3 < 60000 { printf "%-20s uid=%-6s shell=%s\n", $1, $3, $7 }' /etc/passwd >> "$REPORT"
cat "$REPORT"
echo "Report saved to $REPORT"锁定、解锁和删除账户
离职处理与入职配置同样重要。当用户离开时,正确的顺序是:
- 立即锁定账户(
usermod -L)——在 shadow 密码哈希前添加!,阻止登录但不删除数据 - 撤销 sudo 权限——如果存在其 sudoers 片段,则将其删除
- 转移所有权——将其文件的所有权转给主管或归档账户
- 归档——在删除之前,将主目录打包为 tar 文件
- 删除——使用
userdel -r,移除主目录和邮件队列
usermod -U 可以解锁账户(移除 ! 前缀),适用于临时停用的场景。
#!/usr/bin/env bash
# offboard_user.sh — lock, archive, then optionally delete
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
ARCHIVE_DIR="/srv/archived-homes"
mkdir -p "$ARCHIVE_DIR"
# 1. Lock account
usermod -L "$USERNAME"
echo "[OK] Account $USERNAME locked"
# 2. Remove sudoers fragment if present
SUDOERS_FILE="/etc/sudoers.d/${USERNAME}"
[[ -f "$SUDOERS_FILE" ]] && rm -f "$SUDOERS_FILE" && echo "[OK] Removed sudoers fragment"
# 3. Archive home directory
HOME_DIR="$(getent passwd "$USERNAME" | cut -d: -f6)"
if [[ -d "$HOME_DIR" ]]; then
tar -czf "${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz" -C "$(dirname "$HOME_DIR")" "$(basename "$HOME_DIR")"
echo "[OK] Home archived to ${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz"
fi
echo "[NOTICE] Review archive, then run: userdel -r $USERNAME"使用试运行模式测试脚本
生产环境中的配置脚本必须能够在不产生副作用的情况下进行测试。请使用 DRY_RUN 标志实现试运行模式,将所有会修改系统的命令替换为 echo 占位命令。
其模式很简单:定义一个 run() 辅助函数,根据标志决定是执行命令还是输出命令。这样做意味着:
- 测试期间会执行每条代码路径
- 输出会准确显示实际运行时将会发生什么
- 持续集成流水线无需 root 权限即可验证逻辑
请将试运行模式与专用的测试用户前缀结合使用(例如 test_),这样便于在集成测试后进行清理。
#!/usr/bin/env bash
# provision_with_dryrun.sh
set -euo pipefail
DRY_RUN="${DRY_RUN:-false}"
# Wrapper: execute or echo
run() {
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
create_user() {
local user="$1" group="$2"
if id "$user" &>/dev/null; then
echo "[SKIP] $user exists"
return
fi
run useradd -m -s /bin/bash -G "$group" "$user"
run chage -M 90 -m 1 -W 14 -d 0 "$user"
echo "[OK] $user provisioned (dry=$DRY_RUN)"
}
# Test run
DRY_RUN=true create_user testuser developers
echo "---"
create_user realuser developers 2>/dev/null || true在不删除现有组成员关系的情况下将用户添加到附加组,应使用哪个命令?
在批量配置脚本中,您需要将现有用户加入 auditors 组。该用户已经是 developers 组和 staff 组的成员。哪个命令可以在添加新组的同时保留所有现有成员关系?
课程回顾:自动化用户和组配置
在本课中,您构建了一套完整的生产级用户配置工具。以下是要点:
- 防御性解析输入——使用
IFS/read循环,跳过空行和注释行,并在进行任何系统调用前验证字段 useradd的要点——始终使用-m(主目录)、-s(Shell)、-c(注释)和-G(组);先用id检查用户是否存在,以确保幂等性- 密码——通过
chpasswd的标准输入设置密码,使明文密码不会出现在进程参数中;使用chage -d 0强制用户在首次登录时重置密码 - 使用
chage配置策略——在所有非系统账户上统一设置最长有效期(-M)、警告天数(-W)和不活动锁定(-I) - 组成员关系——使用
gpasswd -a或usermod -aG(带有-a选项)追加成员关系,而不是替换成员关系 - sudoers 插入文件——写入
/etc/sudoers.d/,安装前使用visudo -c -f验证,并设置0440 root:root权限 - 离职处理——锁定(
usermod -L)、归档主目录,然后删除;绝不要跳过归档步骤 - 试运行模式——将会修改系统的命令封装在
run()辅助函数中,使流水线能够在不产生 root 权限副作用的情况下验证逻辑
结合这些模式,您就能为任意规模的 Linux 身份管理提供可重复、可审计且安全的自动化层。
常见问题解答
「自动化用户与用户组配置」课时是免费的吗?
是的 — 「自动化用户与用户组配置」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
「自动化用户与用户组配置」这节课中我会学到什么?
使用 useradd、chage 和 sudoers 片段管理批量创建、修改和审计账户。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「自动化用户与用户组配置」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?
能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。