web-dev-qa-db-ja.com

ファイルパターンに一致する最新のものを除いて、2週間以上前のディレクトリ内のすべてのディレクトリを削除するにはどうすればよいですか?

私は次のパスを持っています:

/dir1/dir2/

このパスには、さまざまな(関係のない)アプリケーションdetriusを含む次のディレクトリがあります。

follower1234  1-Dec-2018
follower3456  2-Dec-2018
follower4567  3-Dec-2018
follower7890  9-Jan-2019
follower8901 10-Jan-2019
leader8765    4-Dec-2018
bystander6789 5-Dec-2018

今日が2019年1月10日であるとします。

followerXXXXleaderXXXX、およびbystanderXXXXディレクトリはいくつでも存在できると想定します。

2週間以上前の最新のfollowerXXXXディレクトリを除くすべてのfollowerXXXディレクトリを削除する必要があります。

これで、すべてのディレクトリを削除できます 特定の日付より古い 。しかし、それは私の質問ではありません。 2つのパラメーターを追加します。

この場合、私は削除したいと思います:

follower1234  1-Dec-2018
follower3456  2-Dec-2018
follower4567  3-Dec-2018

だがしかし

follower7890  9-Jan-2019
follower8901 10-Jan-2019
leader8765    4-Dec-2018
bystander6789 5-Dec-2018

つまり、ファイルを削除したい

(a)パターンのマッチング

(b)2週間以上経過

(c)パターンに一致する最新のディレクトリではない(つまり、最後のディレクトリを保持する)

私の質問は:ファイルパターンに一致する最新のものを除いて、2週間より古いディレクトリ内のすべてのディレクトリを削除する方法

6
hawkeye

前書き

質問が変更されました。

  • 私の最初の選択肢(ワンライナー)は新しい仕様と一致しませんが、削除するのに十分古い(14日より古い)ディレクトリの中で最新のディレクトリを保存します。

  • 私は2番目の選択肢(シェルスクリプト)を作りました

    1970年1月1日00:00 GMTから@秒、小数部分。

    そして、14日に対応する秒を差し引いて、ソートされたディレクトリのリストのseclimにある「秒単位の制限」のタイムスタンプを取得します。

1.ワンライナー

以前の回答は簡潔で見事ですが、最新のfollowerディレクトリは保持されません。次のコマンドラインはそれを実行します(スペースで名前を管理できますが、改行のある名前は問題を引き起こします)

find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r

このディレクトリ構造でテストされ、

$ find -printf "%T+ %p\n"|sort
2019-01-10+13:11:40.6279621810 ./follower1
2019-01-10+13:11:40.6279621810 ./follower1/2/3
2019-01-10+13:11:40.6279621810 ./follower1/2/dirnam with spaces
2019-01-10+13:11:40.6279621810 ./follower1/2/name with spaces
2019-01-10+13:11:56.5968732640 ./follower1/2/file
2019-01-10+13:13:18.3975675510 ./follower2
2019-01-10+13:13:19.4016254340 ./follower3
2019-01-10+13:13:20.4056833250 ./follower4
2019-01-10+13:13:21.4097412230 ./follower5
2019-01-10+13:13:22.4137991260 ./follower6
2019-01-10+13:13:23.4138568040 ./follower7
2019-01-10+13:13:24.4219149500 ./follower8
2019-01-10+13:13:25.4259728780 ./follower9
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 .
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+13:25:03.0751878450 ./follower1/2

そのようです、

$ find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r
rm -r ./follower1 ./follower2 ./follower3 ./follower4 ./follower5 ./follower6 ./follower7 ./follower8

