Как удалить все, кроме 10 новейших файлов в Linux?

49

Я пытаюсь держать каталог, полный файлов журнала, управляемым. Ночью я хочу удалить все, кроме 10 самых последних. Как я могу сделать это в одной команде?

user75814
источник
3
взгляните на logrotate
knittl
дубликат: stackoverflow.com/questions/6024088/…
майкл
@Michael Как мой может быть дубликатом этого, если моя дискуссия была создана в первую очередь?
dev4life
@ovaherenow "твой"? Я не вижу твоего имени ни в одном вопросе, поэтому не уверен, что ты имеешь в виду. Мой комментарий даже не указывает, что является дубликатом другого. Но это не имеет значения - это просто комментарий со ссылкой. Его можно добавить либо к вопросу, либо к обоим вопросам. Это зависит от кого-то еще - администратора - чтобы пометить один или другой как дубликат. Никакой гнусной ловкости не было и не должно быть. Более важным, чем вопрос был первым, является вопрос, какой из них имеет лучший ответ - опять же, ссылка облегчает эту дискуссию, но не определяет ответ.
Майкл
@ Майкл вау. Это действительно странно. Эта тема была открыта мной, но это явно не мое имя пользователя. Но я получаю уведомление об этой теме.
dev4life

Ответы:

58

Для портативного и надежного решения, попробуйте это:

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

tail -n -10Синтаксис в одном из других ответов , кажется , не работать везде (то есть, не на моих системах RHEL5).

И использование $()или ``в командной строке rmрискует

  1. разделение имен файлов пробелами и
  2. превышение максимального ограничения символов командной строки.

xargsустраняет обе эти проблемы, потому что он автоматически выяснит, сколько аргументов он может передать в пределах ограничения на количество символов, и при этом -d '\n'он будет разбиваться только на границе строки ввода. Технически это все еще может вызвать проблемы для имен файлов с новой строкой в ​​них, но это гораздо реже, чем имена файлов с пробелами, и единственный способ обойти новые строки будет намного сложнее, вероятно, с использованием по крайней мере awk, если не perl.

Если у вас нет xargs (возможно, старых систем AIX?), Вы можете сделать это циклом:

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

Это будет немного медленнее, потому что он порождает отдельное rmдля каждого файла, но все равно будет избегать предостережений 1 и 2 выше (но все еще страдает от новых строк в именах файлов).

Исаак Фриман
источник
@HaukeLaging: со 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
Исаак Фриман
На Солярисе 10 headи xargsвыдают ошибки. Правильные варианты есть head -n 10и xargs rm -f. Полная командаls -1tr ./ | head -n 10 | xargs rm -rf
Дмитрий Сандалов
1
Обратите внимание, что ls -1trномер ОДИН, а не буква "L".
пользователь shonky linux
1
Следует также отметить, что переводы строки - это редкие, но допустимые символы в именах файлов ext. Это означает, что если у вас есть файлы «тот» и «тот \ nthing», если этот скрипт ударит «тот \ nthing», он удалит «тот», ошибку, когда он не смог удалить «вещь», и оставит «что \ nthing» (предполагаемая цель) не затрагивается.
Synthead
1
@IsaacFreeman Я удалил свой плохой совет, ура.
Уолф
20

Код, который вы хотите включить в свой скрипт

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

Опция -1(числовая) печатает каждый файл в одной строке, чтобы быть в безопасности. -fВариант rmговорит , что игнорировать файлы несуществующие для того, когда lsвозвращает ничего.

Даниэль Бёмер
источник
Оригинальный постер, здесь. Я попробовал эту команду, но она не работает - я получаю сообщение об ошибке «rm: отсутствующий операнд»
user75814
2
Вы использовали правильный путь? Очевидно, /path/to/your/logsэто просто сделано для вас, чтобы ввести правильный путь. Для того, чтобы найти ошибку выполнения частей ls -t /path/...и в ls -t /path/... | tail -n +11одиночку , и проверить , если результат является правильным.
Даниэль Бёмер
1
@HaukeLaging Я совершенно уверен, что вы что-то не так поняли. Следите +за числом. Он tailпечатает не последние n элементов, а все элементы, начиная с n-го. Я зарегистрировался снова, и команда должна обязательно удалить только элементы старше 10 самых последних файлов при любых обстоятельствах.
Даниэль Бёмер
@ Halo Действительно, извини.
Хауке Лагинг
2
Я думаю, что ваш ответ очень опасен, lsпечатает имя файла, а не путь, поэтому вы rm -fбудете пытаться удалить файлы в текущем каталоге
Юрген Штейнблок
14

