Your script will fail if any of the file names contain newlines. Parsing ls
is a very bad idea and very likely to fail. Additionally, your script will only ever delete the most recent file. So if there are 100 files, you will be left with 99. You seem to be expecting that it will delete everything except the most recent 4, but the script's logic doesn't work that way.
Here's an alternative approach that can deal with arbitrary file names and actually deletes all but the most recent 4 files:
#!/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
The work here is being done by that stat
command and the various downstream pipes. Here's a breakdown of what the command is doing:
stat --printf '%Y %n\0' "$backup_dir"/*tar.gz
: this prints the file name and file age in seconds since the epoch of all .tar.gz
files in the backup directory. In order to be able to handle file names with newlines (\n
), we need to end each entry with a NULL (\0
). This is what the output looks like:
$ 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
For this example, I piped the output to tr '\0' '\n'
so that it is legible, but in the actual output the end of each record has a \0
instead.
sort -rznk1,1
: the output of the stat
above, is piped to sort
which will sort it numerically (-n
), in reverse order (-r
), using \0
as the record separator (-z
) and only considering the 1st field (-k1,1
), which is the file's age.
The output looks like:
$ 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"
: the command tail -n +X
will print the last records you give it starting from record X
. Here, X
is the $max_backups
variable, which is why that variable needs to be set to the number of files you want to keep plus one. The -z
lets tail
deal with null-terminated records.
At this point, we have the list of files we want to delete, but they also have their age and we need to remove it:
$ 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]* //'
: removes the file's age, leaving only the name. Once again, the -z
is to deal with null-terminated records:
$ 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
: the last step. This will delete the files and, once more, the -z
is so it can handle null-terminated records.
IMPORTANT: the script assumes you are using GNU tools. Open Media Vault claims to be Linux and run Debian, so it should work for you, but I have never used that system so I cannotbe sure.
ls | wc -l
andls -t |tail -1
should work fine. (Not quoting the$(ls | wc-l)
also doesn't matter with defaultIFS
, and the other one is properly quoted.) And while it only ever removes one file each run, if they're only created in the same script, one at a time, it shouldn't be able to end up having more. Hmm. – ilkkachu Mar 27 '21 at 15:12-
or_
is just much safer wrt. shell scripts and stuff. – ilkkachu Mar 27 '21 at 15:18set -x
at the top, and then see from the output what it actually runs each time it runs. Assuming your cron is properly set up to send the output as email. – ilkkachu Mar 27 '21 at 16:09