同じプレフィックスを共有する各ファイルグループから、ディレクトリ内の最新の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含むsedすべてのシステムsortで機能します。lsgrepxargsrm

スクリプトは大きなディレクトリでブロックされてはいけません。シェルファイル名の拡張を行わないでください(ファイルが多すぎるとブロックされる可能性がありますが、これは膨大な数です)。

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

意図的に、このスクリプトは削除されるファイルのみをリストすることに注意してください。スクリプトでコメント化されたループの出力をパイピングして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つのパラメータを受け入れます。N(ファイル数)
(:s,-,/,:h)はglob修飾子であり、最初のものを:sヘッダー-/置き換えて:h抽出します(最後のスラッシュまでの部分、この場合は1つしかないので最初のスラッシュでもあります)はTakeの
(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))])

関連情報