Jak usunąć wszystkie oprócz 10 najnowszych plików w systemie Linux?

49

Staram się, aby katalog pełen plików dziennika był zarządzalny. Co noc chcę usunąć wszystkie oprócz 10 najnowszych. Jak mogę to zrobić za pomocą jednego polecenia?

użytkownik75814
źródło
3
spójrz na logrotate
knittl
@ Michael Jak mój może być duplikatem tego, jeśli moja dyskusja została utworzona jako pierwsza?
dev4life
@ovaherenow „twój”? Nie widzę twojego imienia w żadnym z pytań, więc nie jestem pewien, co masz na myśli. Mój komentarz nawet nie wskazuje, który jest duplikatem drugiego. Ale to nie ma znaczenia - to tylko komentarz z linkiem. Można go dodać do jednego lub obu pytań. To do kogoś innego - administratora - oznaczenie jednego lub drugiego jako duplikatu. Żaden nikczemny sztuczka nie był zamierzony i nie należy go postrzegać. Ważniejsze od tego, które pytanie było pierwsze, jest to, które pytanie ma najlepszą odpowiedź - ponownie link ułatwia tę dyskusję, nie określa odpowiedzi.
Michael
@michael wow. To naprawdę dziwne. Ten wątek został przeze mnie otwarty, ale oczywiście nie jest to moja nazwa użytkownika. Ale dostaję powiadomienie o tym wątku.
dev4life

Odpowiedzi:

58

Aby uzyskać przenośne i niezawodne rozwiązanie, spróbuj tego:

ls -1tr | head -n -10 | xargs -d '\n' rm -f --

tail -n -10Składni w jednej z pozostałych odpowiedzi nie wydają się działać wszędzie (czyli nie na moich systemów RHEL5).

I za pomocą $()lub ``w linii poleceń rmuruchamia ryzyko

  1. dzielenie nazw plików za pomocą białych znaków i
  2. przekroczenie maksymalnego limitu znaków w linii poleceń.

xargsnaprawia oba te problemy, ponieważ automatycznie obliczy liczbę argumentów, które może przekazać w ramach limitu znaków, a wraz z -d '\n'nim podzieli się tylko na granicy linii wejściowej. Technicznie może to nadal powodować problemy z nazwami plików z nową linią, ale jest to o wiele mniej powszechne niż nazwy plików ze spacjami, a jedynym sposobem obejścia nowych linii byłoby znacznie bardziej skomplikowane, prawdopodobnie obejmujące przynajmniej awk, jeśli nie perl.

Jeśli nie masz xargs (może stare systemy AIX?), Możesz zrobić z niego pętlę:

ls -1tr | head -n -10 | while IFS= read -r f; do
  rm -f "$f"
done

Będzie to nieco wolniejsze, ponieważ odradza się osobno rmdla każdego pliku, ale nadal będzie unikać ostrzeżeń 1 i 2 powyżej (ale nadal cierpi na nowe wiersze w nazwach plików).

Isaac Freeman
źródło
@HaukeLaging: Ze strony podręcznika head(1):-n, --lines=[-]K print the first K lines instead of the first 10; with the leading '-', print all but the last K lines of each file
Isaac Freeman
W systemie Solaris 10 headi xargspodawaj błędy. Prawidłowe opcje to head -n 10i xargs rm -f. Pełne polecenie tols -1tr ./ | head -n 10 | xargs rm -rf
Dmitrij Sandałow
1
Zauważ, że ls -1trto liczba JEDEN, a nie litera „L”.
shonky użytkownik Linuxa
1
Należy również zauważyć, że znaki nowej linii to rzadkie, ale prawidłowe znaki w nazwach plików ext. Oznacza to, że jeśli masz pliki „to” i „tamto”, jeśli ten skrypt trafi „tamto”, usunie „tamto”, błąd, gdy nie będzie można usunąć „rzecz”, i odejdzie „to \ nthing” (zamierzony cel) pozostaje nienaruszone.
Synthead,
1
@IsaacFreeman Usunąłem złą radę, na zdrowie.
Walf
20

Kod, który chcesz dołączyć do skryptu, to

 rm -f $(ls -1t /path/to/your/logs/ | tail -n +11)

