Bash

為什麼這個腳本很少工作?

  • March 27, 2021

我有以下 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 個最近的備份)。最後一行/命令並不總是有效。在終端中手動執行它可以按預期工作,有時腳本會執行它,但有時它會停止,直到我手動嘗試執行腳本/行,然後它似乎神奇地修復了一段時間。

有誰知道為什麼它偶爾會停止執行最後一行?即使我看到正在創建備份。

如果任何文件名包含換行符,您的腳本將失敗。解析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,1stat上面的輸出通過管道傳送到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將列印您從記錄開始給它的最後一條記錄X。這裡X$max_backups變數,這就是為什麼需要將該變數設置為您要保留的文件數加一的原因。讓-z我們tail處理以空結尾的記錄。

此時,我們有了要刪除的文件列表,但它們也有它們的年齡,我們需要將其刪除:

  $ 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 工具。Open Media Vault聲稱是 Linux 並執行 Debian,所以它應該適合你,但我從未使用過那個系統,所以我不能確定。

引用自:https://unix.stackexchange.com/questions/642373