web-dev-qa-db-ja.com

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

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

14
user11598

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@_の_-printf_形式指定子は、ファイルの「最終変更時刻」(mtime)を示すTと、小数秒を含む「1970年からの秒数」を示す_@_に分類されます。 。

スペースは単なる任意の区切り文字です。ファイルへのフルパスは後で参照できるようにするためのものであり、NULL文字はファイル名に含まれる不正な文字であるためターミネーターであり、そのため、パスへのパスの最後に到達したことを確実に知らせますファイル。

_2>/dev/null_を含めたのは、ユーザーがアクセスする権限を持っていないファイルは除外されますが、除外されているというエラーメッセージは抑制されます。

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

  • _-z_ NULLを改行ではなく行終了文字として扱います。
  • _-n_数値で並べ替え

Seconds-since-1970が常に上がるので、タイムスタンプが最も小さい番号のファイルが必要です。 sortの最初の結果は、最も小さい番号のタイムスタンプを含む行になります。あとは、ファイル名を抽出するだけです。

findsortパイプラインの結果は プロセス置換 を介してwhileに渡され、そこでstdin上のファイルであるかのように読み取られます。次に、whilereadを呼び出して入力を処理します。

readのコンテキストでは、IFS変数に何も設定しません。これは、空白が区切り文字として不適切に解釈されないことを意味します。 readには、エスケープ拡張を無効にする_-r_と、行末の区切り文字をNULLにする_-d $'\0'_が通知され、findsortパイプラインからの出力と一致します。

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

この時点で、ファイル名は_$file_に保存され、好きなように操作できます。 _$file_の処理が完了すると、whileステートメントがループし、readコマンドが再度実行され、次のチャンクと次のファイル名が抽出されます。

より簡単な方法はありませんか?

いいえ。より単純な方法はバグがあります。

_ls -t_を使用し、headまたはtail(またはanything)にパイプすると、ファイル名に改行が含まれるファイルで中断されます。 mv $(anything)の場合、名前に空白が含まれているファイルは破損の原因になります。 mv "$(anything)"の場合、名前の末尾に改行が含まれるファイルは破損します。 _-d $'\0'_なしでreadを使用すると、名前に空白が含まれているファイルが壊れます。

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

解決

_#!/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ПавелТанков に感謝します。

13
Sorpigal

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

すべてのファイル名に空白と\[*?以外の印刷可能な文字のみが含まれている場合にのみ機能する簡単な例:

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

これはzshで最も簡単で、Omglob qualifier を使用して、一致を日付(最も古いもの)と[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

GNU find for this:

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を削除します。

3
maxschlepzig

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

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

これはtest-dataスニペットです

# 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

これがcheck-resultsスニペットです

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*
2
Peter.O

GNU findを使用するのが最も簡単です。Linuxで毎日使用していますDVRは、1日以上前のビデオ監視システムから記録を削除するために使用します。

構文は次のとおりです。

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

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

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

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

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

man find
0
Jonathan Frank

他の回答は私や質問の目的に合わないため、このシェルは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"
0
Spektakulatius