web-dev-qa-db-ja.com

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

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

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

編集:実際、私はファイル名について何か知っています。つまり、それらはすべてprefix-some_digits-some_digits.tar.bz2のパターンに従います。ここで重要なのはprefixの部分だけであり、各prefix内には数字やダッシュがないと想定できます。

bashスクリプトで次のことを実行したいと思います。

  1. 指定されたディレクトリを調べて、既存のすべての「グループ」を特定し、ファイルのグループごとに、グループの最新のnファイルを除くすべてを削除します。

  2. グループのファイル数がn未満の場合は、そのグループに対して何もしないでください。つまり、そのグループのファイルを削除しないでください。

bashで上記を行うための堅牢で安全な方法は何ですか?コマンドを段階的に説明していただけますか?

5
skyork

スクリプト:

#!/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.
3
ferdy

要求に応じて、この回答は、迅速で汚いものではなく、要求したとおりに「堅牢で安全」になる傾向があります。

移植性:この回答は、shfindsedsortlsgrepxargs、および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を使用して、コマンドラインの長さやファイル名のスペース(存在する場合)に関する問題を回避します。スクリプトでログを生成する場合は、-vrmに追加します。例:rm -v --#を削除して、削除コードを有効にします。

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

これでうまくいく場合は、この回答を受け入れて投票してください。ありがとう。

3
RobertL

辞書式順序でリストされている場合、ファイルはプレフィックスによってグループ化されていると想定します。これは、別のグループのサフィックスであるプレフィックスを持つグループがないことを意味します。 foo-1-2-3.tar.bz2foo-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 -rlrmに置き換えて、実際にファイルを削除します。例:

#!/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))])
1
don_crissti