Удалить все, кроме самых последних файлов X в Bash - PullRequest
130 голосов
/ 25 августа 2008

Есть ли простой способ в довольно стандартной среде UNIX с bash запустить команду для удаления всех, кроме самых последних X-файлов из каталога?

Чтобы привести чуть более конкретный пример, представьте себе, что какое-то задание cron записывает файл (скажем, файл журнала или резервную копию) в каталог каждый час. Я хотел бы иметь способ запустить еще одно задание cron, которое бы удаляло самые старые файлы в этом каталоге, пока их не станет меньше, скажем, 5.

И чтобы было ясно, присутствует только один файл, его никогда не следует удалять.

Ответы [ 17 ]

92 голосов
/ 25 августа 2008

Удалить все, кроме 5 (или любого другого числа) самых последних файлов в каталоге.

rm `ls -t | awk 'NR>5'`
91 голосов
/ 18 января 2016

Проблемы с существующими ответами:

  • невозможность обрабатывать имена файлов со встроенными пробелами или переводами строки.
    • В случае решений, которые вызывают rm непосредственно при подстановке команды без кавычек (rm `...`), существует дополнительный риск непреднамеренного сглаживания.
  • неспособность провести различие между файлами и каталогами (т. Е. Если бы каталоги оказались в числе 5 самых последних измененных элементов файловой системы, вы фактически сохранили бы меньше , чем 5 файлов, и применение rm к каталогам не удастся).

ответ wnoise решает эти проблемы, но решение является GNU -конкретным (и довольно сложным).

Вот прагматичное POSIX-совместимое решение , которое поставляется только с одним предупреждением : оно не может обрабатывать имена файлов со встроенными символами новой строки - но я не рассматриваю это реальная проблема для большинства людей.

Для справки, вот объяснение того, почему вообще не очень хорошая идея анализировать 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) символов, который также передает (обычно) все имена файлов сразу (также будет работать с 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 затем вызывается один раз для каждой строки ввода, но с именами файлов со встроенными пробелами, которые обрабатываются правильно.
    • -- во всех случаях гарантирует, что любые имена файлов, начинающиеся с -, не будут приняты за options rm.

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

# 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
85 голосов
/ 25 августа 2008
(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
58 голосов
/ 12 апреля 2012

Более простой вариант ответа thelsdj:

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

ls -tr отображает все файлы, сначала самые старые (сначала -t самые новые, -r наоборот).

head -n -5 отображает все, кроме 5 последних строк (т.е. 5 новейших файлов).

xargs rm вызывает rm для каждого выбранного файла.

16 голосов
/ 18 ноября 2008
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

Требуется GNU find для -printf, GNU sort для -z, GNU awk для "\ 0" и GNU xargs для -0, но обрабатывает файлы со встроенными символами новой строки или пробелами.

13 голосов
/ 18 ноября 2008

Все эти ответы не выполняются, если в текущем каталоге есть каталоги. Вот что работает:

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

Это:

  1. работает, когда в текущем каталоге есть каталоги

  2. пытается удалить каждый файл, даже если предыдущий не может быть удален (из-за разрешений и т. Д.)

  3. отказоустойчиво, если количество файлов в текущем каталоге слишком велико, и xargs обычно вас подставляет (-x)

  4. не учитывает пробелы в именах файлов (возможно, вы используете не ту ОС?)

12 голосов
/ 25 июля 2013
ls -tQ | tail -n+4 | xargs rm

Список имен файлов по времени модификации, цитируя каждое имя файла. Исключить первые 3 (3 самых последних). Удалить оставшиеся.

РЕДАКТИРОВАТЬ после полезного комментария от mklement0 (спасибо!): Исправлен аргумент -n + 3, и обратите внимание, что это не будет работать должным образом, если имена файлов содержат символы новой строки и / или каталог содержит подкаталоги.

8 голосов
/ 13 июня 2009

Игнорирование новых строк игнорирует безопасность и хорошее кодирование. У wnoise был единственный хороший ответ. Вот вариант его, который помещает имена файлов в массив $ x

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
4 голосов
/ 25 августа 2008

Если в именах файлов нет пробелов, это будет работать:

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

Если в именах файлов есть пробелы, что-то вроде

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

Базовая логика:

  • получить список файлов в порядке времени, один столбец
  • получить все, кроме первых 5 (n = 5 для этого примера)
  • первая версия: отправьте их в rm
  • вторая версия: gen скрипт, который удалит их правильно
2 голосов
/ 11 января 2017

Я понимаю, что это старая ветка, но, возможно, кому-то это поможет. Эта команда найдет файлы в текущем каталоге:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...