私の質問は、単に「ディレクトリ内の最新のn
ファイルを除くすべてを削除する」という古い質問とは少し異なります。
ファイルのさまざまな「グループ」を含むディレクトリがあり、ファイルの各グループは任意のプレフィックスを共有し、各グループには少なくとも1つのファイルがあります。これらのプレフィックスは事前にわかりません。また、グループがいくつあるかわかりません。
編集:実際、私はファイル名について何か知っています。つまり、それらはすべてprefix-some_digits-some_digits.tar.bz2
のパターンに従います。ここで重要なのはprefix
の部分だけであり、各prefix
内には数字やダッシュがないと想定できます。
bash
スクリプトで次のことを実行したいと思います。
指定されたディレクトリを調べて、既存のすべての「グループ」を特定し、ファイルのグループごとに、グループの最新のn
ファイルを除くすべてを削除します。
グループのファイル数がn
未満の場合は、そのグループに対して何もしないでください。つまり、そのグループのファイルを削除しないでください。
bash
で上記を行うための堅牢で安全な方法は何ですか?コマンドを段階的に説明していただけますか?
スクリプト:
#!/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
を繰り返します:PREFIX
を使用してALL_FILES
を計算します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.
要求に応じて、この回答は、迅速で汚いものではなく、要求したとおりに「堅牢で安全」になる傾向があります。
移植性:この回答は、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
-一意):
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 --
これでうまくいく場合は、この回答を受け入れて投票してください。ありがとう。
辞書式順序でリストされている場合、ファイルはプレフィックスによってグループ化されていると想定します。これは、別のグループのサフィックスであるプレフィックスを持つグループがないことを意味します。 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 -- "$@"
)
これが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つの引数を受け入れます:n(ファイルの数)(: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))])