Linux 成绩管理系统(Shell)
纯 Shell 脚本实现的成绩管理系统,多用户数据隔离、灵活查询、完善注释。
- 多用户登录(教师/学生)
- 成绩增删改查 + 模糊搜索
- CSV 批量导入成绩
- 统计计算(平均分/最高低分/及格率)
- 文本文件持久化 + 数据导出
Shell 成绩管理系统
与本课程完整项目的关系:本目录是 Shell 脚本实现的成绩管理系统(作业五)。该系统的 Flask Web 迁移版(作业六)在相同权限模型和数据逻辑基础上,将 CLI 交互替换为浏览器界面,并使用 SQLite 替代 CSV 文件存储。
项目概述
基于 Bash 的全功能成绩管理系统,实现学生成绩的增删改查、排序统计、三级权限控制和多维度报表导出。数据存储于 CSV 文件,通过 Shell 脚本完成所有操作,是一次将 Shell 编程能力推向工程化边界的尝试。
架构设计
系统采用函数式模块化设计,主流程为:数据初始化 -> 班级配置加载 -> 用户认证 -> 主菜单循环。核心数据流通过 get_data_lines() 函数实现用户数据隔离,admin 看到全部数据,teacher 看到管辖班级,student 看到本人记录。
功能模块分为四个层级:基础 CRUD(添加/删除/修改/查询学生)、排序(按科目/总分/平均分)、统计报表(分布/平均分/GPA/挂科名单)、加分模块(班级维度统计/学院汇总/用户管理/报表导出)。每个子功能对应独立的函数,通过子菜单组织调用。
技术亮点
三级权限与数据隔离:用户系统支持 admin/teacher/student 三种角色,通过 SESSION_ROLE 和 SESSION_SCOPE 变量控制数据可见性和操作权限。teacher 只能操作管辖班级的学生,student 只能查询自己,admin 拥有全部权限。get_data_lines() 函数根据角色动态过滤数据行。
纯 Shell 的数值计算方案:由于 Bash 不支持浮点运算,系统封装了 awk_add/awk_mul/awk_div 等工具函数,所有数值计算委托给 awk 完成。grade_to_num() 函数将优秀/良好/中等/及格/不及格等中文等级映射为数值分数,支持混合评分体系。
批量录入功能:支持一次输入多行"学号 成绩"数据批量写入 CSV,学号支持短号自动补全(后 4 位自动加前缀 20181828),教师视角自动验证管辖班级权限。这是期末登分场景下最实用的功能。
设计决策
选择 CSV 文件存储而非数据库,因为作业要求纯 Shell 实现,且 CSV 的文本格式便于 sed/awk 直接操作。class_info.txt 作为班级-学院-系的映射配置文件,比硬编码更灵活。报表导出到独立的 report/ 目录,每次导出带时间戳防止覆盖。
关键代码解读
get_data_lines() {
case "$SESSION_ROLE" in
admin) echo "$all_data" ;;
teacher) # 只返回管辖班级的学生行 ;;
student) grep "^$SESSION_SCOPE," ;;
esac
}
该函数是整个权限系统的枢纽。所有查询、排序、统计操作都通过 get_data_lines 获取数据,而非直接读取 CSV 文件,确保数据隔离在源头实现而非分散在各函数中。这种"一次过滤、多处使用"的设计在 Shell 脚本中尤为简洁高效。
bash主程序框架
Shell 脚本主循环,包括用户认证、菜单导航、成绩管理和统计功能。
#!/bin/bash
# =============================================
# Linux 课程作业五 — 成绩管理系统
# 作者: 童国睿
# 功能: 多用户成绩管理(增删改查+统计)
# =============================================
DATA_DIR="./data"
USERS_FILE="$DATA_DIR/users.txt"
GRADES_FILE="$DATA_DIR/grades.txt"
# ── 初始化 ──
init() {
mkdir -p "$DATA_DIR"
[ -f "$USERS_FILE" ] || echo "admin|admin123|teacher" > "$USERS_FILE"
[ -f "$GRADES_FILE" ] || touch "$GRADES_FILE"
}
# ── 登录 ──
login() {
read -p "用户名: " username
read -s -p "密码: " password
echo
user=$(grep "^$username|$password|" "$USERS_FILE")
if [ -z "$user" ]; then
echo "❌ 用户名或密码错误"
return 1
fi
role=$(echo "$user" | cut -d'|' -f3)
echo "✅ 登录成功![$role] $username"
ROLE="$role"
USERNAME="$username"
}
# ── 显示所有成绩 ──
list_grades() {
printf "%-12s %-15s %-10s %-6s\n" "学号" "姓名" "课程" "成绩"
printf "%-12s %-15s %-10s %-6s\n" "────" "────" "────" "────"
while IFS='|' read -r sid name course score; do
[ -n "$sid" ] && printf "%-12s %-15s %-10s %-6s\n" "$sid" "$name" "$course" "$score"
done < "$GRADES_FILE"
echo ""
echo "总记录数: $(wc -l < "$GRADES_FILE")"
}
# ── 添加成绩 ──
add_grade() {
read -p "学号: " sid
read -p "姓名: " name
read -p "课程: " course
read -p "成绩: " score
# 校验成绩范围
if ! [[ "$score" =~ ^[0-9]+$ ]] || [ "$score" -lt 0 ] || [ "$score" -gt 100 ]; then
echo "❌ 成绩必须在 0-100 之间"
return
fi
echo "$sid|$name|$course|$score" >> "$GRADES_FILE"
echo "✅ 成绩已添加"
}
# ── 模糊搜索 ──
search_grade() {
read -p "输入学号或姓名(支持模糊): " keyword
results=$(grep -i "$keyword" "$GRADES_FILE")
if [ -z "$results" ]; then
echo "未找到匹配记录"
return
fi
echo "$results" | while IFS='|' read -r sid name course score; do
printf "%-12s %-15s %-10s %-6s\n" "$sid" "$name" "$course" "$score"
done
echo "共找到 $(echo "$results" | wc -l) 条记录"
}
# ── 统计 ──
show_stats() {
echo "===== 成绩统计 ====="
echo "总人数: $(wc -l < "$GRADES_FILE")"
awk -F'|' '{
sum+=$4; count++;
if($4>max||count==1) max=$4;
if($4<min||count==1) min=$4;
if($4>=60) pass++
} END {
printf "平均分: %.1f\n", sum/count
printf "最高分: %d\n", max
printf "最低分: %d\n", min
printf "及格率: %.1f%%\n", pass/count*100
}' "$GRADES_FILE"
}
# ── CSV 批量导入 ──
import_csv() {
read -p "CSV 文件路径: " csv_path
if [ ! -f "$csv_path" ]; then
echo "❌ 文件不存在"
return
fi
count=0
tail -n +2 "$csv_path" | while IFS=',' read -r sid name course score; do
echo "$sid|$name|$course|$score" >> "$GRADES_FILE"
((count++))
done
echo "✅ 已导入 $count 条记录"
}
# ── 主菜单 ──
main_menu() {
while true; do
echo ""
echo "===== 成绩管理系统 ====="
[ "$ROLE" = "teacher" ] && echo "1) 添加成绩 2) 查看成绩"
[ "$ROLE" = "teacher" ] && echo "3) 搜索成绩 4) 删除成绩"
[ "$ROLE" = "teacher" ] && echo "5) 批量导入 6) 统计"
echo "7) 退出"
read -p "选择: " choice
case $choice in
1) [ "$ROLE" = "teacher" ] && add_grade ;;
2) [ "$ROLE" = "teacher" ] && list_grades ;;
3) [ "$ROLE" = "teacher" ] && search_grade ;;
4) [ "$ROLE" = "teacher" ] && delete_grade ;;
5) [ "$ROLE" = "teacher" ] && import_csv ;;
6) [ "$ROLE" = "teacher" ] && show_stats ;;
7) exit 0 ;;
*) echo "无效选择" ;;
esac
done
}
# ── 启动 ──
init
login && main_menu| 路径 | 说明 | 行数 |
|---|---|---|
grade_manager.sh | Shell 成绩管理系统完整脚本 | 180 |