Удалите все, кроме самых последних X-файлов в bash

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
165
0
117 464
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

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

Мне это нужно было только для рассмотрения моих архивных файлов. изменить ls -t на ls -td *.bz2

James T Snell 07.02.2014 00:37

Я использовал это для каталогов, изменив его на rm -rf ls -t | awk 'NR>1' (мне нужен был только самый последний). Спасибо!

lohiaguitar91 09.07.2014 22:07

ls -t | awk 'NR>5' | xargs rm -f, если вы предпочитаете каналы и вам нужно подавить ошибку, если нечего удалять.

H2ONaCl 30.07.2014 11:58

Это очень мило. Используя это для очистки моего каталога сборки CI / CD. Спасибо.

Michael Andrews 13.12.2014 21:44

Как ни странно, это не сработало для меня. Мне пришлось использовать версию xargs @ H2ONaCl

Ashutosh Jindal 04.11.2015 23:02

Возможно, кратко и читабельно, но опасно в использовании; при попытке удалить файл, созданный с помощью touch 'hello * world', это приведет к удалению абсолютно все в текущем каталоге.

Charles Duffy 18.01.2016 23:16

Несмотря на то, что на этот вопрос был дан ответ в 2008 году, он работает как шарм, и мне нужно просто удалить старые резервные копии из определенного каталога. Потрясающие.

Rens Tillmann 29.05.2017 18:28

Я хотел указать здесь явный путь.

Swapnil Mhaske 29.05.2020 16:39
ПРЕДУПРЕЖДЕНИЕ Please make sure you run this from the directory that files are going to be deleted from! I stupidly ran this from a working code directory of 100 files or so and it zapped the f*&$*ing lot!! Fortunately I had just taken a backup 30 mins before (Phew!) (You know that sinking feeling you get when your heart stops and you cant find the files in the trash bin)
joe_evans 21.07.2020 01:24

