步骤/目录： 1.rpi-backup.sh详解 （1）准备工作 （2）挂载img文件并备份 （3）修改PARTUUID （4）收尾工作 （5）使用备份镜像 2.编写脚本文件 （1）dump/store法 （2）tar法 3.验证 （1）备份系统 （2）还原系统本文首发于个人博客https://lisper517.top/index.php/archives/10/，转载请注明出处。本文实验日期为2022年2月7日。使用的是树莓派4B（内存8G版），cat /etc/os-release查看系统为Raspbian GNU/Linux 10 (buster)；待备份系统为Pi OS 32位桌面版（即raspbian。2022年1月28日更新，镜像名 2022-01-28-raspios-bullseye-armhf.img ）。步骤参考了制作树莓派img镜像文件，脚本在RaspberryBackup项目原址基础上略微改动。1.rpi-backup.sh详解（1）准备工作注意，如果手动挂载读卡器前，读卡器已自动挂载（用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置），那么可能需要umount一下，并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作，就可能是这个原因。待备份系统sd卡用读卡器接上树莓派，使用lsblk查看，待备份系统的/boot目录在设备/dev/sdb1（大小为256M）、/目录在/dev/sdb2（大小为58.2G），但是还没挂载。在media下新建文件夹并挂载：mkdir /media/boot1 /media/root1 mount /dev/sdb1 /media/boot1 mount /dev/sdb2 /media/root1用df -h查看，已经挂载上了。接下来安装需要的软件：apt install dosfstools dump parted kpartx创建工作目录并进入：mkdir ~/backupimg cd ~/backupimg创建空白img文件（待备份系统大概使用了4G空间，img文件使用5000M）：dd if=/dev/zero of=raspi.img bs=1M count=5000简单介绍一下dd命令：用于复制文件，复制时可转换格式。这里的bs是指定读取输入文件、写入输出文件时的字节大小，注意这里带不带B的区别（不带B是1024进制，带B是1000进制。img文件大小必须是512 bytes的整数倍）；count指定大小。经过一段时间后，img文件创建完毕。接下来看看待备份sd卡的分区：fdisk -l #显示信息如下 ... Device Boot Start End Sectors Size Id Type /dev/sdb1 8192 532479 524288 256M c W95 FAT32 (LBA) /dev/sdb2 532480 122138623 121606144 58G 83 Linux本例中待备份的sd卡为64GB。接下来使用parted给img文件分区（注意分区的起点扇区号是8192的倍数，4K对齐。这里的8192单位是512 bytes）：parted raspi.img --script -- mklabel msdos parted raspi.img --script -- mkpart primary fat32 8192s 532479s parted raspi.img --script -- mkpart primary ext4 532480s -1然后parted raspi.img进入parted命令行，print free检查一下分区并quit。准备工作已完成，接下来挂载img文件并开始备份。（2）挂载img文件并备份使用losetup把img文件虚拟成区块设备：losetup -f --show raspi.img #显示/dev/loop0 kpartx -av /dev/loop0 #这时find /dev/mapper -name "loop0p*"可以找到loop0p1（/boot）和loop0p2（/）接下来格式化，创建文件夹并挂载到文件夹：mkfs.vfat -n boot /dev/mapper/loop0p1 mkfs.ext4 -L rootfs /dev/mapper/loop0p2 mkdir tgt_boot tgt_Root mount -t vfat -o uid=root,gid=root,umask=0000 /dev/mapper/loop0p1 ./tgt_boot mount -t ext4 /dev/mapper/loop0p2 ./tgt_Root这里用root账户，其他账户需要更改uid和gid。备份/boot目录（直接cp，需要改挂载位置）：cp -rfp /media/boot1/* ./tgt_boot/这里再提醒一次，如果手动挂载读卡器前，读卡器已自动挂载（用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置），那么可能需要umount一下，并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作，就可能是这个原因。备份/目录：chmod 777 ./tgt_Root chown root.root ./tgt_Root rm -rf ./tgt_Root/* cd tgt_Root #开始备份/ dump -0uaf - /media/root1 | restore -rf -上述方法为dump/restore法。实验时在DUMP: dumping (Pass IV) [regular files]后出现Broken pipe错误。于是查询最初的原文，使用tar法（系统中需要有和待备份系统的/已用空间一样大的额外空间）：cd .. #使用tar法前先恢复一下tgt_Root文件夹。直接用tar法时则从上文的chmod 777 ./tgt_Root开始 rm -rf ./tgt_Root/* tar --directory=/media/root1 -pcf ~/backupimg/backup.tar . #注意最后的.不要忘记该tar命令切换到/目录挂载点（记得自行更改）后将该目录下所有文件打包。过程较慢且运行时树莓派较卡，可以另开PuTTY观察backup.tar大小（watch -d -n 5 ls -l ~/backupimg/backup.tar），或者添加-v如下：tar --directory=/media/root1 -vpcf ~/backupimg/backup.tar .tar打包时出现socket ignored（忽略套接字）错误可以忽略。接下来将backup.tar解压到img文件的根文件系统：tar --directory=/root/backupimg/tgt_Root -vpxf ~/backupimg/backup.tar这个过程同样也比较长。完成后即可删除backup.tar。（3）修改PARTUUID备份已经完成，但Raspbian启动要对应分区的PARTUUID，需要修改2个文件（~/backupimg/tgt_boot/cmdline.txt和~/backupimg/tgt_Root/etc/fstab）。首先看loop0p*对应的PARTUUID：blkid #输出结果 /dev/mapper/loop0p1: SEC_TYPE="msdos" LABEL_FATBOOT="boot" LABEL="boot" UUID="6CA0-1B14" TYPE="vfat" PARTUUID="622d2beb-01" /dev/mapper/loop0p2: LABEL="rootfs" UUID="6b5a3e6b-1c31-4f39-8a07-6bdba7c80282" TYPE="ext4" PARTUUID="622d2beb-02"修改cmdline.txt文件，将root=PARTUUID=dfd7da0c-02改为：root=PARTUUID=622d2beb-02修改fstab文件，改为：PARTUUID=622d2beb-01 /boot vfat defaults 0 2 PARTUUID=622d2beb-02 / ext4 defaults,noatime 0 1最后，删除/目录中系统自动产生的文件、临时文件，镜像文件就制作完成了：cd ~/backupimg/tgt_Root rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable cd ..（4）收尾工作删除各种挂载点和临时文件夹：umount ~/backupimg/tgt_boot ~/backupimg/tgt_Root kpartx -d /dev/loop0 losetup -d /dev/loop0 rmdir ~/backupimg/tgt_boot ~/backupimg/tgt_Root（5）使用备份镜像镜像写好后可以用dd命令或者Win32DiskImager等工具烧录到sd卡中（如果sd卡原先是系统盘可以使用SD Card Formatter）。注意使用备份系统时首先要在raspi-config中Expand Filesystem。2.编写脚本文件脚本在RaspberryBackup项目原址基础上略微改动，主要有3点改动：增加了使用tar法的脚本；增加了获取gid的准确性；增加了fdisk -l在boot分区前有无*的判断。另外改正了1处：bootsz=`df -P | grep $dev_boot | awk '{print $2}'改为：bootsz=`df -P | grep $dev_boot | awk '{print $3}'本文中在树莓派4B上使用tar法脚本进行备份用时16min58s。用PC会快一些。（1）dump/store法若运行时出现DUMP: Broken pipe的错误，请使用tar法脚本。另外再次再次提醒，如果手动挂载读卡器前，读卡器已自动挂载（用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置），那么可能需要umount一下，并新建文件夹、手动挂载。如果备份/boot区时出现cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作，就可能是这个原因。#!/bin/sh if [ $# != 2 ]; then echo "argument error: Usage: $0 boot_device_name root_device_name" echo "example: $0 /dev/mmcblk0p1 /dev/mmcblk0p2" exit 0 fi dev_boot=$1 dev_root=$2 mounted_boot=`df -h | grep $dev_boot | awk '{print $6}'` mounted_root=`df -h | grep $dev_root | awk '{print $6}'` img=rpi-`date +%Y%m%d-%H%M`.img #install tools sudo apt-get install dosfstools dump parted kpartx echo ===================== part 1, prepare workspace =============================== mkdir ~/backupimg cd ~/backupimg echo ===================== part 2, create a new blank img =============================== echo "creating new blank img..." bootsz=`df -P | grep $dev_boot | awk '{print $3}'` rootsz=`df -P | grep $dev_root | awk '{print $3}'` totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3/1024)}'` sudo dd if=/dev/zero of=$img bs=1M count=$totalsz #sync echo "...created a blank img, size ${totalsz}M " # format virtual disk bootstart=`sudo fdisk -l | grep $dev_boot | awk '{print $2}'` bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $3}'` #有些系统 sudo fdisk -l 时boot分区的boot标记会标记为*,此时bootstart和bootend最后应改为 $3 和 $4 first_mark=`sudo fdisk -l | grep $dev_boot | awk '{print $1}'` if [ "$first_mark" == '*' ]; then $bootstart=$bootend $bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $4}'` fi rootstart=`sudo fdisk -l | grep $dev_root | awk '{print $2}'` echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end" sudo parted $img --script -- mklabel msdos sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1 echo ===================== part 3, mount img to system =============================== loopdevice=`sudo losetup -f --show $img` device=/dev/mapper/`sudo kpartx -av $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1` sleep 5 sudo mkfs.vfat -n boot ${device}p1 sudo mkfs.ext4 -L rootfs ${device}p2 #在backupimg文件夹下新建两个文件夹，将两个分区挂载在下面 mkdir tgt_boot tgt_Root uid=`whoami` gid=`id $uid | awk '{print $2}' | awk 'BEGIN{FS="("} {print $NF}' | awk 'BEGIN{FS=")"} {print $1}'` sudo mount -t vfat -o uid=${uid},gid=${gid},umask=0000 ${device}p1 ./tgt_boot sudo mount -t ext4 ${device}p2 ./tgt_Root echo ===================== part 4, backup /boot ========================= sudo cp -rfp ${mounted_boot}/* ./tgt_boot/ sync echo "...Boot partition done" echo ===================== part 5, backup / ========================= sudo chmod 777 ./tgt_Root sudo chown ${uid}.${gid} ./tgt_Root sudo rm -rf ./tgt_Root/* cd tgt_Root # start backup sudo dump -0uaf - ${mounted_root} | sudo restore -rf - sync echo "...Root partition done" cd .. echo ===================== part 6, replace PARTUUID ========================= # replace PARTUUID opartuuidb=`sudo blkid -o export $dev_boot | grep PARTUUID` opartuuidr=`sudo blkid -o export $dev_root | grep PARTUUID` npartuuidb=`sudo blkid -o export ${device}p1 | grep PARTUUID` npartuuidr=`sudo blkid -o export ${device}p2 | grep PARTUUID` sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_boot/cmdline.txt sudo sed -i "s/$opartuuidb/$npartuuidb/g" ./tgt_Root/etc/fstab sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_Root/etc/fstab echo "...replace PARTUUID done" echo "remove auto generated files" #下面内容是删除树莓派中系统自动产生的文件、临时文件等 cd ~/backupimg/tgt_Root sudo rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable cd .. echo ===================== part 7, unmount ========================= sudo umount tgt_boot tgt_Root sudo kpartx -d $loopdevice sudo losetup -d $loopdevice rmdir tgt_boot tgt_Root echo "==== All done. img file is under ~/backupimg/ "（2）tar法再次再次再次提醒，如果手动挂载读卡器前，读卡器已自动挂载（用df -h查看时发现挂载到/media/pi/boot和/media/pi/rootfs等位置），那么可能需要umount一下，并新建文件夹、手动挂载。如果备份/boot区时cp: 无法保留'./tgt_boot/文件名' 的所有者: 不允许的操作，就可能是这个原因。#!/bin/sh if [ $# != 2 ]; then echo "argument error: Usage: $0 boot_device_name root_device_name" echo "example: $0 /dev/mmcblk0p1 /dev/mmcblk0p2" exit 0 fi dev_boot=$1 dev_root=$2 mounted_boot=`df -h | grep $dev_boot | awk '{print $6}'` mounted_root=`df -h | grep $dev_root | awk '{print $6}'` img=rpi-`date +%Y%m%d-%H%M`.img #install tools sudo apt-get install dosfstools parted kpartx echo ===================== part 1, prepare workspace =============================== mkdir ~/backupimg cd ~/backupimg echo ===================== part 2, create a new blank img =============================== echo "creating new blank img..." bootsz=`df -P | grep $dev_boot | awk '{print $3}'` rootsz=`df -P | grep $dev_root | awk '{print $3}'` totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3/1024)}'` sudo dd if=/dev/zero of=$img bs=1M count=$totalsz #sync echo "...created a blank img, size ${totalsz}M " # format virtual disk bootstart=`sudo fdisk -l | grep $dev_boot | awk '{print $2}'` bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $3}'` #有些系统 sudo fdisk -l 时boot分区的boot标记会标记为*,此时bootstart和bootend最后应改为 $3 和 $4 first_mark=`sudo fdisk -l | grep $dev_boot | awk '{print $1}'` if [ "$first_mark" == '*' ]; then $bootstart=$bootend $bootend=`sudo fdisk -l | grep $dev_boot | awk '{print $4}'` fi rootstart=`sudo fdisk -l | grep $dev_root | awk '{print $2}'` echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end" sudo parted $img --script -- mklabel msdos sudo parted $img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s sudo parted $img --script -- mkpart primary ext4 ${rootstart}s -1 echo ===================== part 3, mount img to system =============================== loopdevice=`sudo losetup -f --show $img` device=/dev/mapper/`sudo kpartx -av $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1` sleep 5 sudo mkfs.vfat -n boot ${device}p1 sudo mkfs.ext4 -L rootfs ${device}p2 #在backupimg文件夹下新建两个文件夹，将两个分区挂载在下面 mkdir tgt_boot tgt_Root uid=`whoami` gid=`id $uid | awk '{print $2}' | awk 'BEGIN{FS="("} {print $NF}' | awk 'BEGIN{FS=")"} {print $1}'` sudo mount -t vfat -o uid=${uid},gid=${gid},umask=0000 ${device}p1 ./tgt_boot sudo mount -t ext4 ${device}p2 ./tgt_Root echo ===================== part 4, backup /boot ========================= sudo cp -rfp ${mounted_boot}/* ./tgt_boot/ sync echo "...Boot partition done" echo ===================== part 5, backup / ========================= sudo chmod 777 ./tgt_Root sudo chown ${uid}.${gid} ./tgt_Root sudo rm -rf ./tgt_Root/* cd tgt_Root # start backup echo "tar -c running..." sudo tar --directory=${mounted_root} -pcf ~/backupimg/backup.tar . sync cd ~/backupimg/tgt_Root echo "tar -x running..." sudo tar -pxf ../backup.tar sync cd .. sudo rm backup.tar echo "...Root partition done" echo ===================== part 6, replace PARTUUID ========================= # replace PARTUUID opartuuidb=`sudo blkid -o export $dev_boot | grep PARTUUID` opartuuidr=`sudo blkid -o export $dev_root | grep PARTUUID` npartuuidb=`sudo blkid -o export ${device}p1 | grep PARTUUID` npartuuidr=`sudo blkid -o export ${device}p2 | grep PARTUUID` sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_boot/cmdline.txt sudo sed -i "s/$opartuuidb/$npartuuidb/g" ./tgt_Root/etc/fstab sudo sed -i "s/$opartuuidr/$npartuuidr/g" ./tgt_Root/etc/fstab echo "...replace PARTUUID done" echo "remove auto generated files" #下面内容是删除树莓派中系统自动产生的文件、临时文件等 cd ~/backupimg/tgt_Root sudo rm -rf ./.gvfs ./dev/* ./media/* ./mnt/* ./proc/* ./run/* ./sys/* ./tmp/* ./lost+found/ ./restoresymtable cd .. echo ===================== part 7, unmount ========================= sudo umount tgt_boot tgt_Root sudo kpartx -d $loopdevice sudo losetup -d $loopdevice rmdir tgt_boot tgt_Root echo "==== All done. img file is under ~/backupimg/ "3.验证（1）备份系统树莓派4B插上sd卡、接上电源，待备份sd卡插到读卡器、通过usb插到树莓派。输入df -h检查：df -h #输出结果 文件系统 容量 已用 可用 已用% 挂载点 /dev/root 57G 4.1G 51G 8% / devtmpfs 3.7G 0 3.7G 0% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 1.6G 980K 1.6G 1% /run tmpfs 5.0M 4.0K 5.0M 1% /run/lock /dev/mmcblk0p1 256M 49M 207M 20% /boot tmpfs 790M 20K 790M 1% /run/user/1000 /dev/sdb2 58G 4.0G 51G 8% /media/pi/rootfs /dev/sdb1 253M 49M 204M 20% /media/pi/boot tmpfs 790M 16K 790M 1% /run/user/1002发现sd卡已自动挂载。umount一下，并重新挂载：umount /media/pi/rootfs umount /media/pi/boot mkdir /media/boot1 mkdir /media/root1 mount /dev/sdb1 /media/boot1 mount /dev/sdb2 /media/root1写入脚本，为脚本增加执行权限，并执行：nano /tmp/rpi-backup.sh #复制tar法脚本内容 chmod +x /tmp/rpi-backup.sh #或chmod 100 /tmp/rpi-backup.sh /tmp/rpi-backup.sh /dev/sdb1 /dev/sdb2以下为输出结果：正在读取软件包列表... 完成 正在分析软件包的依赖关系树... 完成 正在读取状态信息... 完成 dosfstools 已经是最新版 (4.2-1)。 kpartx 已经是最新版 (0.8.5-2)。 parted 已经是最新版 (3.4-1)。 升级了 0 个软件包，新安装了 0 个软件包，要卸载 0 个软件包，有 0 个软件包未被升级。 ===================== part 1, prepare workspace =============================== ===================== part 2, create a new blank img =============================== creating new blank img... 记录了5310+0 的读入 记录了5310+0 的写出 5567938560字节（5.6 GB，5.2 GiB）已复制，332.425 s，16.7 MB/s ...created a blank img, size 5310M /tmp/rpi-backup.sh: 35: [: /dev/sdb1: unexpected operator boot: 8192 >>> 532479, root: 532480 >>> end ===================== part 3, mount img to system =============================== mkfs.fat 4.2 (2021-01-31) mkfs.fat: Warning: lowercase labels might not work properly on some systems mke2fs 1.46.2 (28-Feb-2021) Discarding device blocks: done Creating filesystem with 1292544 4k blocks and 323200 inodes Filesystem UUID: b5730506-8cbd-4f4b-993c-fb4eaeb2fa1e Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736 Allocating group tables: done Writing inode tables: done Creating journal (16384 blocks): done Writing superblocks and filesystem accounting information: done ===================== part 4, backup /boot ========================= ...Boot partition done ===================== part 5, backup / ========================= tar -c running... tar: ./home/pi/.pcsc11/pcscd.comm: 忽略套接字(socket) tar -x running... ...Root partition done ===================== part 6, replace PARTUUID ========================= ...replace PARTUUID done remove auto generated files ===================== part 7, unmount ========================= ==== All done. img file is under ~/backupimg/运行用时16min58s。输出中有一行/tmp/rpi-backup.sh: 35: [: /dev/sdb1: unexpected operator，这个可以不管。（2）还原系统将~/backupimg/下的备份镜像用Win32DiskImager写入一张新sd卡，插上树莓派后运行。树莓派开机后，在raspi-config中的6 Advanced Options下设置Expand Filesystem。检查用户组、用户名、wifi设置，与原系统一致。