Есть ли простой способ в довольно стандартной среде 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 '%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 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 '%T@ %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 '%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
Возьмите второй столбец (имя файла, а не временную метку):
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