Opcja -1(numeryczna) drukuje każdy plik w jednym wierszu, aby być bezpiecznym. -fOpcja rmmówi, że ignorowanie nieistniejących plików do kiedy lsnic nie zwraca.

Daniel Böhmer
źródło
Oryginalny plakat tutaj. Próbowałem tego polecenia, ale to nie działa - pojawia się błąd „rm: missing operand”
user75814
2
Czy użyłeś właściwej ścieżki? Oczywiście /path/to/your/logsjest to po prostu nadrobione, abyś podał prawidłową ścieżkę. Aby znaleźć błąd wykonania części ls -t /path/...i ls -t /path/... | tail -n +11sam i sprawdzić, czy wynik jest prawidłowy.
Daniel Böhmer,
1
@HaukeLaging Jestem pewien, że coś jest nie tak. Uważaj +przed liczbą. Powoduje to, że tailnie drukuje ostatnich n elementów, ale wszystkie elementy zaczynające się od n-tego. Zalogowałem się ponownie i polecenie zdecydowanie powinno usunąć we wszystkich okolicznościach tylko elementy starsze niż 10 najnowszych plików.
Daniel Böhmer
@halo Rzeczywiście przepraszam.
Hauke ​​Laging
2
Myślę, że twoja odpowiedź jest bardzo niebezpieczna, lswypisuje nazwę pliku, a nie ścieżkę, więc rm -fspróbujesz usunąć pliki z bieżącego katalogu
Jürgen Steinblock
14

Najwyraźniej parsowanie lsjest złe .

Jeśli każdy plik jest tworzony codziennie i chcesz zachować pliki utworzone w ciągu ostatnich 10 dni, możesz:

find /path/to/files -mtime 10 -delete

Lub jeśli każdy plik jest tworzony arbitralnie:

find /path/to/files -maxdepth 1 -type f -printf '%Ts\t%P\n' | sort -n | head -n -10 | cut -f 2- | xargs rm -rf
Jeden
źródło
5
-mtime 10 -deleteusuwa tylko pliki zmodyfikowane dokładnie 10 dni temu. Starsze pliki nie zostaną usunięte. -mtime +11 -deleteusunie pliki, które zostały zmodyfikowane 11 lub więcej dni temu, pozostawiając ostatnie 10 dni plików dziennika.
Markus
1
Myślę, że użycie %pzamiast zamiast %Pjest potrzebne, printfaby pliki miały pełną ścieżkę. W przeciwnym razie rm -rfnie działa.
jalopaba
9

Narzędzie takie jak logrotate robi to za Ciebie. To znacznie ułatwia zarządzanie logami. Możesz także dołączyć dodatkowe procedury czyszczenia, takie jak przesycenie halo.

BillThor
źródło
2

Trochę zmodyfikowałem podejście Izaaka .

Teraz działa z wybraną ścieżką:

