auto_mount.sh — 跨发行版数据盘自动挂载脚本
auto_mount.sh — 跨发行版数据盘自动挂载脚本
一、简介
auto_mount.sh 用于在 Linux 服务器上自动完成数据盘的分区、格式化、挂载、写入开机自动挂载全流程。它能自动识别新加的空闲数据盘并跳过系统盘,也支持手动指定单块盘,适合云主机扩容、批量初始化、新机器交付等场景。
兼容系统
| 发行版 | 版本 | 包管理器 | 默认文件系统 |
|---|---|---|---|
| CentOS | 7 | yum | xfs |
| CentOS | 8 | dnf | xfs |
| openEuler | 22.03 / 24.04 | dnf | xfs |
| Ubuntu | 22.04 / 24.04 | apt | ext4* |
*Ubuntu 系统盘多用 ext4,但脚本默认仍以
xfs格式化数据盘,可用-t ext4切换。两种文件系统在上述所有系统都受支持。
核心特性
- 自动发现空闲盘:识别未挂载、无分区、不在
fstab中的整块盘,自动排除系统盘。 - 跨发行版依赖自愈:缺
parted/xfsprogs/e2fsprogs时按系统自动安装。 - 设备命名兼容:同时正确处理
/dev/sdb1与/dev/nvme0n1p1。 - 按 UUID 写 fstab:避免设备名漂移导致开机挂载失败;带
nofail,盘异常也不会卡住启动。 - 安全优先:默认交互确认、操作前占用检查、改
fstab前自动备份、mount -a校验、系统盘保护。 - dry-run 预演:
-n只打印将要执行的动作,不动磁盘。 - 全程日志:写入
/var/log/auto_mount_disk.log。
二、使用说明
前提
- 以 root(或
sudo)运行。 - 目标盘上没有需要保留的数据——分区+格式化会清空整块盘。
命令行选项
| 选项 | 说明 |
|---|---|
-d <dev> |
指定磁盘设备,如 /dev/sdb。省略则自动发现所有空闲数据盘。 |
-m <path> |
挂载点,如 /data。配合 -d 使用。 |
-p <prefix> |
自动发现模式下的挂载点前缀,自动编号。默认 /data → /data1、/data2。 |
-t <fstype> |
文件系统类型:xfs(默认)或 ext4。 |
-r |
remount 模式,把已挂载的盘改挂到新目录、保留数据(不格式化)。需配合 -d 和 -m。 |
-f |
强制模式,跳过交互确认。危险,确保盘内无数据。 |
-n |
dry-run,只打印将要执行的操作,不做任何实际更改。 |
-h |
显示帮助。 |
常用示例
# 1. 预演:看自动发现会选中哪些盘、打算如何处理(不动磁盘)
sudo ./scripts/auto_mount.sh -n
# 2. 自动发现并挂载所有空闲盘 -> /data1 /data2 ...(交互确认)
sudo ./scripts/auto_mount.sh
# 3. 指定单盘 + 挂载点
sudo ./scripts/auto_mount.sh -d /dev/sdb -m /data
# 4. NVMe 盘 + ext4 + 免确认
sudo ./scripts/auto_mount.sh -d /dev/nvme0n1 -m /data -t ext4 -f
# 5. 自动发现,挂载点前缀改为 /mnt/disk -> /mnt/disk1 /mnt/disk2
sudo ./scripts/auto_mount.sh -p /mnt/disk
# 6. remount: 把已挂载在 /A 的 /dev/sdb 改挂到 /B, 保留数据(先 -n 预演)
sudo ./scripts/auto_mount.sh -r -d /dev/sdb -m /B -n
sudo ./scripts/auto_mount.sh -r -d /dev/sdb -m /B
切换挂载点(remount)
如果一块盘已经挂载并有数据,想把它从 A 目录改挂到 B 目录而不丢数据,用 -r 模式:
sudo ./scripts/auto_mount.sh -r -d /dev/sdb -m /newpath
它做的是非破坏性搬迁,不会 wipefs/格式化:
- 定位该盘当前承载数据的分区及其旧挂载点(自动处理
/dev/sdb下的sdb1,或整盘直挂的情况)。 umount旧挂载点(若被进程占用会报错并中止,提示用lsof/fuser排查)。- 创建新挂载点,按 UUID 更新
/etc/fstab(保留盘原有的文件系统类型),改前自动备份。 mount -a重新挂载并校验,旧目录卸载后保留为空目录。
支持 -n 预演。若新旧挂载点相同则直接跳过。
推荐操作流程
- 先
-n预演,确认选盘和挂载点正确。 - 去掉
-n,交互模式执行(需输入大写YES确认)。 - 用
df -hT或lsblk验证挂载结果。 - 可选:
reboot后确认开机自动挂载生效。
退出码
0:成功,或无空闲盘可处理,或用户取消。1:出错(非 root、不支持的文件系统、设备无效、占用冲突、挂载失败等)。
三、代码说明
执行流程图
脚本有两条主分支:格式化挂载(破坏性)和 remount(非破坏性)。
1. 格式化挂载模式(默认,-r 未设置)
2. remount 模式(-r 设置)
模块详解
脚本为单文件 Bash,开启 set -euo pipefail(遇错即停、未定义变量报错、管道错误传播)。主要模块如下:
1. 参数与前置检查
getopts 解析选项;检查 root 权限、文件系统类型合法性;初始化日志文件(不可写时降级到 /dev/null)。
2. 日志与 run 包装器
log/warn/err/die 同时输出到终端(带颜色)和日志文件。run 是 dry-run 的核心——DRY_RUN=1 时只打印命令字符串,否则真正执行。所有破坏性命令都经 run 调用。
3. 依赖自愈 ensure_deps / detect_pkg_mgr
按 dnf > yum > apt 优先级探测包管理器,对缺失命令映射到对应包名并安装。dry-run 下只提示不安装。
4. 系统盘识别 get_system_disks
通过 findmnt 找到 /、/boot、/boot/efi 的来源设备,再用 lsblk -no pkname 回溯到底层物理盘(正确处理 LVM、分区等层级),得到需要排除的系统盘列表。
5. 空闲盘发现 discover_data_disks
遍历 lsblk 所有 TYPE=disk 的盘,按以下条件逐一排除:是系统盘、整盘已挂载、已有子设备(分区/LVM/RAID)、已出现在 fstab 中。剩余即为可用空闲数据盘。
6. 占用检查 check_disk_free
处理前再次确认目标盘未被挂载、未被 LVM 占用、未用作 swap,任一命中即终止。
7. 单盘处理 process_disk
核心流程:wipefs 清签名 → parted 建 GPT 分区表和单一主分区 → partprobe+udevadm settle 刷新 → 循环探测分区设备(最多 10 次,应对内核异步生成延迟)→ mkfs 格式化 → blkid 取 UUID → 备份并去重写入 /etc/fstab → mount -a 校验 → mountpoint 确认挂载成功。part_name 负责 sdb1 与 nvme0n1p1 两种命名。
8. 主流程 main
确定目标盘集合(指定盘走系统盘保护分支,否则自动发现)→ 展示待处理盘和容量 → 交互确认(dry-run 跳过)→ 逐盘处理并按需自动编号挂载点 → 输出最终 lsblk 布局。-r 时则提前进入 remount 分支,要求 -d/-m 齐全。
9. 切换挂载点 remount_disk
非破坏性流程:解析盘当前承载数据的分区和旧挂载点 → 取 UUID 和真实文件系统类型 → umount 旧点 → 建新点 → 备份并按 UUID 更新 fstab → mount -a 校验。全程不碰 wipefs/mkfs,数据原样保留;新旧挂载点相同时直接跳过。
关键安全设计
- UUID + nofail:设备名变化不影响挂载;盘缺失也不阻塞开机。
- fstab 备份:每次写入前
cp一份带时间戳的备份,便于回滚。 - 写入去重:按挂载点和 UUID 清理旧记录,重复运行不会产生重复条目(幂等)。
- 系统盘保护:指定盘时,将其底层物理盘与
get_system_disks回溯出的系统盘列表比对,命中(承载/或/boot)即拒绝操作,无论是否强制模式。
四、后续提升方向
按价值和实现成本,可分阶段增强:
易做、收益高
--label文件系统卷标:格式化时打标签,便于识别用途。- 可配置挂载选项:当前固定
defaults,nofail,可增加-o自定义(如noatime、discard)。 - 日志轮转:日志文件无上限,可接入
logrotate或自带按大小截断。
中等成本
- LVM 模式:可选用 PV/VG/LV 替代裸分区,支持后续在线扩容、跨盘聚合。
- 多分区 / 容量比例切分:支持把一块盘切成多个分区按比例分配。
- 配置文件驱动:支持从 YAML/INI 读取"设备→挂载点→文件系统"映射,实现声明式批量初始化。
- 幂等性增强:检测到盘已按预期格式化挂载时直接跳过(当前对已用盘是排除而非"对账")。
较高成本 / 工程化
btrfs/zfs等更多文件系统支持。- 软 RAID(mdadm)编排:多盘做 RAID0/1/5 后再格式化挂载。
- 加密盘(LUKS)支持:格式化前先建加密层。
- 可观测性:执行结果上报(退出码、挂载清单)到监控系统,或输出 JSON 供自动化流水线消费。
- 测试覆盖:用 loop device(
losetup+ 稀疏文件)在 CI 里模拟磁盘,对各发行版做端到端验证。
五、注意事项
- 分区+格式化不可逆,务必先用
-n预演确认选盘。 - 自动发现依赖"无分区表"判断空闲盘;若新盘上已有残留分区表,会被视为"已使用"而跳过,此时需手动
-d指定。 - 强制模式(
-f)会跳过所有确认,仅建议在确知无数据的自动化场景使用。 - 改动记录在
/var/log/auto_mount_disk.log,/etc/fstab的备份为/etc/fstab.bak.<时间戳>。
六、完整脚本
与
scripts/auto_mount.sh一致。保存后chmod +x auto_mount.sh即可运行。
#!/usr/bin/env bash
#
# auto_mount.sh - 自动识别并挂载数据盘 (跨发行版)
#
# 兼容: CentOS 7 / CentOS 8 / openEuler 22.03 / openEuler 24.04 / Ubuntu 22.04 / Ubuntu 24.04
#
# 功能:
# 1. 指定单盘, 或自动发现未挂载/未分区/不在 fstab 中的空闲数据盘 (自动跳过系统盘)
# 2. wipefs 清残留签名 -> 创建 GPT 分区表 -> 单一主分区
# 3. 格式化为指定文件系统 (默认 xfs, 可选 ext4)
# 4. 按 UUID 写入 /etc/fstab (带 nofail), 备份原 fstab, mount -a 校验
# 5. LVM / swap / 已挂载 占用检查, 操作全程写日志
# 6. remount 模式: 把已挂载的盘原样改挂到新目录, 保留数据 (不格式化)
#
# 用法:
# ./auto_mount.sh # 交互, 自动发现所有空闲数据盘
# ./auto_mount.sh -d /dev/sdb -m /data # 指定单盘和挂载点
# ./auto_mount.sh -d /dev/nvme0n1 -t ext4 -f # NVMe + ext4 + 强制(免确认)
# ./auto_mount.sh -p /mnt/disk # 自动发现, 多盘自动编号 disk1/disk2..
# ./auto_mount.sh -r -d /dev/sdb -m /B # 把 /dev/sdb 从原挂载点改挂到 /B (保留数据)
#
# 选项:
# -d <dev> 指定磁盘设备 (省略则自动发现所有空闲数据盘)
# -m <path> 挂载点 (配合 -d 使用)
# -p <prefix> 自动发现模式的挂载点前缀, 自动编号 (默认 /data -> /data1 /data2)
# -t <fstype> 文件系统类型: xfs(默认) 或 ext4
# -r remount 模式, 把已挂载的盘改挂到新目录, 保留数据 (需配合 -d -m)
# -f 强制模式, 跳过交互确认 (危险, 确保盘内无数据)
# -n dry-run, 只打印将要执行的操作, 不做任何实际更改
# -h 显示帮助
set -euo pipefail
# ---------- 默认参数 ----------
FSTYPE="xfs"
MOUNT_PREFIX="/data"
TARGET_DEV=""
TARGET_MOUNT=""
FORCE_MODE=0
DRY_RUN=0
REMOUNT_MODE=0
LOG_FILE="/var/log/auto_mount_disk.log"
# ---------- 颜色 / 日志 ----------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'
log() { echo -e "${GREEN}[INFO]${NC} $*" | tee -a "$LOG_FILE"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_FILE"; }
err() { echo -e "${RED}[ERROR]${NC} $*" | tee -a "$LOG_FILE" >&2; }
die() { err "$*"; exit 1; }
# dry-run 下打印命令而不执行; 否则执行并记录
run() {
if [ "$DRY_RUN" -eq 1 ]; then
echo -e "${YELLOW}[DRY-RUN]${NC} $*" | tee -a "$LOG_FILE"
else
"$@"
fi
}
usage() { sed -n '2,45p' "$0" | sed 's/^#//; s/^ //'; exit 0; }
# ---------- 解析参数 ----------
while getopts "d:m:p:t:rfnh" opt; do
case "$opt" in
d) TARGET_DEV="$OPTARG" ;;
m) TARGET_MOUNT="${OPTARG%/}" ;;
p) MOUNT_PREFIX="${OPTARG%/}" ;;
t) FSTYPE="$OPTARG" ;;
r) REMOUNT_MODE=1 ;;
f) FORCE_MODE=1 ;;
n) DRY_RUN=1 ;;
h) usage ;;
*) usage ;;
esac
done
# ---------- 前置检查 ----------
[ "$(id -u)" -eq 0 ] || die "请以 root 运行 (sudo $0 ...)"
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/dev/null"
echo "===== $(date '+%F %T') 开始 =====" >> "$LOG_FILE"
case "$FSTYPE" in
xfs|ext4) ;;
*) die "不支持的文件系统类型: $FSTYPE (仅支持 xfs / ext4)" ;;
esac
# ---------- 依赖处理 ----------
# detect_pkg_mgr - 探测当前系统的包管理器
# 输出: "dnf" / "yum" / "apt" / 空字符串(无法识别)
detect_pkg_mgr() {
if command -v dnf >/dev/null 2>&1; then echo "dnf" # CentOS 8 / openEuler
elif command -v yum >/dev/null 2>&1; then echo "yum" # CentOS 7
elif command -v apt-get >/dev/null 2>&1; then echo "apt" # Ubuntu
else echo ""; fi
}
# ensure_deps - 检查并自动安装所需依赖包
# 根据所选文件系统 ($FSTYPE) 决定需要 xfsprogs 或 e2fsprogs
# dry-run 模式下只打印不安装
ensure_deps() {
local need=()
# 分区工具
command -v parted >/dev/null 2>&1 || need+=("parted")
command -v partprobe >/dev/null 2>&1 || need+=("parted")
# 设备管理工具
command -v wipefs >/dev/null 2>&1 || need+=("util-linux")
command -v lsblk >/dev/null 2>&1 || need+=("util-linux")
# 文件系统工具(按选定类型)
if [ "$FSTYPE" = "xfs" ]; then
command -v mkfs.xfs >/dev/null 2>&1 || need+=("xfsprogs")
else
command -v mkfs.ext4 >/dev/null 2>&1 || need+=("e2fsprogs")
fi
# 去重并检查是否有缺失
local uniq
uniq=$(printf "%s\n" "${need[@]:-}" | sort -u | grep -v '^$' || true)
[ -z "$uniq" ] && return 0 # 全部已安装
local pm; pm=$(detect_pkg_mgr)
if [ "$DRY_RUN" -eq 1 ]; then
warn "[DRY-RUN] 缺少依赖, 将通过 $pm 安装: $(echo $uniq | tr '\n' ' ')"
return 0
fi
# 实际安装
warn "缺少依赖, 尝试安装: $(echo $uniq | tr '\n' ' ')"
case "$pm" in
dnf) dnf install -y $uniq ;;
yum) yum install -y $uniq ;;
apt) apt-get update -y && apt-get install -y $uniq ;;
*) die "无法识别包管理器, 请手动安装: $uniq" ;;
esac
}
# ---------- 系统盘识别 (排除根/boot 所在物理盘) ----------
# get_system_disks - 回溯 /、/boot、/boot/efi 所在的底层物理盘
# 输出: 物理盘名列表(每行一个, 如 sda、nvme0n1), 用于自动发现时排除
# 正确处理 LVM、分区等多层级结构
get_system_disks() {
local sysdisks="" src pk base mp
# 遍历根和启动分区
while read -r mp; do
src=$(findmnt -no SOURCE --target "$mp" 2>/dev/null || true) # 设备源
[ -z "$src" ] && continue
# lsblk pkname 回溯到父级物理盘
pk=$(lsblk -no pkname "$src" 2>/dev/null | head -n1 || true)
if [ -n "$pk" ]; then
sysdisks="$sysdisks $pk" # 有父级盘, 记录父级
else
base=$(basename "$src"); sysdisks="$sysdisks $base" # 已是顶层, 直接记录
fi
done < <(findmnt -rno TARGET | grep -E '^(/|/boot|/boot/efi)$' || true)
echo "$sysdisks" | tr ' ' '\n' | sort -u | grep -v '^$' || true
}
# ---------- 自动发现空闲数据盘 ----------
# discover_data_disks - 列出所有可用于初始化的空闲数据盘
# 输出: 设备路径列表(每行一个, 如 /dev/sdb), 已排除:
# - 系统盘(承载 / /boot /boot/efi)
# - 整盘已挂载
# - 已有分区/LVM/RAID 子设备
# - 已出现在 /etc/fstab 中
discover_data_disks() {
local sysdisks; sysdisks=$(get_system_disks) # 先取系统盘清单
local found=() name type mountpoint children
while read -r name type mountpoint; do
[ "$type" = "disk" ] || continue # 只看整块盘(TYPE=disk)
echo "$sysdisks" | grep -qx "$name" && continue # 排除系统盘
[ -n "$mountpoint" ] && continue # 排除整盘已挂载
# 统计子设备数(分区/LVM/RAID), 有则说明已使用
children=$(lsblk -rno NAME "/dev/$name" | tail -n +2 | wc -l)
[ "$children" -ne 0 ] && continue
# 检查 fstab 是否提及该设备(粗略匹配设备名+数字/p 或空白)
grep -qE "/dev/$name([0-9p]|[[:space:]])" /etc/fstab 2>/dev/null && continue
found+=("/dev/$name") # 通过所有筛选, 记录为可用空闲盘
done < <(lsblk -rno NAME,TYPE,MOUNTPOINT)
printf "%s\n" "${found[@]:-}"
}
# ---------- 占用检查 ----------
# check_disk_free - 检查目标盘是否被挂载/LVM/swap 占用
# 参数: $1 设备路径(如 /dev/sdb)
# 有占用则 die 终止, 无占用则静默返回
check_disk_free() {
local dev="$1"
local mp
# 检查盘或其子设备是否已挂载
mp=$(lsblk -no MOUNTPOINT "$dev" 2>/dev/null | grep -v '^$' | head -1 || true)
[ -n "$mp" ] && die "$dev 已有分区挂载在 $mp, 请先卸载"
# 检查 LVM 占用(PV)
if command -v lvs >/dev/null 2>&1 && lvs 2>/dev/null | grep -q "$(basename "$dev")"; then
die "$dev 被 LVM 使用, 请先处理"
fi
# 检查 swap 占用
if swapon --show 2>/dev/null | grep -q "$dev"; then
die "$dev 被用作 swap, 请先 swapoff"
fi
}
# ---------- 分区设备名 (/dev/sdb -> sdb1, /dev/nvme0n1 -> nvme0n1p1) ----------
# part_name - 根据磁盘名推算第一分区的设备名
# 参数: $1 磁盘设备路径
# 输出: 分区设备路径(NVMe 类设备后缀 p1, 其他设备后缀 1)
part_name() {
local dev="$1"
if [[ "$dev" =~ [0-9]$ ]]; then echo "${dev}p1"; else echo "${dev}1"; fi
}
# ---------- 处理单盘(格式化并挂载) ----------
# process_disk - 对空闲数据盘执行: wipefs -> 分区 -> 格式化 -> 挂载 -> 写 fstab
# 参数: $1 磁盘设备, $2 挂载点
# 破坏性操作: 清空整块盘数据
# dry-run 模式下只打印不执行
process_disk() {
local dev="$1" mount="$2"
[ -b "$dev" ] || die "$dev 不是有效的块设备"
check_disk_free "$dev" # 占用检查
log "处理磁盘 $dev -> 挂载点 $mount (文件系统 $FSTYPE)"
# === 1. 分区 ===
run wipefs -a "$dev" # 清残留签名(旧文件系统/分区表)
run parted -s "$dev" mklabel gpt # 创建 GPT 分区表
run parted -s "$dev" mkpart primary 0% 100% # 单一主分区占满整盘
run partprobe "$dev" # 通知内核重新读取分区表
[ "$DRY_RUN" -eq 0 ] && udevadm settle 2>/dev/null || true # 等待 udev 规则完成
# === 2. 循环探测分区设备(应对内核异步生成延迟) ===
local part; part=$(part_name "$dev")
if [ "$DRY_RUN" -eq 1 ]; then
log "(dry-run) 预期分区设备: $part"
log "(dry-run) 将格式化 $part 为 $FSTYPE, 写入 /etc/fstab(UUID), 挂载到 $mount"
return 0
fi
local retry=0
while [ ! -b "$part" ] && [ "$retry" -lt 10 ]; do
sleep 1; retry=$((retry+1)); partprobe "$dev" 2>/dev/null || true
done
[ -b "$part" ] || die "分区 $part 未生成, 请检查磁盘"
log "分区创建完成: $part"
# === 3. 格式化 ===
log "格式化 $part 为 $FSTYPE ..."
if [ "$FSTYPE" = "xfs" ]; then mkfs.xfs -f "$part"; else mkfs.ext4 -F "$part"; fi
# === 4. 获取 UUID(用于 fstab 写入, 避免设备名漂移) ===
local uuid; uuid=$(blkid -s UUID -o value "$part")
[ -n "$uuid" ] || die "无法获取 $part 的 UUID"
# === 5. 创建挂载点(非空则告警, 挂载后原内容被遮蔽) ===
if [ -d "$mount" ] && [ -n "$(ls -A "$mount" 2>/dev/null)" ]; then
warn "挂载点 $mount 非空, 挂载后原内容将被遮蔽"
fi
mkdir -p "$mount"
# === 6. 写入 fstab(备份 + 按 UUID 和挂载点去重 + 追加新行, 带 nofail 防开机卡死) ===
cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d%H%M%S)"
sed -i "\#[[:space:]]${mount}[[:space:]]#d" /etc/fstab # 删同挂载点旧行
sed -i "\#^UUID=${uuid}#d" /etc/fstab # 删同 UUID 旧行
echo "UUID=${uuid} ${mount} ${FSTYPE} defaults,nofail 0 0" >> /etc/fstab
log "已写入 /etc/fstab: UUID=${uuid} -> ${mount}"
# === 7. 挂载并校验 ===
if ! mount -a 2>&1 | tee -a "$LOG_FILE"; then
die "mount -a 执行失败, 请检查 /etc/fstab 语法"
fi
if mountpoint -q "$mount"; then
log "挂载成功:"
df -hT "$mount" | sed 's/^/ /' | tee -a "$LOG_FILE"
else
die "$mount 挂载失败, 请手动排查"
fi
}
confirm_yes() {
[ "$FORCE_MODE" -eq 1 ] && return 0
local ans
read -r -p "$1 请输入 YES 确认: " ans
[ "$ans" = "YES" ]
}
# ---------- remount: 把已挂载的盘改挂到新目录, 保留数据 (不格式化) ----------
# remount_disk - 改变已挂载盘的挂载点, 数据原样保留
# 参数: $1 磁盘设备(如 /dev/sdb, 内部自动定位其承载数据的分区), $2 新挂载点
# 非破坏性: 不做 wipefs/mkfs, 只卸载/改 fstab/重新挂载
# 失败场景: 盘未挂载、卸载失败(进程占用)、新旧挂载点相同(直接跳过)
remount_disk() {
local dev="$1" newmount="$2"
[ -b "$dev" ] || die "$dev 不是有效的块设备"
# === 1. 找到承载数据的实际分区及其当前挂载点 ===
local part oldmount uuid
# 先判断整盘是否直接被挂载(无分区表场景)
if mountpoint -q "$dev" 2>/dev/null || findmnt -rno TARGET -S "$dev" >/dev/null 2>&1; then
part="$dev" # 整盘直挂
else
# 取该盘下已挂载的第一个分区(NR>1 跳过父设备行)
part=$(lsblk -rno NAME,MOUNTPOINT "$dev" | awk 'NR>1 && $2!="" {print $1; exit}')
[ -n "$part" ] && part="/dev/$part"
fi
[ -n "$part" ] || die "$dev 当前没有已挂载的分区, 无需 remount (新盘请用普通挂载模式)"
oldmount=$(findmnt -nro TARGET "$part" | head -1)
[ -n "$oldmount" ] || die "无法确定 $part 的当前挂载点"
uuid=$(blkid -s UUID -o value "$part")
[ -n "$uuid" ] || die "无法获取 $part 的 UUID"
# 保留盘的真实文件系统类型(而非 $FSTYPE 默认值), 写 fstab 时用
local realfs; realfs=$(blkid -s TYPE -o value "$part")
[ -n "$realfs" ] || realfs="$FSTYPE"
# === 2. 新旧挂载点相同则跳过 ===
if [ "$oldmount" = "$newmount" ]; then
log "$part 已挂载在 $newmount, 无需改动"
return 0
fi
log "remount: $part (UUID=$uuid) 从 $oldmount 改挂到 $newmount, 数据保留"
if [ "$DRY_RUN" -eq 1 ]; then
log "(dry-run) umount $oldmount"
log "(dry-run) mkdir -p $newmount"
log "(dry-run) 更新 /etc/fstab: 删除 UUID=$uuid 旧行, 写入 -> $newmount"
log "(dry-run) mount -a 并校验"
return 0
fi
# === 3. 卸载旧挂载点(失败则终止, 提示用 lsof/fuser 排查占用进程) ===
umount "$oldmount" || die "卸载 $oldmount 失败 (可能有进程占用, 可用 lsof/fuser 排查)"
# === 4. 创建新挂载点(非空则告警) ===
if [ -d "$newmount" ] && [ -n "$(ls -A "$newmount" 2>/dev/null)" ]; then
warn "新挂载点 $newmount 非空, 挂载后原内容将被遮蔽"
fi
mkdir -p "$newmount"
# === 5. 更新 fstab(备份 + 按 UUID 和挂载点去重 + 写新行, 保留真实文件系统类型) ===
cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d%H%M%S)"
sed -i "\#^UUID=${uuid}#d" /etc/fstab # 删旧 UUID 行
sed -i "\#[[:space:]]${newmount}[[:space:]]#d" /etc/fstab # 删新挂载点旧行(防重复)
echo "UUID=${uuid} ${newmount} ${realfs} defaults,nofail 0 0" >> /etc/fstab
log "已更新 /etc/fstab: UUID=${uuid} (${realfs}) -> ${newmount}"
# === 6. 挂载并校验 ===
if ! mount -a 2>&1 | tee -a "$LOG_FILE"; then
die "mount -a 执行失败, 请检查 /etc/fstab 语法 (备份可回滚)"
fi
if mountpoint -q "$newmount"; then
log "改挂成功:"
df -hT "$newmount" | sed 's/^/ /' | tee -a "$LOG_FILE"
log "旧挂载点 $oldmount 已卸载 (空目录保留)"
else
die "$newmount 挂载失败, 请手动排查 (/etc/fstab 已备份)"
fi
}
# ---------- 主流程 ----------
# main - 脚本入口, 分支到 remount 或 格式化挂载 两种模式
main() {
# === remount 模式分支: 改挂已有盘到新目录, 保留数据 ===
if [ "$REMOUNT_MODE" -eq 1 ]; then
[ -n "$TARGET_DEV" ] || die "remount 模式需用 -d 指定磁盘设备"
[ -n "$TARGET_MOUNT" ] || die "remount 模式需用 -m 指定新挂载点"
remount_disk "$TARGET_DEV" "$TARGET_MOUNT"
echo
log "完成。最终磁盘布局:"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
return 0
fi
# === 格式化挂载模式 ===
ensure_deps # 安装依赖
# --- 确定目标盘集合(指定单盘 或 自动发现) ---
local -a disks=()
if [ -n "$TARGET_DEV" ]; then
disks=("$TARGET_DEV")
# 系统盘保护: 用真实回溯结果判定, 拒绝操作系统盘
local devbase sysdisks
devbase=$(basename "$TARGET_DEV")
sysdisks=$(get_system_disks)
if echo "$sysdisks" | grep -qx "$devbase"; then
die "$TARGET_DEV 是系统盘 (承载 / 或 /boot), 拒绝操作以防系统损坏"
fi
else
# 自动发现空闲数据盘
log "自动发现空闲数据盘 (跳过系统盘和已使用的盘)..."
mapfile -t disks < <(discover_data_disks)
if [ "${#disks[@]}" -eq 0 ] || [ -z "${disks[0]:-}" ]; then
warn "没有发现可用的空闲数据盘。当前磁盘布局:"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
exit 0
fi
fi
# --- 展示待处理盘, 交互确认(非 dry-run 且非强制模式) ---
echo
if [ "$DRY_RUN" -eq 1 ]; then
warn "[DRY-RUN 模式] 以下磁盘将被【分区 + 格式化】(本次不会真正执行):"
else
warn "即将对以下磁盘执行【分区 + 格式化】, 盘上原有数据将全部丢失:"
fi
for d in "${disks[@]}"; do
echo " $d (容量: $(lsblk -dno SIZE "$d" 2>/dev/null || echo '?'))"
done
echo
if [ "$DRY_RUN" -eq 0 ]; then
confirm_yes "确认继续?" || die "操作已取消, 未做任何更改。"
fi
# --- 逐盘处理: 分区、格式化、挂载、写 fstab ---
local idx=1
for d in "${disks[@]}"; do
local mount
if [ -n "$TARGET_MOUNT" ]; then
mount="$TARGET_MOUNT" # 指定单盘时用指定挂载点
else
mount="${MOUNT_PREFIX}${idx}" # 多盘自动编号(data1 data2 ...)
fi
process_disk "$d" "$mount"
idx=$((idx+1))
done
# --- 输出最终磁盘布局 ---
echo
log "全部完成。最终磁盘布局:"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
}
main
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)