(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

Эта команда некорректно обрабатывает файлы с пробелами в именах.

tylerl 14.04.2010 00:33

Чтобы исправить выше, используйте: (ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm

BroiSatse 05.02.2014 18:24

Этот не работает, если нет файлов для удаления.

Mantas 03.06.2014 18:36

@Mantas Я думаю, что это можно решить с помощью rm -f (хотя я пробовал только -rf, не знаю, работает ли он без -r).

Andreas Hultgren 19.07.2014 16:55

(ls -t|head -n 5;ls) - это группа команд. Он распечатывает 5 последних файлов дважды. sort объединяет идентичные строки. uniq -u удаляет дубликаты, поэтому остаются все файлы, кроме 5 самых последних. xargs rm вызывает rm для каждого из них.

Fabien 13.11.2014 17:24

(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm -rf работает для: 1) удаления каталогов 2) предотвращения "rm: missing operand", когда нечего удалять

Paul Schwarz 09.02.2015 16:00

Не работает для файлов типа "-000-" (ls -t|head -n 100;ls)|sort|uniq -u | xargs printf './%s\n' | xargs rm работает в моем случае.

lqdc 17.03.2015 06:46

@Matas правильный. не работает, если в папке меньше 5 файлов, будьте осторожны

Andrey 19.04.2015 00:06

Это удалит все ваши файлы, если у вас их 5 или меньше! Добавьте --no-run-if-empty в xargs, как в (ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm, обновите ответ.

Gonfi den Tschal 28.04.2015 00:29

Echo Gonfi, это удаляет 5 самых старых файлов. Это даже отдаленно НЕ то же самое, что хранить 5 самых новых файлов.

Canuteson 05.12.2015 00:54

Даже тот, который «поддерживает имена с пробелами», опасен. Рассмотрим имя, содержащее буквальные кавычки: touch 'foo " bar' отбросит всю остальную часть команды.

Charles Duffy 18.01.2016 19:55

... безопаснее использовать xargs -d $'\n', чем вставлять кавычки в ваш контент, хотя NUL-разделитель входного потока (который требует использования чего-то другого, кроме ls, чтобы В самом деле делать правильно) является идеальным вариантом.

Charles Duffy 18.01.2016 20:00

Почему не ls -t | tail -n +6 вместо (ls -t|head -n 5;ls)|sort|uniq -u?

jayhendren 07.11.2017 00:38

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

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
  • вторая версия: создать скрипт, который удалит их должным образом

Не забудьте уловку while read для работы с пробелами: ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done

pinkeen 17.11.2014 16:00

@pinkeen, не совсем безопасно, как указано там. while IFS= read -r d был бы немного лучше - -r предотвращает использование литералов обратной косой черты read, а IFS= предотвращает автоматическое обрезание конечных пробелов.

Charles Duffy 18.01.2016 23:22

Кстати, если вас беспокоят враждебные имена файлов, это опасный подход очень сильно. Рассмотрим файл, созданный с помощью touch $'hello \'$(rm -rf ~)\' world'; буквальные кавычки внутри имени файла будут противоречить буквальным кавычкам, которые вы добавляете с помощью sed, в результате чего код внутри имени файла будет выполняться.

Charles Duffy 18.01.2016 23:23

(для ясности, «this» выше относится к форме | sh, которая содержит уязвимость инъекции оболочки).

Charles Duffy 20.01.2016 00:13

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

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

Этот:

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

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

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

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

Что произойдет, если find вернет больше имен файлов, чем может быть передано ls -t в одной командной строке? (Подсказка: вы получаете несколько запусков ls -t, каждый из которых сортируется только индивидуально, вместо того, чтобы иметь глобально правильный порядок сортировки; таким образом, этот ответ плохо работает при работе с достаточно большими каталогами).

Charles Duffy 18.01.2016 23:14

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 для -printf и сортировка GNU для -z, GNU awk для "\ 0" и GNU xargs для -0, но обрабатывает файлы со встроенными символами новой строки или пробелами.

Если вы хотите удалить каталоги, просто измените -f на -d и добавьте -r в rm. найти . -maxdepth 1 -type d -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 -rf

alex 10.01.2011 22:19

На первый взгляд, меня удивляет сложность (или, если на то пошло, необходимость) логики awk. Мне не хватает некоторых требований в вопросе OP, которые делают это необходимым?

Charles Duffy 18.01.2016 23:25

@Charles Duffy: sub () удаляет метку времени, по которой выполняется сортировка. Отметка времени, созданная "% T @", может включать дробную часть. Разделение по пространству с помощью FS разрывает пути с вложенными пробелами. Я полагаю, что удаление до первого пробела работает, но его почти так же трудно читать. Разделители RS и ORS не могут быть установлены в командной строке, потому что они NUL.

wnoise 20.01.2016 00:07

@wnoise, мой обычный подход к этому - перенаправить в цикл оболочки while read -r -d ' '; IFS= -r -d ''; do ... - первое чтение завершается на пробеле, а второе - на NUL.

Charles Duffy 20.01.2016 00:08

@ Чарльз Даффи: Я всегда с подозрением отношусь к сырой оболочке, возможно, из-за византийских проблем с цитированием. Теперь я думаю, что GNU sed -z -e 's/[^ ]* //; 1,5d' - самый ясный. (или, возможно, sed -n -z -e 's/[^ ]* //; 6,$p'.

wnoise 20.01.2016 05:09

Я бы сказал, что цитирование не так уж и плохо, если понимать модель исполнения, но допускаю, что, сделав ранее такое вложение, мне трудно оценить, как бы это выглядело, если бы это не было в тумане прошлого.

Charles Duffy 20.01.2016 06:23

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

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

Я бы предложил очистить IFS - иначе вы рискуете потерять конечные пробелы в именах файлов. Можно указать это на команду чтения: while IFS= read -rd ''; do

Charles Duffy 18.01.2016 20:05

почему "${REPLY#* }"?

msciwoj 28.01.2016 19:15

С zsh

Предполагая, что вас не интересуют текущие каталоги, и у вас не будет более 999 файлов (выберите большее число, если хотите, или создайте цикл while).

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

В *(.om[6,999]). означает файлы, o означает порядок сортировки вверх, m означает дату модификации (укажите a для времени доступа или c для изменения inode), [6,999] выбирает диапазон файлов, поэтому не оставляет 5 первый.

Интересно, но хоть убей, я не смог заставить работать классификатор sorting glob (om) (любая сортировка, которую я пробовал, не оказала никакого эффекта - ни на OSX 10.11.2 (пробовал с zsh 5.0.8 и 5.1.1) ), ни в Ubuntu 14.04 (zsh 5.0.2)) - что мне не хватает ?. Что касается конечной точки диапазона: нет необходимости жестко ее кодировать, просто используйте -1 для ссылки на последнюю запись и, таким образом, включите все оставшиеся файлы: [6,-1].

mklement0 19.01.2016 08:42

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

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

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

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

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

Необходимо добавить --no-run-if-empty в xargs, чтобы он не выходил из строя, когда файлов меньше 5.

Tom 07.05.2014 22:31

ls -1tr | голова -n -5 | xargs rm

Al Joslin 16.09.2015 00:02

@AlJoslin, -1 используется по умолчанию, когда вывод идет в конвейер, поэтому здесь это не обязательно. Это имеет гораздо более серьезные проблемы, связанные с поведением xargs по умолчанию при синтаксическом анализе имен с пробелами, кавычками и т. д.

Charles Duffy 18.01.2016 23:18

похоже, что --no-run-if-empty не распознается в моей оболочке. Я использую Cmder в Windows.

StayFoolish 06.09.2018 06:39

Возможно, потребуется использовать параметр -0, если имена файлов могут содержать пробелы. Хотя еще не тестировал. источник

Keith 12.11.2018 18:00

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

xargs без -0 или как минимум -d $'\n' ненадежен; наблюдайте, как это ведет себя с файлом с пробелами или кавычками в его имени.

Charles Duffy 18.01.2016 23:18

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

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

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

Похоже, что на моем компьютере нет опции -Q.

Pierre-Adrien 13.02.2014 14:59

Хм, эта опция присутствует в основных утилитах GNU около 20 лет, но не упоминается в вариантах BSD. Вы на Mac?

Mark 14.02.2014 06:59

Я на самом деле. Не думал, что существуют различия в таких действительно основных командах между современными системами. Спасибо за Ваш ответ !

Pierre-Adrien 14.02.2014 19:10

@Mark: ++ для -Q. Да, -Q - это расширение GNU (здесь Спецификация POSIX ls). Небольшое предостережение (на практике это редко является проблемой): -Q кодирует встроенный новые строки в именах файлов как буквальный \n, который rm не распознает. Чтобы исключить первый 3, аргумент xargs должен быть +4. Наконец, предостережение, которое относится и к большинству других ответов: ваша команда будет работать только по назначению, если в текущем каталоге нет подкаталоги.

mklement0 18.01.2016 23:09

Как я могу запустить это между then и fi в одной строке

juliangonzalez 29.02.2016 23:07

Когда нечего удалять, вы вызываете xargs с параметром --no-run-if-empty: ls -tQ | tail -n+4 | xargs --no-run-if-empty rm

Olivier Lecrivain 30.04.2016 17:53
Ответ принят как подходящий

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

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

Wnoise ответ решает эти проблемы, но решение является специфичным для GNU (и довольно сложным).

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

For the record, here's the explanation for why it's generally not a good idea to parse ls output: http://mywiki.wooledge.org/ParsingLs

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

Note: This command operates in the current directory; to target a directory explicitly, use a subshell ((...)):
(cd /path/to && ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {})
The same applies analogously to the commands below.

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

Если у вас есть GNUxargs, используйте -d '\n', который заставляет xargs рассматривать каждую строку ввода как отдельный аргумент, но передает столько аргументов, сколько поместится в командной строке сразу:

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

-r (--no-run-if-empty) ensures that rm is not invoked if there's no input.

Если у вас есть BSDxargs (включая macOS), вы можете использовать -0 для обработки ввода, разделенного NUL, после первого перевода новой строки в символы NUL (0x0), который также передает (обычно) все имена файлов сразу (также будет работать с GNU xargs) :

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

Объяснение:

  • ls -tp печатает имена элементов файловой системы, отсортированные по дате их изменения, в порядке убывания (сначала измененные элементы) (-t), а каталоги печатаются с пометкой / в конце, чтобы пометить их как таковые (-p).

    • Примечание. Тот факт, что ls -tp всегда выводит только файл / каталог имена, а не полные пути, требует упомянутого выше подхода подоболочки для нацеливания на каталог, отличный от текущего ((cd /path/to && ls -tp ...)).
  • Затем grep -v '/$' исключает каталоги из результирующего списка, опуская (-v) строки, которые имеют завершающий / (/$).

    • Предостережение: поскольку символическая ссылка, указывающая на каталог технически не является каталогом, такие символические ссылки будут исключены.
  • tail -n +6 пропускает первые записи 5 в листинге, фактически возвращая все но 5 последних измененных файлов, если таковые имеются. Обратите внимание, что для исключения файлов N необходимо передать N+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

Конечно, лучше, чем большинство других ответов здесь, поэтому я рад оказать свою поддержку, даже поскольку я считаю, что игнорирование случая новой строки следует делать только с осторожностью.

Charles Duffy 18.01.2016 23:13

Если вы сделаете ls не в текущем каталоге, то пути к файлам будут содержать '/', что означает, что grep -v '/' ни с чем не будет соответствовать. Я считаю, что grep -v '/$' - это то, что вы хотите исключить только каталоги.

waldol1 29.02.2016 15:33

@ waldol1: Спасибо; Я обновил ответ, включив в него ваше предложение, которое также делает команду grep концептуально более понятной. Обратите внимание, однако, что описываемая вами проблема могла бы возникнуть в случае нет с единственным путем к каталогу; например, ls -p /private/var по-прежнему будет печатать только имена файлов. Только если вы передадите аргументы файла несколько (обычно через глобус), вы увидите фактические пути в выводе; например, ls -p /private/var/* (и вы также увидите содержимое соответствующих подкаталогов, если вы также не включили -d).

mklement0 29.02.2016 16:41

Эти команды работают с файлами в текущем каталоге. Я хотел запустить указанную выше команду BSD для файлов в другом каталоге ... / mnt / usb / openwrt. Я адаптировал ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm -- к этому -> ls -tp /mnt/usb/openwrt | grep -v '/$' | tail -n +6 | sed 's|^|/mnt/usb/openwrt/|' | tr '\n' '\0' | xargs -0 rm --

FlexMcMurphy 05.01.2021 02:16

@FlexMcMurphy, мне пришло в голову, что использование подоболочки ((...)) с cd проще и надежнее: (cd /mnt/usb/openwrt && ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --) - Я соответствующим образом обновил ответ.

mklement0 06.01.2021 16:22

Я превратил это в сценарий оболочки 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

нашел интересный cmd в Sed-One Liners - удалите последние 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

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

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 ; }'); 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

Возьмите второй столбец (имя файла, а не метку времени):

awk '{ print ; }'

А затем заверните все это в оператор for:

for F in $(); do rm $F; done

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

Удаляет все файлы, кроме 10 последних (большинство недавних).

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

Если меньше 10 файлов, файл не будет удален, и у вас будет: заголовок ошибки: недопустимое количество строк - 0

Чтобы подсчитать файлы с помощью bash

Мне нужно было элегантное решение для busybox (роутера), все решения xargs или массивов были для меня бесполезны - там нет такой команды. 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 (простая форма, пробелы не допускаются):

print "VarName = "$1

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

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

Другие вопросы по теме