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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

(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

Ответ 3

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

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

Ответ 4

Упрощенный вариант ответа thelsdj:

ls -tr | head -n -5 | xargs rm

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

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

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

Ответ 5

find . -maxdepth 1 -type f -printf '%[email protected] %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 sort для -z и GNU awk для "\ 0" и GNU xargs для -0, но обрабатывает файлы со встроенными новыми строками или пробелами.

Ответ 6

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

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

Это:

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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 a script, которая удалит их правильно

Ответ 10

С zsh

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

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

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

Ответ 11

нашел интересный cmd в Sed-Onliners - удалите последние 3 строки - fnd он идеально подходит для другого способа скинуть кошку (хорошо, но не), но идея:

 #!/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

Ответ 12

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

for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%[email protected] %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 '%[email protected] %p\n'

Затем отсортируйте их по меткам времени:

sort -r -z -n

Затем отмените 4 последних файла из списка:

tail -n+5

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

awk '{ print $2; }'

И затем оберните все это в инструкцию for:

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

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

Ответ 13

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

Ответ 14

Я сделал это в оболочке bash script. Использование: 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

Ответ 15

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

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

Если менее 10 файлов не удаляется файл, и у вас будет: error head: недопустимый номер строки - 0

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

Ответ 16

Запуск на Debian (предположим, что он же на других дистрибутивах я получаю: rm: невозможно удалить каталог `.. '

что довольно неприятно.

Во всяком случае, я подделал выше, а также добавил grep к команде. В моем случае у меня есть 6 резервных файлов в каталоге, например. file1.tar file2.tar file3.tar и т.д., и я хочу удалить только самый старый файл (удалите первый файл в моем случае)

script Я побежал, чтобы удалить самый старый файл:

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

Это (как указано выше) удаляет первый из моих файлов, например. file1.tar это также оставляет файл file2 file4 file5 и file6