177

bash を使用したかなり標準的な UNIX 環境で、ディレクトリから最新の X ファイル以外をすべて削除するコマンドを実行する簡単な方法はありますか?

もう少し具体的な例を挙げると、cron ジョブが 1 時間ごとにディレクトリにファイル (たとえば、ログ ファイルまたは tar されたバックアップ) を書き出すとします。別のcronジョブを実行して、そのディレクトリ内の最も古いファイルを、たとえば5未満になるまで削除する方法が欲しい.

明確にするために、存在するファイルは 1 つだけであり、決して削除しないでください。

4

16 に答える 16

157

既存の回答の問題:

  • スペースまたは改行が埋め込まれたファイル名を処理できない。
    • rm引用符で囲まれていないコマンド置換 ( ) で直接呼び出すソリューションの場合、rm `...`意図しないグロビングのリスクが追加されます。
  • ファイルとディレクトリを区別できない (つまり、ディレクトリがたまたま最近変更された 5 つのファイルシステム項目の中にあった場合、実質的に保持されるファイルは 5 つ未満になり、ディレクトリへの適用rmは失敗します)。

wnoise の回答はこれらの問題に対処していますが、解決策はGNU固有です (そして非常に複雑です)。

これは実用的なPOSIX 準拠のソリューションですが、注意点が 1 つだけあります:改行が埋め込まれたファイル名を処理することはできませんが、ほとんどの人にとって現実世界の懸念事項ではないと思います。

ls記録として、出力を解析することが一般的に良い考えではない理由の説明を以下に示します: http://mywiki.wooledge.org/ParsingLs

ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}

注: このコマンドは現在のディレクトリで動作します。ディレクトリを明示的(...)cdに対象にするには、subshel​​l( ) を
(cd /path/to && ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {})
のように使用します。以下のコマンドにも同様に適用されます

上記は、ファイル名ごとに個別に呼び出す必要があるため、非効率的です。 ただし、プラットフォーム固有の実装により、この問題を解決できる場合があります。xargsrm
xargs


GNUで動作 xargsする解決策は、 を使用すること-d '\n'です。これにより、xargs各入力行が個別の引数と見なされますが、一度にコマンド ラインに収まる数の引数が渡されます。

ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --

注: Option -r( --no-run-if-empty) は、inputrmがない場合に が呼び出されないようにします。

GNUBSD ( macOSを含む)の両方で動作 xargs xargsする解決策は、技術的にはまだPOSIXに準拠していませんが、最初に改行を( ) 文字に変換した後、 -separated 入力を処理するために使用することです。これにより、(通常は) すべてのファイル名も渡されます。一度に:-0NULNUL0x0

ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --

説明:

  • ls -tp最近変更されたファイルシステム項目の名前を降順 (最近変更された項目が最初) に並べ替えて ( -t)、そのことを示す末尾にディレクトリを付けて出力/します ( -p)。

    • 注: 現在のディレクトリ以外のディレクトリをターゲットにするために上記のサブシェル アプローチが必要になるのは、フル パスではなく、ファイル/ディレクトリls -tpのみを常に出力するという事実です( )。(cd /path/to && ls -tp ...)
  • grep -v '/$'-v次に、末尾に/( ) がある行( ) を省略して、結果のリストからディレクトリを除外し/$ます。

    • 警告:ディレクトリを指すシンボリック リンクは、技術的にはそれ自体がディレクトリではないため、そのようなシンボリック リンクは除外されません
  • tail -n +6リストの最初の5 つのエントリをスキップし、実際には、最近変更された 5 つのファイルを除くすべてのファイルを返します。ファイル
    を除外するには、を に渡す必要があることに注意してください。NN+1tail -n +

  • xargs -I {} rm -- {}rm(およびそのバリエーション) は、これらすべてのファイルに対して onを呼び出します。一致するものがまったくない場合は、xargs何もしません。

    • xargs -I {} rm -- {}は、各入力行を全体として{}表すプレースホルダーを定義するため、入力行ごとに 1 回呼び出されますが、スペースが埋め込まれたファイル名は正しく処理されます。rm
    • --すべての場合において、で始まるファイル-が.rm

一致するファイルを個別に処理するか、シェル配列に収集する必要がある場合の、元の問題のバリエーション:

# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done

# One by one, but using a Bash process substitution (<(...), 
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)

# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements
于 2016-01-18T19:24:33.597 に答える
119

ディレクトリ内の最新のファイルのうち、5 つ (または任意の数) を除くすべてを削除します。

rm `ls -t | awk 'NR>5'`
于 2008-08-25T08:41:24.227 に答える
89
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm

このバージョンでは、スペースを含む名前がサポートされています。

