あるフォルダから別のフォルダに最も古い20個のファイルだけを移動するスクリプトを作成するにはどうすればよいですか?フォルダー内の最も古いファイルを取得する方法はありますか?
あるフォルダから別のフォルダに最も古い20個のファイルだけを移動するスクリプトを作成するにはどうすればよいですか?フォルダー内の最も古いファイルを取得する方法はありますか?
回答:
出力を解析すること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
)、次に出力します。
タイムスタンプは重要です。の%T@
形式指定子は、ファイルの「最終変更時刻」(mtime)を示す、および小数秒を含む「1970年からの秒」を示すに-printf
分割されT
ます@
。
スペースは単なる任意の区切り文字です。ファイルへの完全なパスは後で参照できるようにするためです。また、NULL文字はファイル名に含まれる不正な文字であるため、ターミネータです。ファイル。
私が含まれている2>/dev/null
ユーザーがアクセス許可を持っていないファイルが除外されますが、それらについてのエラーメッセージが抑制され、除外されているように。
find
コマンドの結果は、現在のディレクトリ内のすべてのディレクトリのリストです。リストは次のsort
ように指示されているパイプされます。
-z
NULLを改行ではなく行終端文字として扱います。-n
数値で並べ替え1970年以降の秒数は常に増加するため、タイムスタンプが最小のファイルが必要です。からの最初の結果はsort
、最小番号のタイムスタンプを含む行になります。あとは、ファイル名を抽出するだけです。
結果はfind
、sort
パイプラインが経由して渡されたプロセス置換するwhile
ことが標準入力上のファイルであるかのように、それが読み込まれる場所。while
次にread
、入力を処理するために呼び出します。
このコンテキストでread
は、IFS
変数を何も設定しないため、空白は区切り文字として不適切に解釈されません。read
言われる-r
エスケープ拡張を無効にしている、と-d $'\0'
私たちからの出力をマッチング、行末区切りNULLを作るこれ、find
、sort
パイプライン。
タイムスタンプとスペースが先頭にある最も古いファイルパスを表す最初のデータチャンクは、変数に読み込まれますline
。次に、式でパラメータ置換が使用されます#*
。これは、文字列の先頭からスペースを含む最初のスペースまでのすべての文字を単に置換します。これにより、変更タイムスタンプが取り除かれ、ファイルへのフルパスのみが残ります。
この時点でファイル名が保存され$file
、好きなことをすることができます。あなたがやって終わったらで何か文がループとコマンドは次のチャンクと次のファイル名を抽出し、再実行されます。$file
while
read
いいえ。簡単な方法にはバグがあります。
を使用ls -t
してhead
orまたは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 f
、find
呼び出しに追加します。
おかげenzotibとПавелТанковこの回答への改善のために。
-n
。少なくとも私のバージョンでは、小数が正しくソートされません。日付のドットを削除するか-printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz
、ISO 日付などを使用する必要があります。
.
あなたにとって無関係である必要があります)sort -z -n -t. -k1
。
%TS
が発生し00.0000000000
ます。フォームにある「小数部」も表示されるため、粒度も失われます。最近のGNU sort
は、この-V
バージョンの浮動小数点を期待どおりに処理する「バージョンソート」を使用することで、この問題を解決できます。
%T@
も機能します。ゼロが埋め込まれているためです。
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
ls -t
出力をtail
またはと組み合わせますhead
。
単純な例。すべてのファイル名に空白文字と\[*?
:
mv $(ls -1tr | head -20) other_folder
ls -1Atr
touch $'foo\n*'
。そこにあるファイルでmv "$(ls)"を実行するとどうなりますか?
これには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
コマンドラインをテストした場合は削除します。
ファイル名に埋め込まれた改行文字(何でも埋め込まれる)に対応する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/*
GNUを使用するのが最も簡単find
です。Linux DVRで毎日使用して、1日より古いビデオ監視システムから録画を削除します。
構文は次のとおりです。
find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
find
実行時から24時間として1日を定義していることに注意してください。したがって、午後11時に最後に変更されたファイルは午前1時に削除されません。
と組み合わせfind
てcron
、ルートとして次のコマンドを実行することにより、削除を自動的にスケジュールすることもできます。
crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF
find
マニュアルページを参照すると、いつでも詳細情報を入手できます。
man find
他の回答は私と質問の目的に合わないため、このシェルは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"