n
私の質問は、単に「ディレクトリ内の最新のファイルを除くすべてのファイルを削除する」ことを尋ねるいくつかの古い質問とは少し異なります。
さまざまな「グループ」のファイルを含むディレクトリがあり、各ファイル グループは任意のプレフィックスを共有し、各グループには少なくとも 1 つのファイルがあります。これらのプレフィックスは事前にわかりませんし、グループがいくつあるかもわかりません。
編集: 実は、ファイル名については、すべてがパターンに従っているということを知っています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
保持するファイルの数より少ないかどうかを確認します -> true の場合、ここで停止できます。削除するものはありません。 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
スクリプトは、大きなディレクトリで詰まることはありません。シェルのファイル名の拡張は実行されません (ファイルが多すぎると詰まる可能性がありますが、その数は膨大です)。
この回答では、プレフィックスにダッシュ ( -
) が含まれないことを前提としています。
設計上、スクリプトは削除されるファイルのみをリストすることに注意してください。スクリプト内でコメント アウトされている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
--unique):
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
スクリプトは 1 つの引数を受け入れます:ん(ファイルの数)
(:s,-,/,:h)
は glob 修飾子で、:s
最初のもの-
を に置き換えて/
、:h
先頭 (最後のスラッシュまでの部分、この場合は 1 つしかないため最初のスラッシュでもあります) を抽出します。
(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))])