我有以下 bash 脚本作为 OpenMediaVault 上的 cronjob 运行:
BACKUP_DIR='/srv/dev-disk-by-uuid-9EE055CFE055ADF1/Backup dir/'
BACKUP_FILE_PATH="/srv/dev-disk-by-uuid-9EE055CFE055ADF1/Backup dir/Backup [ashen] ($(date +%d-%m-%Y)).tar.gz"
SERVER_DIR=/var/lib/docker/volumes/49c9e5c53ea5b9c893c0a80117860da9b493484395c0$
MAX_BACKUPS_COUNT=4
tar -zcf "$BACKUP_FILE_PATH" $SERVER_DIR
cd "$BACKUP_DIR"
[[ $( ls | wc -l ) -gt $MAX_BACKUPS_COUNT ]] && rm "$(ls -t | tail -1)"
该脚本的重点是在给定位置创建 .tar.gz 备份,如果备份目录中有超过 4 个文件,则删除最旧的文件(重点是仅保留 4 个最近的备份)。最后一行/命令并不总是有效。在终端中手动运行它可以按预期工作,有时脚本会执行它,但有时它会停止,直到我手动尝试运行脚本/行,然后它似乎会神奇地自行修复一段时间。
有谁知道为什么它偶尔停止执行最后一行?即使我看到正在创建备份。
答案1
如果任何文件名包含换行符,您的脚本将失败。解析ls
是一个非常糟糕的主意并且很可能会失败。此外,您的脚本只会删除最新的文件。因此,如果有 100 个文件,您将剩下 99 个。您似乎期望它会删除除最近的 4 个文件之外的所有内容,但脚本的逻辑并非如此。
这是一种替代方法,可以处理任意文件名,并且实际上删除除最近 4 个文件之外的所有文件:
#!/bin/bash
## avoid using CAPS for local variable names in shell scripts
backup_dir='/srv/dev-disk-by-uuid-9EE055CFE055ADF1/Backup dir/'
backup_file_path="/srv/dev-disk-by-uuid-9EE055CFE055ADF1/Backup dir/Backup [ashen] ($(date +%d-%m-%Y)).tar.gz"
server_dir='/var/lib/docker/volumes/49c9e5c53ea5b9c893c0a80117860da9b493484395c0$'
## This needs to be set to the number of files you want to keep plus one,
## so that we can use tail -n $max_backups below.
max_backups=5
tar -zcf "$BACKUP_FILE_PATH" "$SERVER_DIR"
## delete all but the newest 4 tar.gz files in the
## backup directory
stat --printf '%Y %n\0' "$backup_dir"/*tar.gz |
sort -rznk1,1 | tail -z -n +"$max_backups" |
sed -z 's/^[0-9]* //' | xargs -0 rm -v
这里的工作是由该stat
命令和各种下游管道完成的。以下是该命令正在执行的操作的详细说明:
stat --printf '%Y %n\0' "$backup_dir"/*tar.gz
.tar.gz
:这将打印自备份目录中所有文件的纪元以来的文件名和文件期限(以秒为单位) 。为了能够处理带有换行符 (\n
) 的文件名,我们需要以 NULL (\0
) 结束每个条目。输出如下所示:$ stat --printf '%Y %n\0' * | tr '\0' '\n' 1616867929 ./afile 5 tar.gz 1616868565 ./file 10 tar.gz 1616868560 ./file 1 tar.gz 1616868561 ./file 2 tar.gz 1616867927 ./file 3 tar.gz 1616867928 ./file 4 tar.gz 1616867930 ./file 6 tar.gz 1616868562 ./file 7 tar.gz 1616868563 ./file 8 tar.gz 1616868564 ./file 9 tar.gz
对于这个例子,我通过管道传输输出,tr '\0' '\n'
以便它清晰可辨,但在实际输出中,每个记录的末尾都有一个\0
。
sort -rznk1,1
:上面的输出stat
通过管道传输到sort
它将按数字 (-n
) 排序,按相反顺序 (-r
) 排序,\0
用作记录分隔符 (-z
) 并仅考虑第一个字段 (-k1,1
),即文件的年龄。输出看起来像:
$ stat --printf '%Y %n\0' "$backup_dir"/*tar.gz | sort -rznk1,1 | tr '\0' '\n' 1616868565 ./file 10 tar.gz 1616868564 ./file 9 tar.gz 1616868563 ./file 8 tar.gz 1616868562 ./file 7 tar.gz 1616868561 ./file 2 tar.gz 1616868560 ./file 1 tar.gz 1616867930 ./file 6 tar.gz 1616867929 ./afile 5 tar.gz 1616867928 ./file 4 tar.gz 1616867927 ./file 3 tar.gz
tail -z -n +"$max_backups"
:该命令tail -n +X
将打印您从 record 开始给出的最后一条记录X
。这里,X
是$max_backups
变量,这就是为什么需要将该变量设置为要保留的文件数加一。 let-z
处理tail
以 null 结尾的记录。此时,我们有了要删除的文件列表,但它们也有其年龄,我们需要将其删除:
$ stat --printf '%Y %n\0' "$backup_dir"/*tar.gz | sort -rznk1,1 | tail -z -n +5 | tr '\0' '\n' 1616868561 ./file 2 tar.gz 1616868560 ./file 1 tar.gz 1616867930 ./file 6 tar.gz 1616867929 ./afile 5 tar.gz 1616867928 ./file 4 tar.gz 1616867927 ./file 3 tar.gz
sed -z 's/^[0-9]* //'
:删除文件的年龄,仅保留名称。再次,-z
处理空终止记录:$ stat --printf '%Y %n\0' "$backup_dir"/*tar.gz | sort -rznk1,1 | tail -z -n +5 | sed -z 's/^[0-9]* //' | tr '\0' '\n' ./file 2 tar.gz ./file 1 tar.gz ./file 6 tar.gz ./afile 5 tar.gz ./file 4 tar.gz ./file 3 tar.gz
xargs -0 rm -v
: 最后一步。这将删除文件,并且再次删除文件,-z
以便它可以处理以空结尾的记录。
重要的:该脚本假定您正在使用 GNU 工具。打开媒体库声称是 Linux 并运行 Debian,所以它应该适合你,但我从未使用过该系统,所以我不能确定。