我的問題與一些舊問題有點不同,只是要求“刪除n
目錄中除最新文件之外的所有文件”。
我有一個目錄,其中包含不同的文件“組”,其中每組文件共享一些任意前綴,並且每組至少有一個文件。我事先不知道這些前綴,也不知道有多少組。
編輯:實際上,我對文件名有所了解,那就是它們都遵循模式prefix-some_digits-some_digits.tar.bz2
.這裡唯一重要的是prefix
部分,我們可以假設每個部分中prefix
沒有數字或破折號。
我想在bash
腳本中執行以下操作:
n
遍歷給定的目錄,識別所有現有的“群組”,並且對於每組文件,僅刪除該群組中除最新文件之外的所有文件。如果某個群組的檔案數少於
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_FILES
與PREFIX
- 檢查數量是否
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
根據要求,這個答案傾向於“穩健且安全”,而不是快速和骯髒。
可移植性:此答案適用於包含sh
、find
、sed
、sort
、ls
、grep
、xargs
和 的任何系統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 避免命令列長度或檔案名稱中的空格(如果有)出現任何問題。如果您希望腳本產生日誌,請新增-v
至rm
例如: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 -rl
為rm
以實際刪除文件,例如:
#!/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))])