usb_wipe.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #!/usr/bin/env bash
  2. #
  3. # =============================================================================
  4. # U 盘杀毒 / 清理脚本
  5. # 交互式 U 盘彻底抹除与重建分区
  6. # =============================================================================
  7. #
  8. # 用途:
  9. # - 彻底清除 U 盘 / 移动硬盘上的所有数据(包括病毒、恶意软件、隐藏分区)
  10. # - 擦除分区表签名(MBR + 备用 GPT)和首尾关键区域
  11. # - 新建 MBR 分区表 + 单分区,并格式化为 FAT32 / exFAT / NTFS / ext4
  12. #
  13. # 用法:
  14. # sudo ./usb_wipe.sh
  15. #
  16. # 交互步骤:
  17. # 1. 脚本自动扫描并列出所有可移动磁盘设备
  18. # 2. 按编号选择要清理的目标设备
  19. # 3. 输入设备型号进行第一次确认
  20. # 4. 输入大写 YES 进行最终确认(不可逆!)
  21. # 5. 选择文件系统类型(FAT32 / exFAT / NTFS / ext4 / 跳过格式化)
  22. # 6. 可选:输入卷标(回车使用默认值 USB_DRIVE)
  23. #
  24. # 依赖工具:
  25. # util-linux : lsblk, fdisk, sfdisk, wipefs, blockdev, partprobe
  26. # coreutils : dd, sync
  27. # dosfstools : mkfs.vfat (FAT32)
  28. # exfatprogs : mkfs.exfat (exFAT)
  29. # ntfs-3g : mkfs.ntfs (NTFS)
  30. # e2fsprogs : mkfs.ext4 (ext4)
  31. #
  32. # 安装依赖:sudo pacman -S dosfstools exfatprogs ntfs-3g e2fsprogs
  33. # (Debian/Ubuntu:sudo apt install dosfstools exfatprogs ntfs-3g e2fsprogs)
  34. #
  35. # 安全设计:
  36. # - 必须 root 运行
  37. # - 三层确认机制(编号选择 → 型号匹配 → 大写 YES)
  38. # - 自动检测并卸载目标设备上的挂载点
  39. # - 默认过滤系统盘(nvme / sda 非 USB 设备)
  40. # - 仅擦除分区表签名和首尾关键区域(不浪费生命全盘写零)
  41. #
  42. # ⚠️ 警告:本脚本会 永久销毁 目标设备上的所有数据,不可恢复!
  43. # 使用前请再三确认已选中正确的设备。
  44. #
  45. # =============================================================================
  46. set -euo pipefail
  47. # --- 颜色定义 ----------------------------------------------------------------
  48. RED='\033[0;31m'
  49. GREEN='\033[0;32m'
  50. YELLOW='\033[1;33m'
  51. CYAN='\033[0;36m'
  52. BOLD='\033[1m'
  53. NC='\033[0m' # No Color
  54. # --- 必须 root 运行 ----------------------------------------------------------
  55. if [[ $EUID -ne 0 ]]; then
  56. echo -e "${RED}${BOLD}请使用 sudo 运行本脚本。${NC}"
  57. exit 1
  58. fi
  59. # --- 横幅 --------------------------------------------------------------------
  60. clear
  61. echo -e "${RED}${BOLD}"
  62. echo " ╔══════════════════════════════════════════════════════════╗"
  63. echo " ║ ⚠ 警 告 — 不可逆操作 — 请谨慎使用 ⚠ ║"
  64. echo " ║ ║"
  65. echo " ║ 本脚本将 彻底抹除 U 盘全部内容 并重建分区表和文件系统 ║"
  66. echo " ║ 所有数据将 永久丢失,无法恢复! ║"
  67. echo " ╚══════════════════════════════════════════════════════════╝"
  68. echo -e "${NC}"
  69. echo ""
  70. # --- 工具检查 ----------------------------------------------------------------
  71. for cmd in lsblk dd fdisk mkfs.vfat mkfs.exfat mkfs.ext4 wipefs blockdev; do
  72. if ! command -v "$cmd" &>/dev/null; then
  73. echo -e "${YELLOW}缺少工具: $cmd,尝试安装...${NC}"
  74. pacman -S --noconfirm --needed \
  75. util-linux fdisk dosfstools exfatprogs e2fsprogs coreutils 2>/dev/null || true
  76. break
  77. fi
  78. done
  79. # --- 步骤 1:列出可移动设备 --------------------------------------------------
  80. echo -e "${CYAN}[1/5]${NC} 正在扫描可移动磁盘设备..."
  81. echo ""
  82. # 用 lsblk 列出可移动设备(TRAN=RPM 为空或 usb,TYPE=disk)
  83. mapfile -t USB_DISKS < <(
  84. lsblk -dno NAME,SIZE,TRAN,MODEL,VENDOR 2>/dev/null \
  85. | awk '
  86. BEGIN { ignore["sda"]=1; ignore["nvme"]=1; ignore["loop"]=1 }
  87. /^sd[b-z]/ || /^vd[b-z]/ || ($3 == "usb") {
  88. print
  89. }
  90. ' \
  91. | sort -u
  92. )
  93. if [[ ${#USB_DISKS[@]} -eq 0 ]]; then
  94. echo -e "${RED}未检测到任何可移动磁盘设备。${NC}"
  95. echo ""
  96. echo "当前所有块设备:"
  97. lsblk -dno NAME,SIZE,TRAN,MODEL 2>/dev/null | head -20
  98. exit 1
  99. fi
  100. echo -e "${BOLD}检测到以下可移动磁盘:${NC}"
  101. echo ""
  102. printf " %-4s %-10s %-8s %-s\n" "编号" "设备名" "容量" "型号"
  103. echo " ────────────────────────────────────────"
  104. for i in "${!USB_DISKS[@]}"; do
  105. read -r name size tran model vendor <<< "${USB_DISKS[$i]}"
  106. printf " ${GREEN}%-4s${NC} ${RED}%-10s${NC} %-8s %-s %-s\n" \
  107. "$((i+1))" "/dev/$name" "$size" "$model" "$vendor"
  108. done
  109. echo ""
  110. # --- 步骤 2:选择目标设备 ----------------------------------------------------
  111. while true; do
  112. read -r -p "$(echo -e "${YELLOW}请选择要清理的设备编号(输入 0 退出):${NC}")" choice
  113. if [[ "$choice" == "0" ]]; then
  114. echo "已取消。"
  115. exit 0
  116. fi
  117. if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#USB_DISKS[@]} )); then
  118. idx=$((choice - 1))
  119. read -r DEV_NAME DEV_SIZE DEV_TRAN DEV_MODEL DEV_VENDOR <<< "${USB_DISKS[$idx]}"
  120. DEV_PATH="/dev/$DEV_NAME"
  121. break
  122. fi
  123. echo -e "${RED}无效选择,请重新输入。${NC}"
  124. done
  125. echo ""
  126. # --- 步骤 3:二次确认(输入设备型号) -----------------------------------------
  127. echo -e "${RED}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  128. echo -e "${RED}${BOLD} 最终确认:即将销毁 ${DEV_PATH}${NC}"
  129. echo -e "${RED}${BOLD} 设备型号:${DEV_MODEL:-未知} ${DEV_VENDOR:-}${NC}"
  130. echo -e "${RED}${BOLD} 容量:${DEV_SIZE}${NC}"
  131. echo -e "${RED}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  132. echo ""
  133. # 显示设备当前分区信息
  134. echo -e "${CYAN}当前分区布局:${NC}"
  135. lsblk "$DEV_PATH" 2>/dev/null || true
  136. fdisk -l "$DEV_PATH" 2>/dev/null | head -20 || true
  137. echo ""
  138. # 检查是否有挂载
  139. MOUNTS=$(lsblk -no MOUNTPOINT "$DEV_PATH" 2>/dev/null | grep -v '^$' || true)
  140. if [[ -n "$MOUNTS" ]]; then
  141. echo -e "${YELLOW}检测到挂载点:${NC}"
  142. echo "$MOUNTS"
  143. echo ""
  144. read -r -p "$(echo -e "${YELLOW}是否卸载所有挂载点?[y/N] ${NC}")" umount_confirm
  145. if [[ "$umount_confirm" =~ ^[Yy]$ ]]; then
  146. for mp in $MOUNTS; do
  147. umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
  148. done
  149. echo -e "${GREEN}已卸载。${NC}"
  150. fi
  151. fi
  152. echo ""
  153. echo -e "${RED}${BOLD}请输入设备型号 ${DEV_MODEL:-N/A} 来确认操作:${NC}"
  154. read -r confirm_model
  155. if [[ "$confirm_model" != "${DEV_MODEL}" ]]; then
  156. echo -e "${GREEN}型号不匹配,操作已取消。${NC}"
  157. exit 0
  158. fi
  159. echo ""
  160. echo -e "${RED}${BOLD}最后一次确认:输入 YES(大写)来执行不可逆的抹除操作:${NC}"
  161. read -r final_confirm
  162. if [[ "$final_confirm" != "YES" ]]; then
  163. echo -e "${GREEN}操作已取消。${NC}"
  164. exit 0
  165. fi
  166. echo ""
  167. echo -e "${RED}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  168. echo -e "${RED}${BOLD} 开始执行... 请勿中断!${NC}"
  169. echo -e "${RED}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  170. echo ""
  171. # --- 步骤 4:抹除 ------------------------------------------------------------
  172. echo -e "${CYAN}[2/5]${NC} 清除分区表签名(wipefs)..."
  173. wipefs --all --force "$DEV_PATH" 2>/dev/null || true
  174. echo -e "${GREEN} ✓ 完成${NC}"
  175. echo -e "${CYAN}[3/5]${NC} dd 全盘置零(这可能需要几分钟)..."
  176. BLOCK_SIZE=$(blockdev --getsize64 "$DEV_PATH" 2>/dev/null || echo 0)
  177. SECTOR_SIZE=$(blockdev --getss "$DEV_PATH" 2>/dev/null || echo 512)
  178. # 先写开头 10 MiB(MBR/GPT 区域)
  179. dd if=/dev/zero of="$DEV_PATH" bs=1M count=10 conv=fsync status=none 2>/dev/null && \
  180. echo -e "${GREEN} ✓ 前 10 MiB 已清零${NC}" || \
  181. echo -e "${RED} ✗ 写入失败${NC}"
  182. # 再写末尾 10 MiB(备用 GPT 区域)
  183. if [[ "$BLOCK_SIZE" -gt 0 ]]; then
  184. SEEK_VAL=$(( (BLOCK_SIZE - 10*1024*1024) / 512 ))
  185. if [[ "$SEEK_VAL" -gt 0 ]]; then
  186. dd if=/dev/zero of="$DEV_PATH" bs=512 seek="$SEEK_VAL" count=20480 conv=fsync status=none 2>/dev/null && \
  187. echo -e "${GREEN} ✓ 末尾 10 MiB 已清零${NC}" || \
  188. echo -e "${RED} ✗ 末尾写入失败${NC}"
  189. fi
  190. fi
  191. echo -e "${CYAN}[4/5]${NC} 同步并刷新内核分区表..."
  192. sync
  193. partprobe "$DEV_PATH" 2>/dev/null || blockdev --rereadpt "$DEV_PATH" 2>/dev/null || true
  194. sleep 1
  195. echo -e "${GREEN} ✓ 完成${NC}"
  196. # --- 步骤 5:选择文件系统并重建分区 ------------------------------------------
  197. echo ""
  198. echo -e "${CYAN}[5/5]${NC} 选择要创建的文件系统类型:"
  199. echo ""
  200. echo " 1) FAT32 — 兼容性最好(单文件最大 4 GiB)"
  201. echo " 2) exFAT — 兼容性好,支持大文件(推荐)"
  202. echo " 3) NTFS — Windows 默认"
  203. echo " 4) ext4 — Linux 专用"
  204. echo " 5) 跳过格式化"
  205. echo ""
  206. FSTYPE_CHOICE=""
  207. while true; do
  208. read -r -p "$(echo -e "${YELLOW}请选择 [1-5]:${NC}")" fstype
  209. case "$fstype" in
  210. 1) FSTYPE="vfat"; FSTYPE_LABEL="FAT32"; break ;;
  211. 2) FSTYPE="exfat"; FSTYPE_LABEL="exFAT"; break ;;
  212. 3) FSTYPE="ntfs"; FSTYPE_LABEL="NTFS"; break ;;
  213. 4) FSTYPE="ext4"; FSTYPE_LABEL="ext4"; break ;;
  214. 5) FSTYPE=""; break ;;
  215. *) echo -e "${RED}无效选择。${NC}" ;;
  216. esac
  217. done
  218. if [[ -n "$FSTYPE" ]]; then
  219. read -r -p "$(echo -e "${YELLOW}卷标(回车默认 USB_DRIVE):${NC}")" VOLUME_LABEL
  220. VOLUME_LABEL="${VOLUME_LABEL:-USB_DRIVE}"
  221. echo ""
  222. echo -e "${CYAN}创建 MBR 分区表 + 单分区...${NC}"
  223. # 使用 sfdisk 创建单个 Linux 分区(类型稍后根据 fs 调整)
  224. echo 'label: dos' | sfdisk --wipe always "$DEV_PATH" 2>/dev/null
  225. sleep 1
  226. # 创建分区(从 2048 扇区开始,使用全部剩余空间)
  227. echo '2048,,83' | sfdisk --wipe always "$DEV_PATH" 2>/dev/null
  228. sleep 1
  229. partprobe "$DEV_PATH" 2>/dev/null || true
  230. sleep 1
  231. # 获取分区名
  232. PART_PATH="${DEV_PATH}1"
  233. if [[ "$DEV_NAME" == nvme* ]] || [[ "$DEV_NAME" == mmcblk* ]]; then
  234. PART_PATH="${DEV_PATH}p1"
  235. fi
  236. # 等待分区节点出现
  237. for _ in {1..10}; do
  238. if [[ -b "$PART_PATH" ]]; then
  239. break
  240. fi
  241. sleep 0.5
  242. done
  243. if [[ ! -b "$PART_PATH" ]]; then
  244. echo -e "${RED}分区设备 $PART_PATH 未出现,请手动检查。${NC}"
  245. exit 1
  246. fi
  247. echo -e "${GREEN} ✓ 分区已创建:${PART_PATH}${NC}"
  248. # 格式化
  249. echo -e "${CYAN}格式化 ${PART_PATH} 为 ${FSTYPE_LABEL}...${NC}"
  250. case "$FSTYPE" in
  251. vfat)
  252. mkfs.vfat -F 32 -n "${VOLUME_LABEL}" "$PART_PATH"
  253. ;;
  254. exfat)
  255. mkfs.exfat -n "${VOLUME_LABEL}" "$PART_PATH"
  256. ;;
  257. ntfs)
  258. mkfs.ntfs -f -L "${VOLUME_LABEL}" "$PART_PATH" 2>/dev/null || \
  259. mkfs.ntfs -Q -L "${VOLUME_LABEL}" "$PART_PATH"
  260. ;;
  261. ext4)
  262. mkfs.ext4 -F -L "${VOLUME_LABEL}" "$PART_PATH"
  263. ;;
  264. esac
  265. echo -e "${GREEN} ✓ 格式化完成${NC}"
  266. # 刷新分区类型标识
  267. case "$FSTYPE" in
  268. vfat) echo 'type=0c' | sfdisk --part-type "$DEV_PATH" 1 2>/dev/null || true ;;
  269. exfat) echo 'type=07' | sfdisk --part-type "$DEV_PATH" 1 2>/dev/null || true ;;
  270. ntfs) echo 'type=07' | sfdisk --part-type "$DEV_PATH" 1 2>/dev/null || true ;;
  271. esac
  272. else
  273. PART_PATH="$DEV_PATH"
  274. echo -e "${YELLOW}跳过格式化。${NC}"
  275. fi
  276. sync
  277. partprobe "$DEV_PATH" 2>/dev/null || true
  278. # --- 完成 --------------------------------------------------------------------
  279. echo ""
  280. echo -e "${GREEN}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  281. echo -e "${GREEN}${BOLD} ✓ 操作完成!${NC}"
  282. echo -e "${GREEN}${BOLD}══════════════════════════════════════════════════════════════${NC}"
  283. echo ""
  284. echo -e " 设备:${BOLD}${DEV_PATH}${NC}"
  285. if [[ -n "$FSTYPE" ]]; then
  286. echo -e " 分区:${BOLD}${PART_PATH}${NC}"
  287. echo -e " 文件系统:${BOLD}${FSTYPE_LABEL}${NC}"
  288. echo -e " 卷标:${BOLD}${VOLUME_LABEL}${NC}"
  289. fi
  290. echo ""
  291. echo -e " 最终布局:"
  292. lsblk "$DEV_PATH" 2>/dev/null || true
  293. echo ""
  294. echo -e "${CYAN}可以拔下 U 盘了。${NC}"