macOS恢复模式终端备份脚本:无依赖、保层级、避冲突的完整方案
文章目录
- macOS恢复模式终端备份脚本:无依赖、保层级、避冲突的完整方案
- 一、恢复模式备份的核心痛点
- 二、核心功能与实现逻辑
- 三、关键技术点与解决方案
- 1. 替代缺失命令:用纯bash实现`basename`功能
- 2. 避免`eval`依赖:用数组传递`tar`排除参数
- 3. 兼容bash 3.2:严格遵循原生语法
- 4. 强化容错性:提前检查+失败处理
- 四、最终完整脚本(可直接预置)
- 五、恢复模式下的实操执行步骤(适配无复制粘贴环境)
- 步骤1:提前预置脚本到外接备份磁盘(正常开机状态下操作)
- 步骤2:恢复模式下挂载磁盘并执行脚本
- 六、脚本常见问题与解决方案
- 问题1:脚本执行报错「/bin/bash^M: bad interpreter: No such file or directory」
- 问题2:报错「command not found」(针对`date`/`tar`/`find`等命令)
- 问题3:打包目录时报错「No space left on device」
- 问题4:报错「Permission denied」(权限不足)
- 问题5:脚本执行无响应,无日志输出
- 七、总结
当Mac因系统故障进入恢复模式时,终端是备份文件的核心工具。但恢复模式下的bash版本(通常为3.2)语法限制多、部分命令缺失(如
basename/eval),同时外接磁盘空间有限、多目录备份易出现同名冲突——更关键的是,恢复模式无图形界面、无法访问网络,无法直接复制粘贴脚本内容,这些问题都让备份变得棘手。本文将从实际踩坑经验出发,一步步构建完全适配恢复模式环境的备份脚本,实现「多目录批量备份+保留路径层级+排除指定目录+无外部命令依赖」的目标,同时提供「提前预置脚本」的实操流程和常见问题排查指南,附最终可直接运行的完整脚本。
一、恢复模式备份的核心痛点
在编写脚本前,需先明确恢复模式下的特殊限制:
- 命令缺失:
basename、eval等常用命令可能不存在,且环境变量不完整,需用绝对路径调用系统命令; - bash版本低:bash 3.2不支持高级语法(如复合条件
|| {}),函数定义/数组操作需严格遵循原生语法; - 空间有限:外接备份磁盘容量不足,需提前检查空间,避免备份中途中断;
- 同名冲突:不同目录下的同名文件/目录易覆盖,需保留原路径层级;
- 无法复制粘贴:恢复模式无网络、无图形界面剪贴板,无法直接从网页复制脚本,需提前将脚本预置到备份磁盘。
二、核心功能与实现逻辑
我们需要实现的备份逻辑:
- 多目录批量处理:支持同时备份多个分散的目录;
- 保留路径层级:每个待备份目录的内容独立存放在备份盘的对应子目录下;
- 自动区分文件/目录:文件直接复制(保留元数据),目录打包为
tar.gz并排除指定目录(如.git/node_modules); - 容错性强化:跳过不存在的目录、清理打包失败的残缺文件、提前检查磁盘空间;
- 适配无复制粘贴环境:支持提前预置脚本到备份磁盘,恢复模式下直接调用执行。
三、关键技术点与解决方案
针对恢复模式的限制,需对脚本做针对性适配:
1. 替代缺失命令:用纯bash实现basename功能
恢复模式中basename命令可能缺失,通过bash字符串截取实现路径最后一级的提取:
get_last_part(){localPATH_STR="$1"PATH_STR=${PATH_STR%/}# 去除末尾斜杠echo"${PATH_STR##*/}"# 截取最后一个斜杠后的内容}2. 避免eval依赖:用数组传递tar排除参数
恢复模式无eval命令,通过数组传递参数替代字符串拼接,直接传递给tar:
get_exclude_array(){localexclude_arr=()forDIRin".git""node_modules";doexclude_arr+=("--exclude=$DIR")doneecho"${exclude_arr[@]}"}# 调用示例localexclude_params=($(get_exclude_array))/usr/bin/tar zcfp"$TAR_FILE_PATH""${exclude_params[@]}""$SOURCE_ITEM"3. 兼容bash 3.2:严格遵循原生语法
- 函数定义需保证
{前有空格,内部语句完整; - 避免复合语法(如
|| {}),改用显式if/else判断; - 系统命令均用绝对路径(如
/bin/date//usr/bin/tar)。
4. 强化容错性:提前检查+失败处理
- 磁盘空间检查:备份前验证可用空间,预留2GB余量;
- 残缺文件清理:打包失败时自动删除不完整的
tar.gz文件; - 目录不存在处理:跳过不存在的待备份目录,不中断整体流程。
四、最终完整脚本(可直接预置)
以下是适配所有恢复模式限制的无依赖备份脚本,已整合所有功能,可直接预置到备份磁盘:
#!/bin/bash# 配置项(已适配实际场景)EXCLUDE_DIRS=(".git""node_modules")BACKUP_TARGET_ROOT="/Volumes/S1/MyBackup"SOURCE_DIRS=("/Volumes/data/projects""/Volumes/soft/repos")# 检查目录存在check_dir_exists(){localDIR="$1"if[!-d"$DIR"];thenecho"错误:目录不存在 -$DIR"return1fireturn0}# 检查磁盘空间check_disk_space(){localDIR="$1"localFREE_SPACE=$(/bin/df -k"$DIR"|/usr/bin/tail -1|/usr/bin/awk'{print $4}')localMIN_SPACE=$((2048*1024))if!/usr/bin/echo"$FREE_SPACE"|/usr/bin/grep -q'^[0-9]*$';thenecho"警告:无法获取磁盘空间,跳过检查"return0fiif["$FREE_SPACE"-lt"$MIN_SPACE"];thenecho"错误:空间不足!可用$((FREE_SPACE/1024))MB,需2GB"exit1fiecho"磁盘空间检查通过,可用$((FREE_SPACE/1024))MB"}# 替代basenameget_last_part(){localPATH_STR="$1"PATH_STR=${PATH_STR%/}echo"${PATH_STR##*/}"}# 构建排除参数数组get_exclude_array(){localexclude_arr=()forDIRin"${EXCLUDE_DIRS[@]}";doexclude_arr+=("--exclude=$DIR")doneecho"${exclude_arr[@]}"}# 处理单个备份项process_source_item(){localSOURCE_ITEM="$1"localPARENT_SOURCE_DIR="$2"localPARENT_DIR_NAME=$(get_last_part"$PARENT_SOURCE_DIR")localITEM_NAME=$(get_last_part"$SOURCE_ITEM")localTIMESTAMP=$(/bin/date +%Y%m%d_%H%M%S)localBACKUP_SUB_DIR="$BACKUP_TARGET_ROOT/$PARENT_DIR_NAME"/bin/mkdir -p"$BACKUP_SUB_DIR"||{echo" 无法创建子目录";return1;}if[-f"$SOURCE_ITEM"];thenecho" 复制文件:$ITEM_NAME"/bin/cp -p"$SOURCE_ITEM""$BACKUP_SUB_DIR/"[$?-eq0]&&echo" 成功:$PARENT_DIR_NAME/$ITEM_NAME"||echo" 失败"elif[-d"$SOURCE_ITEM"];thenlocalTAR_FILE_NAME="${ITEM_NAME}_${TIMESTAMP}.tar.gz"localTAR_FILE_PATH="$BACKUP_SUB_DIR/$TAR_FILE_NAME"localexclude_params=($(get_exclude_array))echo" 打包目录:$ITEM_NAME->$TAR_FILE_NAME"/usr/bin/tar zcfp"$TAR_FILE_PATH""${exclude_params[@]}""$SOURCE_ITEM"if[$?-eq0];thenecho" 成功:$PARENT_DIR_NAME/$TAR_FILE_NAME"else[-f"$TAR_FILE_PATH"]&&/bin/rm -f"$TAR_FILE_PATH"echo" 失败"fielseecho" 跳过不支持的类型:$ITEM_NAME"fi}# 主流程echo"========================================"echo"macOS恢复模式备份脚本(最终版)"echo"执行时间:$(/bin/date"+%Y-%m-%d %H:%M:%S")"echo"备份目标:$BACKUP_TARGET_ROOT"echo"========================================"if!check_dir_exists"$BACKUP_TARGET_ROOT";thenecho"创建备份根目录:$BACKUP_TARGET_ROOT"/bin/mkdir -p"$BACKUP_TARGET_ROOT"||{echo"创建失败";exit1;}ficheck_disk_space"$BACKUP_TARGET_ROOT"localSOURCE_DIRforSOURCE_DIRin"${SOURCE_DIRS[@]}";doecho"----------------------------------------"if!check_dir_exists"$SOURCE_DIR";thenecho"跳过不存在的目录:$SOURCE_DIR"continuefilocalDIR_NAME=$(get_last_part"$SOURCE_DIR")echo"处理目录:$SOURCE_DIR"echo"备份层级:$BACKUP_TARGET_ROOT/$DIR_NAME"echo"----------------------------------------"/usr/bin/find"$SOURCE_DIR"-maxdepth1-mindepth1|whileread-r ITEM;doprocess_source_item"$ITEM""$SOURCE_DIR"donedoneecho"========================================"echo"备份完成!路径:$BACKUP_TARGET_ROOT"echo"========================================"exit0五、恢复模式下的实操执行步骤(适配无复制粘贴环境)
由于macOS恢复模式无网络访问、无图形界面剪贴板,无法直接复制粘贴脚本,需采用「提前预置脚本到备份磁盘」的方案,具体步骤如下:
步骤1:提前预置脚本到外接备份磁盘(正常开机状态下操作)
- 正常启动Mac,将外接备份磁盘(如
S1)连接到电脑,确保磁盘为可写格式(APFS/ExFAT,避免NTFS只读); - 打开「终端」,输入
nano /Volumes/S1/backup_final.sh(直接在备份磁盘根目录创建脚本); - 将上述完整脚本粘贴到nano编辑器中,按需修改
BACKUP_TARGET_ROOT(已默认适配/Volumes/S1/MyBackup); - 按
Ctrl+O保存,Enter确认文件名,Ctrl+X退出nano; - 赋予脚本执行权限:输入
chmod +x /Volumes/S1/backup_final.sh; - 安全弹出外接备份磁盘,备用(确保脚本已成功存储在磁盘中)。
步骤2:恢复模式下挂载磁盘并执行脚本
- 关闭故障Mac,进入恢复模式:
- 搭载Apple芯片(M1/M2等):按住电源键直到出现「加载启动选项」,选择「选项」→「继续」;
- 搭载Intel芯片:开机时按住
Command (⌘) + R直到出现Apple标志;
- 恢复模式启动后,打开「磁盘工具」,找到外接备份磁盘(如
S1),点击右上角「挂载」(若显示「已锁定」,先点击「解锁」并输入磁盘密码); - 关闭「磁盘工具」,打开「实用工具」→「终端」;
- 切换到备份磁盘根目录:输入
cd /Volumes/S1(对应你的备份磁盘名称,可通过ls /Volumes查看所有挂载磁盘); - 验证脚本是否存在:输入
ls,确认能看到backup_final.sh文件; - 执行备份脚本:输入
./backup_final.sh(或绝对路径/Volumes/S1/backup_final.sh,避免路径问题); - 等待脚本执行完毕(大型目录如
docker/k8s打包时间较长),执行完成后终端会提示「备份完成」; - 安全弹出备份磁盘:输入
diskutil unmount /Volumes/S1,即可移除磁盘完成备份。
六、脚本常见问题与解决方案
在实操过程中,容易遇到以下问题,针对性解决方案如下:
问题1:脚本执行报错「/bin/bash^M: bad interpreter: No such file or directory」
- 原因:脚本在Windows编辑器中编辑过,引入了Windows换行符(
^M,即\r),macOS bash无法识别; - 解决方案:在恢复模式终端中清理Windows换行符,命令如下:
# 进入脚本所在目录(如/Volumes/S1)cd/Volumes/S1# 清理换行符并生成新脚本(backup_fixed.sh)tr-d'\r'<backup_final.sh>backup_fixed.sh# 赋予新脚本执行权限chmod+x backup_fixed.sh# 执行新脚本./backup_fixed.sh - 预防措施:仅在macOS终端(nano/vim)或Mac原生文本编辑器(如TextEdit,需切换为「纯文本模式」)中编辑脚本,避免使用Windows编辑器(如Notepad++、Word)。
问题2:报错「command not found」(针对date/tar/find等命令)
- 原因:恢复模式环境变量缺失,直接调用命令无法找到路径,或命令本身未安装(恢复模式下原生命令均存在,多为路径问题);
- 解决方案:使用命令的绝对路径替代直接调用,脚本中已默认适配,手动修改时参考:
date→/bin/datetar→/usr/bin/tarfind→/usr/bin/findcp→/bin/cp
- 验证方法:输入
which 命令名(如which tar),可查看命令的绝对路径。
问题3:打包目录时报错「No space left on device」
- 原因:备份磁盘可用空间不足,无法存储打包后的压缩文件;
- 解决方案:
- 优先分批备份:删除脚本中
SOURCE_DIRS数组里的大型目录(如docker/k8s),先备份小型目录,清理磁盘后再备份大型目录; - 清理备份磁盘:删除磁盘中无用的文件,释放至少2GB以上可用空间;
- 更换大容量备份磁盘:换用更大容量的外接磁盘,重新预置脚本并执行。
- 优先分批备份:删除脚本中
问题4:报错「Permission denied」(权限不足)
- 原因:备份磁盘为只读格式(如NTFS,macOS默认不支持NTFS写入),或待备份目录未解锁(系统盘);
- 解决方案:
- 备份磁盘问题:更换为APFS/ExFAT格式的磁盘(可在正常开机状态下通过「磁盘工具」格式化);
- 系统盘解锁问题:在「磁盘工具」中选中系统盘(如
Macintosh HD),点击「解锁」,输入Mac登录密码,确保系统盘挂载为可写模式。
问题5:脚本执行无响应,无日志输出
- 原因:正在处理大型文件/目录(如
docker镜像),tar打包需要大量时间,恢复模式终端无实时进度条; - 解决方案:
- 耐心等待:不要中断脚本执行,可通过「磁盘工具」查看备份磁盘的空间使用情况(是否在持续占用);
- 验证脚本运行状态:新开一个终端窗口,输入
ps aux | grep backup_final.sh,确认脚本进程是否在运行; - 后续优化:分批备份大型目录,避免单次处理过大文件。
七、总结
本文的备份脚本完全适配macOS恢复模式的bash 3.2环境,解决了命令缺失、语法限制、无复制粘贴等核心痛点,同时提供了详细的「提前预置脚本」实操流程和常见问题排查指南。无论是系统故障后的应急备份,还是日常恢复模式下的文件迁移,都能通过该脚本高效、安全地完成任务,备份结果保留原目录层级,无同名冲突,便于后续系统恢复后的文件还原。