ls -d -1tr /path/to/folder/* | head -n -10 | xargs -d '\n' rm -f
Sebastian U.
źródło
1

Od tutaj :

#! /bin/sh
# keepnewest
#
# Simple directory trimming tool to handle housekeeping
# Scans a directory and deletes all but the N newest files
#
# Usage: cleanup <dir> <number of files to keep>
#
# v 1.0 Piers Goodhew 1/mar/2007. No rights retained.


if [ $# -ne 2 ]; then
  echo 1>&2 "Usage: $0 <dir> <number of files to keep>"
  exit 1
fi

cd $1
files_in_dir=`ls | wc -l`
files_to_delete=`expr $files_in_dir - $2`
if [ $files_to_delete -gt 0 ]; then
  ls -t | tail -n $files_to_delete | xargs rm
  if [ $? -ne 0 ]; then
    echo "An error ocurred deleting the files"
    exit 1
  else
    echo "$files_to_delete file(s) deleted."
  fi
else
  echo "nothing to delete!"
fi
manoflinux
źródło
1
Dziękujemy za udzielenie tej odpowiedzi. Podanie takiego kodu jest pomocne, ale pomaga również dostarczyć dodatkowe informacje o tym, jak można go użyć lub co robi kod. Możesz użyć przycisku edycji , aby w przyszłości ulepszyć swój post.
nhinkle
1

W Bash możesz spróbować:

ls -1t | (i=0; while read f; do
  if [ $i -lt 10 ]; then
    ((i++))
    continue
  else
    rm -f "$f"
  fi
done)

Pomija 10 najnowszych als usuwa resztę. logrotate może być lepszy, chcę tylko poprawić nieprawidłowe odpowiedzi dotyczące powłoki.

Hauke ​​Laging
źródło
1

nie jestem pewien, że to pomoże każdemu, ale aby uniknąć potencjalnych problemów z dziwacznymi postaciami, których użyłem lsdo sortowania, ale potem użyłem plików inodedo usunięcia.

W bieżącym katalogu zachowuje 10 najnowszych plików na podstawie czasu modyfikacji.

ls -1tri | awk '{print $1}' | head -n -10 | xargs -n1 -t -Iinode find . -inum inode -exec rm -i {} \;

Flo Woo
źródło
0

Potrzebowałem eleganckiego rozwiązania dla busyboksa (routera), wszystkie xargi lub rozwiązania macierzowe były dla mnie bezużyteczne - nie ma tam takiego polecenia. find i mtime nie jest właściwą odpowiedzią, ponieważ mówimy o 10 elementach i niekoniecznie o 10 dniach. Odpowiedź Espo była najkrótsza, najczystsza i prawdopodobnie najbardziej niewersalna.

Błąd spacji i brak usuwania plików są po prostu rozwiązywane w standardowy sposób:

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

Trochę więcej wersji edukacyjnej: możemy to wszystko zrobić, jeśli korzystamy z awk inaczej. Zwykle używam tej metody do przekazywania (zwracania) zmiennych z awk do sh. Kiedy cały czas czytamy, że nie da się tego zrobić, zaczynam się różnić: oto metoda.

Przykład plików .tar bez problemu dotyczących spacji w nazwie pliku. Aby przetestować, zamień „rm” na „ls”.

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

Wyjaśnienie:

ls -td *.tarwyświetla wszystkie pliki .tar posortowane według czasu. Aby zastosować do wszystkich plików w bieżącym folderze, usuń część „d * .tar”

awk 'NR>7... pomija pierwsze 7 linii

print "rm \"" $0 "\"" konstruuje linię: rm „nazwa pliku”

eval wykonuje to

Ponieważ używamy rm, nie użyłbym powyższego polecenia w skrypcie! Mądrzejsze użycie to:

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

W przypadku użycia ls -tpolecenia nie wyrządzi żadnej szkody na tak głupich przykładach jak: touch 'foo " bar'i touch 'hello * world'. Nie żebyśmy kiedykolwiek tworzyli pliki o takich nazwach w prawdziwym życiu!

Dygresja. Gdybyśmy chcieli przekazać zmienną do sh w ten sposób, po prostu zmodyfikowalibyśmy wydruk:

print "VarName="$1

ustawić zmienną VarNamena wartość $1. Za jednym razem można utworzyć wiele zmiennych. To VarNamestaje się normalna zmienna sh i można go używać w skrypcie lub muszli później. Tak więc, aby utworzyć zmienne za pomocą awk i zwrócić je powłoce:

eval $(ls -td *.tar | awk 'NR>7 { print "VarName="$1 }'); echo "$VarName"
Piła
źródło
0

Zgadzam się, że parsowanie ls jest złe!

Upewnij się, że tylko 5 ostatnich plików jest przechowywanych na /srv/backupsścieżce.

find /srv/backups -maxdepth 1 -type f -printf '%Ts\t%P\n' \
    | sort -rn \
    | tail -n +6 \
    | cut -f2- \
    | xargs -r r
h0tw1r3
źródło
0

Na podstawie odpowiedzi Izaaka Freemana, ale działa na dowolnym katalogu:

ls -1tr $PATH_TO_DELETE/* | head -n -10 | xargs -d '\n' rm -f --

Możesz także ustawić prefiks, np $PATH_TO_DELETE/testFi*

Jeśli użyjesz *po PATH lsto wypisze bezwzględne nazwy plików, przynajmniej w „moim” bashu :)

Bis
źródło