(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
于 2008-08-25T08:42:05.487 に答える
68

thelsdj の答えのより単純な変形:

ls -tr | head -n -5 | xargs --no-run-if-empty rm 

ls -tr は、すべてのファイルを最も古いものから順に表示します (-t は新しいもの、-r は逆)。

head -n -5 は、最後の 5 行 (つまり、最新の 5 ファイル) を除くすべてを表示します。

xargs rm は、選択された各ファイルに対して rm を呼び出します。

于 2012-04-12T08:25:06.247 に答える
18
find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f

-printf には GNU find、-z には GNU sort、"\0" には GNU awk、-0 には GNU xargs が必要ですが、改行またはスペースが埋め込まれたファイルを処理します。

于 2008-11-18T19:51:39.977 に答える
14

現在のディレクトリにディレクトリがある場合、これらの回答はすべて失敗します。動作するものは次のとおりです。

find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm

これ:

  1. 現在のディレクトリにディレクトリがある場合に機能します

  2. 前のファイルを削除できなかった場合でも(権限などのために)各ファイルを削除しようとします

  3. 現在のディレクトリ内のファイルの数が多すぎて、xargs通常はあなたを台無しにする場合、フェイルセーフは失敗します(-x

  4. ファイル名のスペースに対応していません(おそらく間違ったOSを使用していますか?)

于 2008-11-18T17:44:25.383 に答える
13
ls -tQ | tail -n+4 | xargs rm

各ファイル名を引用して、ファイル名を変更時刻順にリストします。最初の 3 つ (最新の 3 つ) を除外します。残りを取り除きます。

mklement0 からの有益なコメントの後の編集 (ありがとう!): -n+3 引数を修正しました。ファイル名に改行が含まれている場合、および/またはディレクトリにサブディレクトリが含まれている場合、これは期待どおりに機能しないことに注意してください。

于 2013-07-25T05:51:10.667 に答える
8

改行を無視することは、セキュリティと優れたコーディングを無視することです。wnoise には唯一の良い答えがありました。これは、ファイル名を配列 $x に入れる彼のバリエーションです

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
于 2009-06-13T12:03:34.263 に答える
4

ファイル名にスペースが含まれていない場合、これは機能します。

ls -C1 -t| awk 'NR>5'|xargs rm

ファイル名にスペースが含まれている場合、次のようなものです

ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh

基本的なロジック:

  • ファイルのリストを時系列で 1 列に取得します
  • 最初の 5 つを除くすべてを取得 (この例では n=5)
  • 最初のバージョン: それらを rm に送信します
  • 2 番目のバージョン: それらを適切に削除するスクリプトを生成します
于 2008-08-25T08:43:44.830 に答える
4

これは古いスレッドだと思いますが、誰かがこれから恩恵を受けるかもしれません。このコマンドは、現在のディレクトリ内のファイルを検索します:

for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done

これは、検索ドメインを式に一致するファイルに制限できるため、以前の回答のいくつかよりも少し堅牢です。まず、必要な条件に一致するファイルを見つけます。それらのファイルの横にタイムスタンプを付けて印刷します。

find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'

次に、タイムスタンプで並べ替えます。

sort -r -z -n

次に、リストから最新の 4 つのファイルを削除します。

tail -n+5

2 番目の列 (タイムスタンプではなくファイル名) を取得します。

awk '{ print $2; }'

そして、その全体を for ステートメントにまとめます。

for F in $(); do rm $F; done

これはより冗長なコマンドかもしれませんが、条件付きファイルをターゲットにして、それらに対してより複雑なコマンドを実行できるという幸運に恵まれました。

于 2017-01-10T22:55:39.533 に答える
2

zshを使用

現在のディレクトリを気にせず、999 個を超えるファイルを持たないと仮定します (必要に応じてより大きな数を選択するか、while ループを作成します)。

[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])

では*(.om[6,999]).平均ファイル、o平均ソート順、m平均変更日 (aアクセス時間またはcinode 変更のために配置)、[6,999]はファイルの範囲を選択するため、最初に 5 を RM しません。

于 2011-11-21T18:08:20.997 に答える
1

Sed-Onliners で興味深い cmd を見つけました - 最後の 3 行を削除します - 猫の皮を剥ぐ別の方法に最適だと思います (大丈夫ではありません) が、アイデア:

 #!/bin/bash
 # sed cmd chng #2 to value file wish to retain

 cd /opt/depot 

 ls -1 MyMintFiles*.zip > BigList
 sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList

 for i in `cat DeList` 
 do 
 echo "Deleted $i" 
 rm -f $i  
 #echo "File(s) gonzo " 
 #read junk 
 done 
 exit 0
于 2016-09-01T21:09:57.413 に答える
0
leaveCount=5
fileCount=$(ls -1 *.log | wc -l)
tailCount=$((fileCount - leaveCount))

# avoid negative tail argument
[[ $tailCount < 0 ]] && tailCount=0

ls -t *.log | tail -$tailCount | xargs rm -f
于 2013-06-07T07:34:08.353 に答える