ディレクトリ内の同じプレフィックスを共有するファイルグループごとに、最新のnファイルを除くすべてを削除します。

ディレクトリ内の同じプレフィックスを共有するファイルグループごとに、最新のnファイルを除くすべてを削除します。

n私の質問は、単に「ディレクトリ内の最新のファイルを除くすべてのファイルを削除する」ことを尋ねるいくつかの古い質問とは少し異なります。

さまざまな「グループ」のファイルを含むディレクトリがあり、各ファイル グループは任意のプレフィックスを共有し、各グループには少なくとも 1 つのファイルがあります。これらのプレフィックスは事前にわかりませんし、グループがいくつあるかもわかりません。

編集: 実は、ファイル名については、すべてがパターンに従っているということを知っています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_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を含むすべてのシステムで機能します。sedsortlsgrepxargsrm

スクリプトは、大きなディレクトリで詰まることはありません。シェルのファイル名の拡張は実行されません (ファイルが多すぎると詰まる可能性がありますが、その数は膨大です)。

この回答では、プレフィックスにダッシュ ( -) が含まれないことを前提としています。

設計上、スクリプトは削除されるファイルのみをリストすることに注意してください。スクリプト内でコメント アウトされている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追加します。削除コードを有効にするには を削除します。rmrm -v --#

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

これがうまくいった場合は、この回答を承認して投票してください。ありがとうございます。

答え3

ファイルは、語彙順にリストされるときにプレフィックスによってグループ化されていると仮定します。つまり、プレフィックスが別のグループのサフィックスであるグループ (たとえば、とfoo-1-2-3.tar.bz2の間には入らないグループ)は存在しないということです。この仮定の下では、すべてのファイルをリストでき、プレフィックスの変更を検出すると (または最初のファイルの場合)、新しいグループが作成されます。foo-1-1.tar.bz2foo-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))])

関連情報