したがって、follower9は最新のfollowerdirectory(名前の付いたディレクトリであり、followerで始まらない(leader1leader2および2はゲームに参加していません)。

次に、時間基準-mtime +14を追加し、実際のfollowerディレクトリがある場所にディレクトリを変更したときに、別の「ドライラン」を実行して、想定どおりに機能することを確認します。

find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r

最後に、echoを削除し、必要なコマンドラインを実行します。

find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs rm -r

  • 現在のディレクトリのfind、名前がfollowerで始まるディレクトリ。14日前から変更されていません。
  • 印刷およびソート後head -n -1は最新のfollowerディレクトリを除外します
  • タイムスタンプは切り取られ、各ディレクトリ名の先頭と末尾に二重引用符が追加されます。
  • 最後に、結果をxargsを介してrm -rへのパラメーターとしてパイプし、削除したいディレクトリを削除します。

2.シェルスクリプト

私は2番目の選択肢(シェルスクリプト)を作りました

@      seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.

また、2つのオプションがあります。

  • -n予行演習
  • -v詳細

  • OPの必要に応じてシェルスクリプトを変更しました:パターンをパラメーターとして入力単一引用符で囲みます。 「フォロワー*」。

  • シェルスクリプトの名前はPrune-dirsにすることをお勧めします。これは、より一般的なためです(プルーンディレクトリPrune-followersに対するfollower*だけではなくなりました)。

何をするかを「確認」するために、両方のオプションを指定してシェルスクリプトを最初に実行することをお勧めします。それが正しいと思われる場合は、-nを削除して、シェルスクリプトが古くなるのに十分古いディレクトリを削除します削除されました。それをPrune-dirsと呼び、実行可能にします。

#!/bin/bash

# date        sign     comment
# 2019-01-11  sudodus  version 1.1
# 2019-01-11  sudodus  enter the pattern as a parameter
# 2019-01-11  sudodus  add usage
# 2019-01-14  sudodus  version 1.2
# 2019-01-14  sudodus  check if any parameter to the command to be performed

# Usage

usage () {
 echo "Remove directories found via the pattern (older than 'datint')

 Usage:    $0 [options] <pattern>
Examples: $0 'follower*'
          $0 -v -n 'follower*'  # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the Shell expands
the wild card (for example the star, '*') before it reaches the shellscript"
 exit
}

# Manage options and parameters

verbose=false
dryrun=false
for i in in "$@"
do
 if [ "$1" == "-v" ]
 then
  verbose=true
  shift
 Elif [ "$1" == "-n" ]
 then
  dryrun=true
  shift
 fi
done
if [ $# -eq 1 ]
then
 pattern="$1"
else
 usage
fi

# Command to be performed on the selected directories

cmd () {
 echo rm -r "$@"
}

# Pattern to search for and limit between directories to remove and keep

#pattern='follower*'
datint=14  # days

tmpdir=$(mktemp -d)
tmpfil1="$tmpdir"/fil1
tmpfil2="$tmpdir"/fil2

secint=$((60*60*24*datint))
seclim=$(date '+%s')
seclim=$((seclim - secint))
printf "%s limit-in-seconds\n" $seclim > "$tmpfil1"

if $verbose
then
 echo "----------------- excluding newest match:"
 find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |tail -n1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/'
fi

# exclude the newest match with 'head -n -1'

find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |head -n -1 >> "$tmpfil1"

# put 'limit-in-seconds' in the correct place in the sorted list and remove the timestamps

sort "$tmpfil1" | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' > "$tmpfil2"

if $verbose
then
 echo "----------------- listing matches with 'limit-in-seconds' in the sorted list:"
 cat "$tmpfil2"
 echo "-----------------"
fi

# create 'remove task' for the directories older than 'limit-in-seconds'

params=
while read filnam
do
 if [ "${filnam/limit-in-seconds}" != "$filnam" ]
 then
  break
 else
  params="$params $filnam"
 fi
done < "$tmpfil2"
cmd $params > "$tmpfil1"
cat  "$tmpfil1"

if ! $dryrun && ! test -z "$params"
then
 bash "$tmpfil1"
fi
rm -r $tmpdir
  • 現在のディレクトリをfollowerサブディレクトリのあるディレクトリに変更します
  • ファイルを作成するPrune-dirs
  • 実行可能にする
  • 2つのオプション-v -nで実行します

    cd directory-with-subdirectories-to-be-pruned/
    nano Prune-dirs  # copy and paste into the editor and save the file
    chmod +x Prune-dirs
    ./Prune-dirs -v -n
    

テスト

findで見られるように、次のサブディレクトリがあるディレクトリでPrune-dirsをテストしました

$ find . -type d -printf "%T+ %p\n"|sort
2018-12-01+02:03:04.0000000000 ./follower1234
2018-12-02+03:04:05.0000000000 ./follower3456
2018-12-03+04:05:06.0000000000 ./follower4567
2018-12-04+05:06:07.0000000000 ./leader8765
2018-12-05+06:07:08.0000000000 ./bystander6789
2018-12-06+07:08:09.0000000000 ./follower with spaces old
2019-01-09+10:11:12.0000000000 ./follower7890
2019-01-10+11:12:13.0000000000 ./follower8901
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+14:08:36.2606738580 ./2
2019-01-10+14:08:36.2606738580 ./2/follower with spaces
2019-01-10+17:33:01.7615641290 ./follower with spaces new
2019-01-10+19:47:19.6519169270 .

使用方法

$ ./Prune-dirs
Remove directories found via the pattern (older than 'datint')

 Usage:    ./Prune-dirs [options] <pattern>
Examples: ./Prune-dirs 'follower*'
          ./Prune-dirs -v -n 'follower*'  # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the Shell expands
the wild card (for example the star, '*') before it reaches the shellscript

-v -n(詳細なドライラン)で実行)

$ ./Prune-dirs -v -n 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"

より一般的なパターンの詳細なドライラン

$ LANG=C ./Prune-dirs -v -n '*er*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./leader8765"
"./bystander6789"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./leader1"
"./leader2"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./leader8765" "./bystander6789" "./follower with spaces old"

オプションなしで実行(実際のケースではディレクトリを削除)

$ ./Prune-dirs 'follower*'
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"

-v '再試行'で実行

$ LANG=C ./Prune-dirs -v 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r

シェルスクリプトは、「制限秒数」以上のディレクトリをリストしておらず、rm -rコマンドライン用のファイルもリストされていないため、作業はすでに完了しています(これは正しい結果です)。ただし、シェルスクリプトを数日後に再度実行すると、「秒数制限」の「上」に新しいディレクトリが見つかり、削除される場合があります。

7
sudodus

zshの場合:

(){ n=$#; } follower<->(/)       # count the number of follower<n> dirs

to_remove=(follower<->(/m+13om)) # assumes the dir list is not changed
                                 # since the previous command

(($#to_remove < n)) || to_remove[1]=() # keep the youngest if they're
                                       # all over 2 weeks old



echo rm -rf $to_remove

(幸せならechoを削除してください)

  • <->任意の10進数字のシーケンス(<1-20>の省略形は制限なし)。
  • (){code} args:引数の数を$nに保存する匿名関数。
  • (/omm+13):グロブ修飾子
  • /:タイプdirectoryのファイルのみを選択(find-type dと同等)
  • m+13:丸1日の経過時間が13日より厳密に大きいファイルなので、14日以上経過したファイル(find-mtime +13に相当)。
  • om:変更時間で並べ替え(ls -tより新しいファイルを最初に指定するなど)

ディレクトリの変更時間に依存することは危険であることに注意してください。ファイルが追加、削除、または名前が変更されると(またはそれらがtouchedになると)、ディレクトリが変更されます。これらのディレクトリには番号が付いているため、代わりにその番号を使用することをお勧めします。そのため、omnOnに置き換えます(numerically Order in reverse(capital O)by name)。

変数にパターンを含めるには、follower<->$~patternで置き換え、pattern='follower<->'またはその他の値を設定します。

5

ナナカマドの答えを補完します。ディレクトリへのパスでドットを変更できます

find . -type d -name follower* -mtime +14 -exec rm -rf {} +;
3

時間に関連するファイルやディレクトリを削除する必要があるときは、findを使用します。

何かを削除する前に、コマンドを数回実行して、必要なものがすべて見つかるかどうかを確認できます。

find . -type d -mtime +14
# -type c, File is of type c: d  directory
# -mtime n, File's data was last modified n*24 hours ago.

すべての条件に一致する場合は、後ろに-exec rm -r {} +を追加できます。

find . -type d -mtime +14 -exec rm -r {} +

ここで-execを使用しているのは、ディレクトリが空でないと-deleteが機能しないためです。

詳細については、man findをご覧ください。

2
rowan

いくつかの解決策:

1. GNU findに基づく:

_#!/bin/bash

# The pattern has to be available to subshells
export patt="$1"

find . -maxdepth 1 -type d -name "${patt}*" -mtime +14 \
  -exec sh -c '[ "$(find . -maxdepth 1 -type d -name "${patt}*" -print0 |
    sort -z -V |
    tail -z -n 1 |
    tr -d "\0")" != "$1" ]' sh {} \; \
  -exec sh -c 'echo rm -r "$1"' sh {} \;
_

スクリプトは次のように呼び出されることを意図しています。

_./script name_pattern
_

現状のまま、ドライランを提供します。最後の_-exec_アクションのechoを削除して、実際にディレクトリを削除できるようにします。

そうなる:

  • 14日以上前に変更された現在のディレクトリ内のすべてのディレクトリを検索し(ただし、以下の_-mtime_に関する注記を参照)、値が_${patt}_で始まる名前を付けます。それぞれ:
  • (最初の_-exec_)が、見つかったディレクトリが名前パターンに一致する最後のディレクトリではないことを確認し、 version の昇順でソートします(_-V_)( 、たとえば、_follower100_の後に_follower2_を配置します。テスト(_[_)が失敗した場合、findは次のサイクルにスキップし、その後のアクションを実行しません。
  • 見つかったディレクトリ(2番目の_-exec_)を削除します。

ここでは、ディレクトリを辞書式順序で名前でソートすることと、変更日でソートすることの同等性を想定しています。 latest ディレクトリがその名前で定義されている場合、これは問題ありません。
代わりに、 latest ディレクトリが最新の変更時刻のディレクトリである場合、上記のコードの最初の_-exec ..._をこれに置き換える必要があります1:

_  -exec sh -c '[ "$(find . -maxdepth 1 -type d -name "${patt}*" -printf "%T@\n" |
    sed "s/\..*$//" |
    sort -n |
    tail -n 1)" != "$(stat -c "%Y" "$1")" ]' sh {} \; \
_

内側のfindを使用すると、名前パターンに一致するすべてのディレクトリが見つかります。エポック以降の変更時間のリストを秒単位で出力し、小数部分を切り取り、並べ替えて、最後のものを取り出し、それがそうでないことを確認します外側のfindの現在の結果と同じです。

このフィルターを使用する場合、一致するすべてのディレクトリーが14日よりも古く、すべて変更時刻がまったく同じである場合は、それらが削除されることはありません。


ノート:

検索を現在のディレクトリ(_-maxdepth 1_)のコンテンツに制限することは厳密には必要ありません。

sortに注文方法を伝えたい場合があります。スクリプトの先頭に_export LC_ALL=C_を追加します( を参照してください)ソート時に発生する可能性のある問題について、「「LC_ALL = C」は何をするのか」 への回答、ローカリゼーション設定によって異なります)。

_-mtime +14_を使用すると、14〜15日前に変更されたファイルは、技術的に now から14 * 24時間以上経過していてもスキップされます。詳細については、_man find_、特に_-atime n_の説明を参照してください。

名前にスペース、改行、一般的でなく印刷できない文字が含まれている場合でも機能します。

互換性:逆に、移植性がない:ここで使用されているいくつかの機能、特にfindの_-maxdepth_、_-print0_および_-printf_、statコマンド、sortへの_-V_オプション、およびsorttailへの_-z_オプション(およびいくつか忘れている可能性があります)が指定されていませんPOSIX。

2.シェル機能に基づく

_#!/bin/sh

patt="$1"                 # The name pattern
test -z "$patt" && exit   # Caution: pattern must not be empty

days=14     # How old has to be a directory to get deleted, in days?
last=       # The youngest directory

dirs=( "$patt"* )     # Array of files matched by name (note, here we
                      # have everything that matches, not just dirs)
now="$(date +'%s')"   # Now in seconds since Epoch

threshold="$(( "$now" - ( "$days" * 24 * 60 *60 ) ))"
                      # Dirs older than this date (in seconds since
                      # Epoch) are candidates for deletion

# We find the youngest directory in the array
#
for i in "${!dirs[@]}"; do
  if  [ -z "$last" ] ||
    ( [ -d "${dirs[$i]}" ] &&
      [ "$(stat -c '%Y' -- "${dirs[$i]}")" -gt "$(stat -c '%Y' -- "$last")" ] ); then
    last="${dirs[$i]}"
  fi
done

# We delete all the directories in the array that are
# not the youngest one AND are older that the thrashold
#
for i in "${!dirs[@]}"; do
  if  [ -d "${dirs[$i]}" ] &&
      [ "${dirs[$i]}" != "$last" ] &&
      [ "$(stat -c '%Y' -- "${dirs[$i]}")" -lt "$threshold" ]; then
    echo rm -rf -- "${dirs[$i]}"
  fi
done
_

このスクリプトも、次のように呼び出すことを意図しています。

_./script name_pattern
_

繰り返しますが、_echo rm -rf -- "${dirs[$i]}"_からechoを削除するまで、予行演習が行われます。

そうなる:

  • 現在のディレクトリで、名前パターンに一致するすべてのファイルの名前を配列に入力します。
  • アレイ内の最も新しいディレクトリを特定します。
  • 1)14日以上経過していて、かつ2)最も新しいディレクトリではない配列内のすべてのディレクトリを削除します。

ノート:

now から14日以上経過したディレクトリを対象とします(findとは異なります)。したがって、これらの2つのソリューションは厳密に等価ではありません。
また、一致するすべてのディレクトリがしきい値よりも古く、変更時間がすべて同じ場合、ランダムに選択された1つを除いてすべて削除されます。

改行や印刷できないものも含め、一般的でない文字を含む名前は問題ありません。

互換性:このソリューションでさえ、いくつかの非POSIX機能、つまりstatおよび_%s_ date形式に依存しています。ああ、 arrays 、どうやら...

2
fra-san