最も古いファイルを移動するためのシェルスクリプトですか?


14

あるフォルダから別のフォルダに最も古い20個のファイルだけを移動するスクリプトを作成するにはどうすればよいですか?フォルダー内の最も古いファイルを取得する方法はありますか?


サブディレクトリを含めるか除外しますか?そして、それは再帰的に(ディレクトリツリーで)行われるべきですか?
-maxschlepzig

2
多くの(ほとんど?)* nixファイルシステムは作成日を保存しないため、最も古いファイルを確実に判別することはできません。通常使用可能な属性は、atime(最終アクセス)、ctime(最終アクセス許可の変更)、およびmtime(最終変更)です。ls -tそして検索の printf "%T"使用mtime...によると、思えるこのリンク私のことを、ext4パーティションがある可能 作成日付を扱うのが、lsfindstat...(まだ)適切なオプションを持っていない
Peter.O

回答:


13

出力を解析することls信頼性がありません

代わりに、findファイルを見つけてsortタイムスタンプ順に並べるために使用します。例えば:

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    # do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

これは何をしているのですか?

最初に、findコマンドは現在のディレクトリ内のすべてのファイルとディレクトリを検索し(.)、現在のディレクトリのサブディレクトリ内ではなく(-maxdepth 1)、次に出力します。

  • タイムスタンプ
  • 空間
  • ファイルへの相対パス
  • NULL文字

タイムスタンプは重要です。の%T@形式指定子は、ファイルの「最終変更時刻」(mtime)を示す、および小数秒を含む「1970年からの秒」を示すに-printf分割されTます@

スペースは単なる任意の区切り文字です。ファイルへの完全なパスは後で参照できるようにするためです。また、NULL文字はファイル名に含まれる不正な文字であるため、ターミネータです。ファイル。

私が含まれている2>/dev/nullユーザーがアクセス許可を持っていないファイルが除外されますが、それらについてのエラーメッセージが抑制され、除外されているように。

findコマンドの結果は、現在のディレクトリ内のすべてのディレクトリのリストです。リストは次のsortように指示されているパイプされます。

  • -z NULLを改行ではなく行終端文字として扱います。
  • -n 数値で並べ替え

1970年以降の秒数は常に増加するため、タイムスタンプが最小のファイルが必要です。からの最初の結果はsort、最小番号のタイムスタンプを含む行になります。あとは、ファイル名を抽出するだけです。

結果はfindsortパイプラインが経由して渡されたプロセス置換するwhileことが標準入力上のファイルであるかのように、それが読み込まれる場所。while次にread、入力を処理するために呼び出します。

このコンテキストでreadは、IFS変数を何も設定しないため、空白は区切り文字として不適切に解釈されません。read言われる-rエスケープ拡張を無効にしている、と-d $'\0'私たちからの出力をマッチング、行末区切りNULLを作るこれ、findsortパイプライン。

タイムスタンプとスペースが先頭にある最も古いファイルパスを表す最初のデータチャンクは、変数に読み込まれますline。次に、式でパラメータ置換が使用されます#*。これは、文字列の先頭からスペースを含む最初のスペースまでのすべての文字を単に置換します。これにより、変更タイムスタンプが取り除かれ、ファイルへのフルパスのみが残ります。

この時点でファイル名が保存され$file、好きなことをすることができます。あなたがやって終わったらで何か文がループとコマンドは次のチャンクと次のファイル名を抽出し、再実行されます。$filewhileread

もっと簡単な方法はありませんか?

いいえ。簡単な方法にはバグがあります。

を使用ls -tしてheadorまたはtail(または何か)にパイプすると、ファイル名に改行があるファイルで中断します。あなたがいる場合mv $(anything)、名前に空白を持つファイルの破損の原因になります。あなたは場合mv "$(anything)"、名前に改行を末尾に持つファイルが破損の原因になります。そうしreadない-d $'\0'と、名前に空白文字が含まれるファイルで中断します。

おそらく特定のケースでは、より簡単な方法で十分であることが確実にわかっていますが、そうすることを避けることができるなら、そのような仮定をスクリプトに書かないでください。

解決

