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

Вопрос: Как вы удаляете все файлы в каталоге, кроме самого нового 3?

Найти новейшие 3 файла очень просто:

ls -t | head -3

Но мне нужно найти все файлы, кроме 3 новейших файлов. Как мне это сделать, и как я могу удалить эти файлы в одной строке, не делая ненужного цикла for для этого?

Для этого я использую Debian Wheezy и скрипты bash.

7 ответов

Решение

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

ls -t | tail -n +4

Это удалит эти файлы:

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

Это также перечислит точечные файлы:

ls -At | tail -n +4

и удалите с помощью файлов точек:

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

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

Если вы разрабатываете сценарий для повторного использования, то вам, безусловно, не следует анализировать вывод ls и используйте методы, описанные здесь: http://mywiki.wooledge.org/ParsingLs

Это сочетание ответа Кевинга и Анубхавы. Оба решения не работают для меня. Поскольку я искал сценарий, который должен запускаться каждый день для резервного копирования файлов в архиве, я хотел избежать проблем с ls (кто-то мог сохранить какой-нибудь забавный файл с именем в моей папке для сохранения резервных копий). Поэтому я изменил упомянутые решения в соответствии со своими потребностями. Решение Ceving удаляет три новейших файла - не то, что мне было нужно, и меня об этом спросили.

Мое решение удаляет все файлы, кроме трех новейших файлов.

find . -type f -printf '%T@\t%p\n' |
sort -t $'\t' -g | 
head -n -3 | 
cut -d $'\t' -f 2- |
xargs rm

Некоторое объяснение:

find перечисляет все файлы (не каталоги) в текущей папке. Они распечатываются с отметками времени.
sort сортирует строки по отметке времени (самая старая сверху).
head печатает верхние строки, до последних 3 строк.
cut удаляет временные метки.
xargs работает rm для каждого выбранного файла.

Для проверки моего решения:

(
touch -d "6 days ago" test_6_days_old
touch -d "7 days ago" test_7_days_old
touch -d "8 days ago" test_8_days_old
touch -d "9 days ago" test_9_days_old
touch -d "10 days ago" test_10_days_old
)

Это создает 5 файлов с разными временными метками в текущей папке. Сначала запустите это и код для удаления, чтобы проверить код.

Следующее выглядит немного сложным, но очень осторожно, чтобы быть правильным, даже с необычными или намеренно злонамеренными именами файлов. К сожалению, это требует инструментов GNU:

count=0
while IFS= read -r -d ' ' && IFS= read -r -d '' filename; do
  (( ++count > 3 )) && printf '%s\0' "$filename"
done < <(find . -maxdepth 1 -type f -printf '%T@ %P\0' | sort -g -z) \
     | xargs -0 rm -f --

Объясняя, как это работает:

  • Найти выбрасывает <mtime> <filename><NUL> для каждого файла в текущем каталоге.
  • sort -g -z выполняет общую (с плавающей точкой, в отличие от целочисленного) числовую сортировку на основе первого столбца (времен) со строками, разделенными NUL.
  • Первый read в while цикл удаляет mtime (больше не требуется после sort готово).
  • Второй read в while Цикл читает имя файла (работает до NUL).
  • Цикл увеличивает, а затем проверяет счетчик; если состояние счетчика указывает на то, что мы пропустили первоначальный пропуск, то мы печатаем имя файла, разделенное NUL.
  • xargs -0 затем добавляет это имя файла в список argv, который он собирает для вызова rm с.
ls -t | tail -n +4 | xargs -I {} rm {}

Если вы хотите 1 вкладыш

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

Вы можете сделать это, используя все gnu На основе утилиты для удаления всех, кроме 3-х новейших файлов в текущем каталоге:

find . -maxdepth 1 -type f -printf '%T@\t%p\0' |
sort -z -nrk1 |
tail -z -n +4 |
cut -z -f2- |
xargs -0 rm -f --

В зш:

rm /files/to/delete/*(Om[1,-4])

Если вы хотите включить точечные файлы, замените часть в скобках на (Om[1,-4]D),

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

Пояснение: в скобках указаны Glob Qualifiers. O означает "упорядочить по убыванию", m означает mtime (см. man zshexpn для других ключей сортировки - большая справочная страница; поиск "быть отсортированным"). [1,-4] возвращает только совпадения с единичным индексом 1 - (последний + 1 - 4) (обратите внимание на -4 для удаления всех, кроме 3).

ls -t | tail -n +4 | xargs -I {} rm {}

Ответ Майкла Баллента лучше всего работает как

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

выведите мне ошибку, если у меня меньше 3 файлов

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

Также обрабатывает файлы / каталоги с пробелами, новой строкой и другими нечетными символами

#!/bin/bash
if (( $# != 2 )); then
  echo "Usage: $0 </path/to/top-level/dir> <num files to keep per dir>"
  exit
fi

while IFS= read -r -d $'\0' dir; do
  # Find the nth oldest file
  nthOldest=$(find "$dir" -maxdepth 1 -type f -printf '%T@\0%p\n' | sort -t '\0' -rg \
    | awk -F '\0' -v num="$2" 'NR==num+1{print $2}')

  if [[ -f "$nthOldest" ]]; then
    find "$dir" -maxdepth 1 -type f ! -newer "$nthOldest" -exec rm {} +
  fi
done < <(find "$1" -type d -print0)

Доказательство концепции

$ tree test/
test/
├── sub1
│   ├── sub1_0_days_old.txt
│   ├── sub1_1_days_old.txt
│   ├── sub1_2_days_old.txt
│   ├── sub1_3_days_old.txt
│   └── sub1\ 4\ days\ old\ with\ spaces.txt
├── sub2\ with\ spaces
│   ├── sub2_0_days_old.txt
│   ├── sub2_1_days_old.txt
│   ├── sub2_2_days_old.txt
│   └── sub2\ 3\ days\ old\ with\ spaces.txt
└── tld_0_days_old.txt

2 directories, 10 files
$ ./keepNewest.sh test/ 2
$ tree test/
test/
├── sub1
│   ├── sub1_0_days_old.txt
│   └── sub1_1_days_old.txt
├── sub2\ with\ spaces
│   ├── sub2_0_days_old.txt
│   └── sub2_1_days_old.txt
└── tld_0_days_old.txt

2 directories, 5 files

Как продолжение ответа flohail. Если вы хотите удалить все папки, кроме трех новейших папок, используйте следующее:

find . -maxdepth 1 -mindepth 1 -type d -printf '%T@\t%p\n' |
 sort -t $'\t' -g | 
 head -n -3 | 
 cut -d $'\t' -f 2- |
 xargs rm -rf

В -mindepth 1 игнорирует родительскую папку и -maxdepth 1 вложенные папки.

Это использует find вместо ls с преобразованием Шварца.

find . -type f -printf '%T@\t%p\n' |
sort -t $'\t' -g |
tail -3 |
cut -d $'\t' -f 2-

find ищет файлы и украшает их отметкой времени и использует табулятор для разделения двух значений. sort разбивает ввод с помощью табулятора и выполняет обычную числовую сортировку, которая правильно сортирует числа с плавающей запятой. tail должно быть очевидным и cut undecorates.

Проблема с украшениями в целом заключается в том, чтобы найти подходящий разделитель, который не является частью ввода, имен файлов. В этом ответе используется символ NULL.

Ниже сработало для меня: (Ура)

rm -rf $(ll -t | tail +5 | awk '{ print $9}')

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