質問

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

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

念のため言っておきますが、存在するファイルは 1 つだけなので、決して削除してはいけません。

役に立ちましたか?

解決

既存の回答の問題点:

  • スペースや改行が埋め込まれたファイル名を処理できない。
    • を呼び出すソリューションの場合 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 -- {}

上記は 非効率的な, 、 なぜなら xargs 呼び出さなければなりません rm 一度だけ それぞれ ファイル名。
あなたのプラットフォームの 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 (含む) OS X)、使用できます -0 処理する NUL- 改行を最初に変換した後、分離された入力 NUL (0x0) chars.、これは (通常は) すべてのファイル名も渡します すぐに (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 つのファイル (存在する場合)。
    除外するには注意してください N ファイル、 N+1 に渡さなければなりません tail -n +.
  • xargs -I {} rm -- {} (およびそのバリエーション) を呼び出します。 rm これらすべてのファイルに対して。まったく一致しない場合は、 xargs 何もしません。
    • xargs -I {} rm -- {} プレースホルダーを定義します {} 各入力行を表す 全体として, 、 それで rm その後、入力行ごとに 1 回呼び出されますが、スペースが埋め込まれたファイル名は正しく処理されます。
    • -- どのような場合でも、ファイル名が次で始まることを保証します。 - 間違われてないよ オプション による 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

他のヒント

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

rm `ls -t | awk 'NR>5'`
(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

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

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

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

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

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

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

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

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

これ:

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

  2. (権限などの理由で) 前のファイルを削除できなかった場合でも、各ファイルの削除を試みます。

  3. 現在のディレクトリ内のファイルの数が過剰な場合、フェイルセーフになります。 xargs 通常はあなたを台無しにするでしょう( -x)

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

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

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

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

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

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

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

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

zshあり

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

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

*(.om[6,999]), 、 . ファイルを意味します。 o ソート順を上に意味します。 m 変更日による意味 ( a アクセス時間または c i ノード変更の場合)、 [6,999] ファイルの範囲を選択するため、最初に 5 を rm しません。

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

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

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

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

最新の 10 個のファイルを除くすべてを削除します

ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm

ファイルが 10 個未満の場合、ファイルは削除されず、次のようになります。エラーヘッド:不正な行数 -- 0

bash でファイルを数えるには

ビジーボックス (ルーター) 用の洗練されたソリューションが必要でしたが、すべての xargs または配列ソリューションは私にとって役に立ちませんでした。そこではそのようなコマンドは利用できませんでした。ここで話しているのは 10 項目であり、必ずしも 10 日であるわけではないため、find と mtime は適切な答えではありません。エスポの答えは、最も短く、最も明確で、おそらく最も普遍的なものでした。

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

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「ファイル名」

eval それを実行する

私たちが使用しているので、 rm, 上記のコマンドをスクリプト内で使用することはありません。より賢明な使い方は次のとおりです。

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

ご利用の場合 ls -t コマンドは、次のような愚かな例には何の害も与えません。 touch 'foo " bar' そして touch 'hello * world'. 。現実にはそのような名前のファイルを作成することはありません。

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

print "VarName="$1

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

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

これを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

Debian 上で実行しています (私が入手した他のディストリビューションでも同じであると仮定します:rm:ディレクトリ「..」を削除できません

それはかなり迷惑です..

とにかく、上記を微調整し、コマンドに grep も追加しました。私の場合、ディレクトリに6つのバックアップファイルがあります。file1.tar file2.tar file3.tar など、最も古いファイルのみを削除したい(私の場合は最初のファイルを削除する)

最も古いファイルを削除するために実行したスクリプトは次のとおりです。

LS -C1 -T |グレップファイル| awk 'nr> 5' | xargs rm

これにより(上記と同様)、最初のファイルが削除されます。file1.tar これは、file2 file3 file4 file5 および file6 もそのままにしておきます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top