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

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

最も古いファイルの20個をあるフォルダから別のフォルダに移動するスクリプトを作成するには?フォルダから最も古いファイルをインポートする方法はありますか?

答え1

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表します。

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

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

find、パイプラインの結果はsort以下を通過します。プロセスの交換whileまるで標準入力のファイルのように読み込まれるところです。入力を処理するためにwhile順番に呼び出されますread

read変数をスペースに設定した文脈では、スペースはIFS区切り文字として不適切に解釈されないことを意味します。エスケープ拡張を無効にし、行末の区切り文字をNULLにしてパイプの出力と一致させることがreadわかります。-r-d $'\0'findsort

最初のデータブロックは、タイムスタンプとスペースが先頭にある最も古いファイルパスを表し、変数として読み込まれますline。次に、パラメータの置換式と共に使用すると、#*文字列の先頭から最初のスペースを含むすべての文字をnullに置き換えます。これにより、修正タイムスタンプが削除され、ファイルのフルパスのみが残ります。

この時点でファイル名が保存され、$file必要に応じて何でもできます。何かが完了すると、$fileステートメントwhileが繰り返され、readコマンドが再度実行され、次のチャンクと次のファイル名が抽出されます。

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

いいえ。より簡単なアプローチには欠陥があります。

使用ls -tしてパイプする場合(headまたは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

ありがとう

ありがとうエンゾチップそしてパベル・タニコフこの回答が改善されました。

答え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 個がフィルタリングされ、OK コマンドをテストした場合に移動されますdest_direcho削除してください。

答え3

利用可能なzshで最も簡単です。Om グローバル予選日付(最も古いものから)と修飾子で一致をソートして、上位[1,20]20の一致のみを維持します。

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

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

zshがなければ、Perlの1行のコードがあります(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

ファイル名に改行文字(何でも含む)を含める必要がある要件を満たすbashの例をまだ公開していないので、ここに1つを投稿します。最も古い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/*

関連情報