bashの最新のXファイル以外をすべて削除します


157

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

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

また、明確にするために、ファイルは1つしか存在しないため、削除しないでください。

回答:


117

既存の回答の問題:

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

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

ここでは、警告が1つだけ付いた実用的なPOSIX準拠のソリューションを示します。改行が埋め込まれたファイル名は処理できませんが、実際の問題はほとんどの人には関係ないと思います。

参考までに、ls出力を解析するのが一般によくない理由の説明は次のとおりです。http//mywiki.wooledge.org/ParsingLs

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

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

GNU xargsを使用している場合は、を使用します-d '\n'。これによりxargs、各入力行を個別の引数と見なしますが、コマンドラインに一度に収まるだけの数の引数を渡します。

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

-r--no-run-if-empty)は、rm入力がない場合に呼び出されないようにします。

あなたが持っている場合はBSD xargs(上を含むMacOSのを)は、使用することができます-0処理するためNULに最初の翻訳改行の後に、区切られた入力をNUL0x0)文字を、また、(通常は)通過するすべてのファイル名。一度(意志もGNUとの仕事xargs):

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

説明:

  • ls -tpファイルシステムアイテムの名前を、それらが最後に変更された順に並べ替えて、降順で(最も最近変更されたアイテムを最初に)(-t/表示します-p
  • grep -v '/$'次に-v、末尾に//$)がある行()を省略して、結果のリストからディレクトリを削除します。
    • 警告ディレクトリを指すシンボリックリンクは、それ自体がディレクトリではないため、そのようなシンボリックリンクは除外されません。
  • tail -n +6リストの最初の5つのエントリをスキップし、実際には、最後に変更された5つのファイルを除いてすべてを返します。ファイル
    を除外するNN+1は、に渡す必要があることに注意してくださいtail -n +
  • xargs -I {} rm -- {}(およびそのバリエーション)次にrm、これらすべてのファイルを呼び出します。一致するものがまったくない場合、xargs何もしません。
    • xargs -I {} rm -- {}定義プレースホルダ{}各入力ラインを表し、全体としては、そう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

2
ここで他のほとんどの答えよりも確かに良いので、改行のケースを無視して慎重に行うべきことであると考えているとしても、私は私のサポートを喜んで貸してくれます。
Charles Duffy

2
ls現在のディレクトリにいない場合、ファイルへのパスには「/」が含まれますが、これはgrep -v '/'何にも一致しません。私grep -v '/$'はあなたがディレクトリだけを除外したいものだと信じています。
waldol1 2016

1
@ waldol1:ありがとうございます。回答を更新して、提案を含めました。これにより、grepコマンドが概念的にも明確になります。ただし、ここで説明する問題は単一のディレクトリパスでは発生しないことに注意してください。たとえば、ls -p /private/varまだファイル名だけを出力します。複数のファイル引数を(通常はグロブを介して)渡した場合にのみ、出力に実際のパスが表示されます。たとえば、ls -p /private/var/*(も含めない限り、一致するサブディレクトリの内容も表示されます-d)。
mklement0 2016

108

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

rm `ls -t | awk 'NR>5'`

2
アーカイブファイルのみを考慮するためにこれが必要でした。変更ls -tls -td *.bz2
ジェームズ・T・スネル

3
私はこれをrm -rfに変更することでディレクトリに使用しましたls -t | awk 'NR>1'(最新のものだけが必要でした)。ありがとう!
lohiaguitar91 2014

11
ls -t | awk 'NR>5' | xargs rm -f パイプが必要で、削除するものが何もない場合にエラーを抑制する必要がある場合。
H2ONaCl 2014

16
簡潔で読みやすいかもしれませんが、使用するのは危険です。で作成されたファイルを削除しようとすると、現在のディレクトリのすべてが完全にtouch 'hello * world'削除されます。
Charles Duffy

1
これは2008年に回答されましたが、それは魅力的なように機能し、特定のディレクトリから古いバックアップを単に削除するために必要なものでした。驚くばかり。
Rens Tillmann、2017年

86
(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

20
このコマンドは、名前にスペースが含まれているファイルを正しく処理しません。
tylerl 2010

5
(ls -t|head -n 5;ls)コマンドグループです。最新の5つのファイルを2回印刷します。sort同じ行をまとめます。uniq -u重複を削除して、最新の5つのファイルを除くすべてを残します。それらのそれぞれをxargs rm呼び出しrmます。
Fabien

15
5個以下の場合、すべてのファイルが削除されます!に追加--no-run-if-emptyxargs(ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm回答を更新してください。
Gonfi den Tschal

3
「スペースで名前をサポートする」ものでも危険です。リテラルの引用符を含む名前を考えてみましょう:touch 'foo " bar'残りのコマンド全体が破棄されます。
Charles Duffy、2016年

2
...それは、使用に安全ですxargs -d $'\n'(以外使用する必要があり、入力ストリームNUL-区切りかかわらず、あなたのコンテンツに注入引用符によりls本当に右行うことは)理想的なオプションです。
Charles Duffy 2016年

59

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

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

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

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

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


15
xargsに--no-run-if-emptyを追加して、ファイルが5つ未満の場合に失敗しないようにする必要があります。
トム

ls -1tr | 頭-n -5 | xargs rm <---------- lsに-1を追加する必要があります。そうしないと、headが適切に機能するためのリスト出力が得られません
Al Joslin

3
@AlJoslin -1は、出力がパイプラインに対するものである場合のデフォルトであるため、ここでは必須ではありません。これにはxargs、スペース、引用符、&cを使用して名前を解析するときのデフォルトの動作に関連するはるかに大きな問題があります。
Charles Duffy

--no-run-if-empty私のシェルでは認識されないようです。WindowsでCmderを使用しています。
StayFoolish 2018

-0ファイル名に空白が含まれる可能性がある場合は、オプションを使用する必要がある場合があります。まだそれをテストしていません。 出典
キース、

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が必要ですが、改行またはスペースが埋め込まれたファイルを処理します。


2
ディレクトリを削除したい場合は、-fを-dに変更し、rmに-rを追加します。見つける。-maxdepth 1 -type d -printf '%T @%p \ 0' | ソート-r -z -n | awk 'BEGIN {RS = "\ 0"; ORS = "\ 0"; FS = ""} NR> 5 {sub( "^ [0-9] *(。[0-9] *)?"、 ""); 印刷} '| xargs -0 rm -rf
alex

1
一見すると、awkロジックの複雑さ(さらに言えば、必要性)に驚いています。OPの質問に必要ないくつかの要件がありませんか?
Charles Duffy

@Charles Duffy:sub()はタイムスタンプを削除します。これは並べ替えの基準になります。「%T @」によって生成されるタイムスタンプには、小数部が含まれる場合があります。FSでスペースを分割すると、スペースが埋め込まれたパスが壊れます。私は最初のスペースを介して削除することはうまくいくと思いますが、読むのと同じくらい難しいです。RSおよびORSセパレータはNULであるため、コマンドラインで設定できません。
wnoise 2016年

1
@wnoise、これに対する私の通常のアプローチは、シェルwhile read -r -d ' '; IFS= -r -d ''; do ...ループにパイプすることです-最初の読み取りはスペースで終了し、2番目の読み取りはNULに進みます。
Charles Duffy

@Charles Duffy:おそらくビザンチンの引用の懸念のために、私は常に生の殻にこだわっています。私は今、GNU sed -z -e 's/[^ ]* //; 1,5d'が最も明確だと思います。(または多分sed -n -z -e 's/[^ ]* //; 6,$p'
wnoise

14

現在のディレクトリにディレクトリがある場合、これらすべての回答は失敗します。これが機能するものです:

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

この:

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

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

  3. 現在のディレクトリにあるファイルの数が多すぎて、xargs通常は失敗する場合、安全に失敗します(-x

  4. ファイル名にスペースが含まれていない(おそらく間違ったOSを使用しているのでしょうか?)


5
find単一のコマンドラインで渡されるよりも多くのファイル名を返すとどうなりますls -tか?(ヒント:の実行は複数回行わls -tれ、グローバルに正しい並べ替え順序ではなく、個別に並べ替えられます。したがって、十分に大きなディレクトリで実行すると、この回答は大きく崩れます)。
Charles Duffy

12
ls -tQ | tail -n+4 | xargs rm

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

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


-Qオプションでは、私のマシン上に存在していないようです。
Pierre-Adrien Buisson 2014

4
うーん、このオプションはGNUコアutilsに20年ほど存在していますが、BSDバリアントでは言及されていません。Macを使用していますか?
マーク

私は本当に。この種の本当に基本的なコマンドには、最新のシステム間で違いがあるとは思わなかった。ご回答有難うございます !
Pierre-Adrien Buisson 2014

3
@マーク:++ -Q。はい、-QGNU拡張機能です(POSIX ls仕様はこちら)。小さな注意点(実際にはまれな問題):-Q埋め込まれたエンコード改行をリテラルとしてファイル名に\nrm認識されません。最初の3つを除外するには、xargs引数を指定する必要があり+4ます。最後に、他のほとんどの回答にも適用される警告:コマンドは、現在のディレクトリにサブディレクトリがない場合にのみ、意図したとおりに機能します。
mklement0 2016年

1
削除するものがないときは、とのコールxargsの持つ--no-run-if-emptyオプション:ls -tQ | tail -n+4 | xargs --no-run-if-empty rm
オリヴィエLecrivain

8

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

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )

2
クリアすることをお勧めIFSします。そうしないと、ファイル名の末尾の空白が失われる危険性があります。缶スコープそのリードコマンドに:while IFS= read -rd ''; do
チャールズ・ダフィー

1
なんで"${REPLY#* }"
msciwoj 2016年

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番目のバージョン:それらを適切に削除するスクリプトを生成する

while readスペースを扱うための秘訣を忘れないでください: ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done
pinkeen '17 / 11/14

1
@pinkeen、そこに与えられているようにかなり安全ではありません。while IFS= read -r dは少し良いでしょう-は-rによってバックスラッシュリテラルが消費されるreadのをIFS=防ぎ、は末尾の空白の自動トリミングを防ぎます。
Charles Duffy

4
ところで、敵意のあるファイル名が心配な場合、これは非常に危険なアプローチです。で作成されたファイルについて考えますtouch $'hello \'$(rm -rf ~)\' world'。ファイル名内のリテラル引用符は、追加するリテラル引用符に対抗するためsed、ファイル名内のコードが実行されます。
Charles Duffy

1
(明確にするために、上記の「これ」| shは、シェルインジェクションの脆弱性があるフォームを指していました)。
Charles Duffy

2

zshを使用

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

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

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


興味深いですが、私の人生では、並べ替えグロブ修飾子(om)を機能させることができませんでした(私が試した並べ替えは効果がありませんでした-OSX 10.11.2(zsh 5.0.8および5.1.1で試した場合) 、Ubuntu 14.04(zsh 5.0.2)でも)-何が欠けていますか?範囲のエンドポイントについては、ハードコーディングする必要はありません。-1最後のエントリを参照し、残りのすべてのファイルを含めるために使用するだけです[6,-1]
mklement0 2016年

2

私はこれが古いスレッドであることを理解していますが、誰かがこれから利益を得るでしょう。このコマンドは、現在のディレクトリでファイルを検索します。

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

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


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


1

私はbusybox(ルーター)のためのエレガントなソリューションを必要としていましたが、すべてのxargsまたは配列ソリューションは役に立たなかった-そのようなコマンドはそこにはありませんでした。10項目について話しているため、find and mtimeは適切な答えではありません。必ずしも10日間とは限りません。Espoの答えは、最も短く、最もクリーンで、おそらく最も逆の答えでした。

スペースのエラーとファイルを削除しない場合は、どちらも標準的な方法で簡単に解決できます。

rm "$(ls -td *.tar | awk 'NR>7')" 2>&-

もう少し教育的なバージョン:awkを別の方法で使用すれば、すべてを実行できます。通常、私はこのメソッドを使用して、awkからshに変数を渡します(返す)。私たちはできないことをいつも読んでいるので、私は違うことを頼みました:ここに方法があります

ファイル名のスペースに関して問題のない.tarファイルの例。テストするには、「rm」を「ls」に置き換えます。

eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')

説明:

ls -td *.tar時間順にソートされたすべての.tarファイルをリストします。現在のフォルダ内のすべてのファイルに適用するには、「d * .tar」の部分を削除します

awk 'NR>7... 最初の7行をスキップします

print "rm \"" $0 "\"" 次の行を作成します:rm "file name"

eval それを実行する

rm使用しているため、上記のコマンドはスクリプトでは使用しません。より賢い使い方は:

(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))

使用する場合はls -t次のコマンドをこのような愚かな例に害しないだろうtouch 'foo " bar'としtouch 'hello * world'。実際にそのような名前のファイルを作成することはありません!

サイドノート。この方法で変数をshに渡したい場合は、単純に出力を変更します(単純な形式で、スペースは許容されません)。

print "VarName="$1

変数VarNameをの値に設定します$1。複数の変数を一度に作成できます。これVarNameは通常のsh変数になり、通常は後でスクリプトまたはシェルで使用できます。したがって、awkで変数を作成してシェルに戻すには、次のようにします。

eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\""  }'); echo "$VarName"

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

2
xargsなし、-0または最低で-d $'\n'も信頼できません。名前にスペースまたは引用符が含まれるファイルでこれがどのように動作するかを確認します。
Charles Duffy

0

これをbashシェルスクリプトにしました。使用法:keep NUM DIRここで、NUMは保持するファイルの数、DIRはスクラブするディレクトリです。

#!/bin/bash
# Keep last N files by date.
# Usage: keep NUMBER DIRECTORY
echo ""
if [ $# -lt 2 ]; then
    echo "Usage: $0 NUMFILES DIR"
    echo "Keep last N newest files."
    exit 1
fi
if [ ! -e $2 ]; then
    echo "ERROR: directory '$1' does not exist"
    exit 1
fi
if [ ! -d $2 ]; then
    echo "ERROR: '$1' is not a directory"
    exit 1
fi
pushd $2 > /dev/null
ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
popd > /dev/null
echo "Done. Kept $1 most recent files in $2."
ls $2|wc -l
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.