刪除目錄中每組共享相同前綴的檔案中除最近的 n 個檔案之外的所有文件

刪除目錄中每組共享相同前綴的檔案中除最近的 n 個檔案之外的所有文件

我的問題與一些舊問題有點不同,只是要求“刪除n目錄中除最新文件之外的所有文件”。

我有一個目錄,其中包含不同的文件“組”,其中每組文件共享一些任意前綴,並且每組至少有一個文件。我事先不知道這些前綴,也不知​​道有多少組。

編輯:實際上,我對文件名有所了解,那就是它們都遵循模式prefix-some_digits-some_digits.tar.bz2.這裡唯一重要的是prefix部分,我們可以假設每個部分中prefix沒有數字或破折號。

我想在bash腳本中執行以下操作:

  1. n遍歷給定的目錄,識別所有現有的“群組”,並且對於每組文件,僅刪除該群組中除最新文件之外的所有文件。

  2. 如果某個群組的檔案數少於n該群組的檔案數,則對該群組不執行任何操作,即不刪除該群組的任何檔案。

在 中執行上述操作的穩健且安全的方法是什麼bash?您能逐步解釋一下這些命令嗎?

答案1

劇本:

#!/bin/bash

# Get Prefixes

PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq)

if [ -z "$1" ]; then
  echo need a number of keep files.
  exit 1
else
  NUMKEEP=$1
fi

for PREFIX in ${PREFIXES}; do

  ALL_FILES=$(ls -t ${PREFIX}*)

  if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then
    echo Not enough files to be kept. Quit.
    continue
  fi

  KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP})

  for file in $ALL_FILES ; do
    if [[ "$KEEP" =~ "$file" ]]; then
      echo keeping $file
    else
      echo RM $file
    fi
  done
done

解釋:

  • 計算前綴:
    • 尋找something-something-something.tar.bz2正規表示式後面的所有文件,僅剪切第一部分到第一個破折號,並使其唯一。
    • 結果是標準化列表PREFIXES
  • 遍歷所有PREFIXES
  • 計算ALL_FILESPREFIX
  • 檢查數量是否ALL_FILES小於要保留的檔案數 -> 如果為真,我們可以到此為止,無需刪除任何內容
  • 計算KEEP最近的NUMKEEP文件
  • 遍歷ALL_FILES並檢查給定文件是否不在KEEP文件列表中。如果是這樣:將其刪除。

運行時的範例結果:

$ ./remove-old.sh 2
keeping bar-01-01.tar.bz2
keeping bar-01-02.tar.bz2
RM bar-01-03.tar.bz2
RM bar-01-04.tar.bz2
RM bar-01-05.tar.bz2
RM bar-01-06.tar.bz2
keeping foo-01-06.tar.bz2
keeping foo-01-05.tar.bz2
RM foo-01-04.tar.bz2
RM foo-01-03.tar.bz2
RM foo-01-02.tar.bz2

$ ./remove-old.sh 8
Not enough files to be kept. Quit.
Not enough files to be kept. Quit.

答案2

根據要求,這個答案傾向於“穩健且安全”,而不是快速和骯髒。

可移植性:此答案適用於包含shfindsedsortlsgrepxargs和 的任何系統rm

腳本永遠不應該因大目錄而阻塞。不執行 shell 檔案名稱擴充(如果檔案太多,這可能會阻塞,但這是一個巨大的數字)。

此答案假設前綴不包含任何破折號 ( -)。

請注意,根據設計,該腳本僅列出將被刪除的檔案。您可以透過管道傳輸腳本中註解掉的while循環的輸出來刪除檔案xargs -d '/n' rm。這樣您就可以在啟用刪除程式碼之前輕鬆測試腳本。

#!/bin/sh -e

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
sed 's/-.*//; s,^\./,,' |
sort -u |
while read prefix
do
    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
done # | xargs -d '\n' rm --

N參數(要保留的文件數)預設為64000(即保留所有文件)。

帶註解的程式碼

取得命令列參數並透過加法檢查整數,如果沒有給出參數預設為 64000(實際上是全部):

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

尋找目前目錄中與檔案名稱格式相符的所有檔案:

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |

取得前綴:刪除前綴後面的所有內容並刪除開頭的「./」:

sed 's/-.*//; s,^\./,,' |

對前綴進行排序並刪除重複項(-u--唯一):

sort -u |

讀取每個前綴和過程:

while read prefix
do

按時間排序列出目錄中的所有文件,選擇當前前綴的文件,並刪除我們要保留的文件之外的所有行:

    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"

為了測試註解掉刪除檔案的程式碼。使用 xargs 避免命令列長度或檔案名稱中的空格(如果有)出現任何問題。如果您希望腳本產生日誌,請新增-vrm例如:rm -v --。刪除#以啟用刪除代碼:

done # | xargs -d '\n' rm --

如果這對您有用,請接受此答案並投票。謝謝。

答案3

我假設文件按詞彙順序列出時按前綴分組在一起。這意味著不存在帶有作為另一個組的後綴的前綴的組,例如,foo-1-2-3.tar.bz2不會出現在foo-1-1.tar.bz2和之間foo-1-2.tar.bz2。在這種假設下,我們可以列出所有文件,當我們偵測到前綴變更(或第一個文件)時,我們就有了一個新群組。

#!/bin/bash
n=$1; shift   # number of files to keep in each group
shopt extglob
previous_prefix=-
for x in *-+([0-9])-+([0-9]).tar.bz2; do
  # Step 1: skip the file if its prefix has already been processed
  this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2}
  if [[ "$this_prefix" == "$previous_prefix" ]]; then
    continue
  fi
  previous_prefix=$this_prefix
  # Step 2: process all the files with the current prefix
  keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2
done

現在我們要解決的問題是確定顯式清單中最舊的文件

假設檔案名稱不包含換行符或ls不按字面顯示的字符,這可以透過以下方式實現ls

keep_latest () (
  n=$1; shift
  if [ "$#" -le "$n" ]; then return; fi
  unset IFS; set -f
  set -- $(ls -t)
  shift "$n"
  rm -- "$@"
)

答案4

我知道這是標記的bash,但我認為這樣會更容易zsh

#!/usr/bin/env zsh

N=$(($1 + 1))                         # calculate Nth to last
typeset -U prefixes                   # declare array with unique elements
prefixes=(*.tar.bz2(:s,-,/,:h))       # save prefixes in the array
for p in $prefixes                    # for each prefix
do
arr=(${p}*.tar.bz2)                   # save filenames starting with prefix in arr
if [[ ${#arr} -gt $1 ]]               # if number of elements is greather than $1
then
print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N 
fi
done

該腳本接受一個參數:n(檔案數)
(:s,-,/,:h)是 glob 修飾符,:s將第一個替換-/:h提取頭部(直到最後一個斜杠的部分,在本例中也是第一個斜杠,因為只有一個)
(Om[1,-$N])是 glob 限定符,Om對以最舊的文件,並[1,-$N]從第一個到第 N 個到最後一個進行選擇
如果您對結果滿意,請替換print -rlrm以實際刪除文件,例如:

#!/usr/bin/env zsh

typeset -U prefixes
prefixes=(*.tar.bz2(:s,-,/,:h))
for p in $prefixes
arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))])

相關內容