#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    echo mv "$file" "$dest"
    let limit-=1
    [[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

次のように呼び出します:

move-oldest /mnt/backup/ /var/log/foo/ 20

最も古い20個のファイルをから/var/log/foo/に移動するには/mnt/backup/

ファイルディレクトリを含めていることに注意してください。ファイルの場合のみ-type ffind呼び出しに追加します。

ありがとう

おかげenzotibПавелТанковこの回答への改善のために。


並べ替えにはを使用しないでください-n。少なくとも私のバージョンでは、小数が正しくソートされません。日付のドットを削除するか-printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz、ISO 日付などを使用する必要があります。
l0b0

@ l0b0:この制限は私に知られています。私は、そのレベルの粒度を要求しないことで十分であると推測します(つまり、それ以上の並べ替えは.あなたにとって無関係である必要があります)sort -z -n -t. -k1
ソルピガル

@ l0b0:ソリューションに関係なく、同じバグ%TSが発生し00.0000000000ます。フォームにある「小数部」も表示されるため、粒度も失われます。最近のGNU sortは、この-Vバージョンの浮動小数点を期待どおりに処理する「バージョンソート」を使用することで、この問題を解決できます。
ソルピガル

いいえ、数値ソートではなく「YYYY-MM-DDThh:mm:ss」で文字列ソートを行うためです。文字列ソートは小数を
考慮

@ l0b0:文字列の並べ替え%T@も機能します。ゼロが埋め込まれているためです。
ソルピガル

4

zshでは最も簡単で、Om glob修飾子を使用して日付(最も古いもの)で一致を並べ替え、[1,20]修飾子を使用して最初の20個の一致のみを保持できます。

mv -- *(Om[1,20]) target/

Dドットファイルも含める場合は、修飾子を追加します。.ディレクトリではなく通常のファイルのみに一致させる場合に追加します。

zshがない場合は、Perlのワンライナーがあります(80文字未満で実行できますが、わかりやすくするにはさらにコストがかかります)。

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

POSIXツールのみ、またはbashやkshでさえ、ファイルを日付順に並べ替えるのは面倒です。で簡単にできますlsが、の出力の解析にlsは問題があるため、ファイル名に改行以外の印刷可能な文字のみが含まれている場合にのみ機能します。

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done

4

ls -t出力をtailまたはと組み合わせますhead

単純な例。すべてのファイル名に空白文字と\[*?

 mv $(ls -1tr | head -20) other_folder

1
-Aオプションをlsに追加しますls -1Atr
。– Arcege

1
-1、危険です。ここで例を作りましょうtouch $'foo\n*'。そこにあるファイルでmv "$(ls)"を実行するとどうなりますか?
ソルピガル

1
@マジで?それは言って親切弱いのだ「私はあなたが、具体的な作業ではないと述べた例を思い付くしてみましょうねえ見て、そうでない作品。」
マイケル・Mrozek

1
@Sorpigal悪い考えではなく、99%のケースで機能します。答えは、「通常の名前のファイルがある場合、これは機能します。ファイル名に改行を埋め込む非常識な人なら、機能しません」です。それは完全に正しいです
マイケルMrozek

1
@MichaelMrozek:それは悪い考えであり、時々失敗するので悪いことです。時々失敗するものとしないものを行うオプションがある場合は、失敗しないオプションを選択する必要があります(そして、失敗するオプションは悪いです)。インタラクティブに好きなことを行いますが、スクリプトファイルで、アドバイスを与えるときは正しく行います。
ソルピガル

2

これにはGNU findを使用できます。

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

findが変更時刻(1970年からの秒数)と現在のディレクトリの各ファイルの名前を出力する場合、出力は最初のフィールドに従ってソートされ、最も古い20がフィルタリングされてに移動されdest_dirます。echoコマンドラインをテストした場合は削除します。


2

ファイル名に埋め込まれた改行文字(何でも埋め込まれる)に対応するbashの例を(まだ)誰も投稿していないので、ここにあります。3つの最も古い(mdate)通常ファイルを移動します

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p\0" |sort -znk1 | { 
  while IFS= read -d $'\0' -r file; do
      printf "%s\0" "${file#*$'\t'}"
      ((--move==0)) && break
  done } |xargs -0 mv -t dest

これはテストデータスニペットです

# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest

ここにチェック結果スニペットがあります

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*

+1、私の前の唯一の有用な答え(そしてテストデータを持っていることは常に良いことです。)
ソルピガル

0

GNUを使用するのが最も簡単findです。Linux DVRで毎日使用して、1日より古いビデオ監視システムから録画を削除します。

構文は次のとおりです。

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

find実行時から24時間として1日を定義していることに注意してください。したがって、午後11時に最後に変更されたファイルは午前1時に削除されません。

と組み合わせfindcron、ルートとして次のコマンドを実行することにより、削除を自動的にスケジュールすることもできます。

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

findマニュアルページを参照すると、いつでも詳細情報を入手できます。

man find

0

他の回答は私と質問の目的に合わないため、このシェルはCentOS 7でテストされています。

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.