Видимо, разбор lsэто зло .

Если каждый файл создается ежедневно и вы хотите сохранить файлы, созданные в течение последних 10 дней, вы можете сделать следующее:

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

Или, если каждый файл создан произвольно:

find /path/to/files -maxdepth 1 -type f -printf '%Ts\t%P\n' | sort -n | head -n -10 | cut -f 2- | xargs rm -rf
Тот самый
источник
5
-mtime 10 -deleteудаляет только файлы, измененные ровно 10 дней назад. Старые файлы не будут удалены. -mtime +11 -deleteудалит файлы, которые были изменены 11 или более дней назад, оставив последние 10 дней файлов журнала.
Маркус
1
Я думаю, что использование %pвместо %Pнеобходимо для того, printfчтобы файлы получили полный путь. В противном случае rm -rfне работает.
Халопаба
9

Такой инструмент, как logrotate, сделает это за вас. Это делает управление журналами намного проще. Вы также можете включить дополнительные процедуры очистки, такие как предложенный гало.

BillThor
источник
2

Я немного изменил подход Исаака .

Теперь он работает с желаемым путем:

ls -d -1tr /path/to/folder/* | head -n -10 | xargs -d '\n' rm -f
Себастьян У.
источник
1

От сюда :

#! /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
источник
1
Спасибо за предоставленный ответ. Хотя предоставление кода, подобного этому, полезно, оно также помогает предоставить некоторую дополнительную информацию о том, как его можно использовать или что делает код. Вы можете использовать кнопку редактирования , чтобы в будущем улучшить свой пост.
nhinkle
1

В Bash вы можете попробовать:

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

Это пропускает 10 новейших файлов, удаляет остальные. logrotate может быть лучше, я просто хочу исправить неправильные ответы, связанные с оболочкой.

Хауке Лагинг
источник
1

Не уверен, что это кому-нибудь поможет, но чтобы избежать потенциальных проблем со странными символами, которые я использовал lsдля сортировки, а затем использовать файлы inodeдля удаления.

Для текущего каталога это сохраняет последние 10 файлов в зависимости от времени модификации.

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

Фло Ву
источник
0

Мне нужно было элегантное решение для busybox (роутера), все решения xargs или array были для меня бесполезны - такой команды там не было. find и mtime не правильный ответ, так как речь идет о 10 пунктах и ​​не обязательно 10 днях. Ответ Эспо был самым коротким и чистым и, вероятно, самым неожиданным.

Ошибка с пробелами и когда файлы не должны быть удалены, просто решаются стандартным способом:

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 "VarName="$1

установить переменную VarNameв значение $1. Несколько переменных могут быть созданы за один раз. Это VarNameстановится нормальной переменной sh и впоследствии может быть использовано в скрипте или оболочке. Итак, чтобы создать переменные с помощью awk и вернуть их обратно в оболочку:

eval $(ls -td *.tar | awk 'NR>7 { print "VarName="$1 }'); echo "$VarName"
Pila
источник
0

Я согласен, что разбор ls это зло!

Убедитесь, что только последние 5 файлов хранятся в /srv/backupsпути.

find /srv/backups -maxdepth 1 -type f -printf '%Ts\t%P\n' \
    | sort -rn \
    | tail -n +6 \
    | cut -f2- \
    | xargs -r r
h0tw1r3
источник
0

Основано на ответах Исаака Фримена, но работает с любым каталогом:

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

Вы также можете установить префикс, например, $PATH_TO_DELETE/testFi*

Если вы используете *после того, как PATH lsбудет выводить абсолютные имена файлов, по крайней мере, в "моем" Bash :)

Бис
источник