Удалить все, кроме самых последних файлов X в Bash
93789 просмотра
16 ответа
Есть ли простой способ в довольно стандартной среде UNIX с bash выполнить команду, чтобы удалить все, кроме самых последних X-файлов из каталога?
Чтобы привести немного более конкретный пример, представьте себе, что какое-то задание cron записывает файл (скажем, файл журнала или архивную резервную копию) в каталог каждый час. Я хотел бы иметь способ запустить еще одно задание cron, которое бы удаляло самые старые файлы в этом каталоге, пока их не станет меньше, скажем, 5.
И чтобы было ясно, присутствует только один файл, его никогда не следует удалять.
Автор: Matt Sheppard Источник Размещён: 04.10.2019 11:11Ответы (16)
94 плюса
Проблемы с существующими ответами:
- невозможность обрабатывать имена файлов со встроенными пробелами или символами новой строки.
- в случае решений, которые вызываются
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
) chars., Который также передает (как правило) все имена файлов одновременно (также будет работать с 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
затем вызывается один раз для каждой входной строки, но с именами файлов со встроенными пробелами, которые обрабатываются правильно.--
во всех случаях гарантирует , что любые имена файлов , которые происходят , чтобы начать с-
не ошибаемся для опций по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
Автор: mklement0
Размещён: 18.01.2016 07:24
94 плюса
Удалите все, кроме 5 (или любого другого числа) самых последних файлов в каталоге.
rm `ls -t | awk 'NR>5'`
Автор: Espo
Размещён: 25.08.2008 08:41
85 плюса
(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
Автор: thelsdj
Размещён: 25.08.2008 08:42
58 плюса
Более простой вариант ответа thelsdj:
ls -tr | head -n -5 | xargs --no-run-if-empty rm
ls -tr отображает все файлы, сначала самые старые (сначала -t самые новые, -r наоборот).
head -n -5 отображает все, кроме 5 последних строк (то есть 5 новейших файлов).
xargs rm вызывает rm для каждого выбранного файла.
Автор: Fabien Размещён: 12.04.2012 08:2516 плюса
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, но обрабатывает файлы со встроенными символами новой строки или пробелами.
Автор: wnoise Размещён: 18.11.2008 07:5113 плюса
Все эти ответы терпят неудачу, когда есть каталоги в текущем каталоге. Вот то, что работает:
find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
Этот:
работает, когда есть каталоги в текущем каталоге
пытается удалить каждый файл, даже если предыдущий не может быть удален (из-за разрешений и т. д.)
терпит неудачу в безопасности , когда количество файлов в текущем каталоге является чрезмерным и ,
xargs
как правило , ввернуть Вас более года (-x
)не учитывает пробелы в именах файлов (возможно, вы используете не ту ОС?)
12 плюса
ls -tQ | tail -n+4 | xargs rm
Список имен файлов по времени модификации, цитируя каждое имя файла. Исключить первые 3 (3 самых последних). Удалить оставшиеся.
РЕДАКТИРОВАТЬ после полезного комментария от mklement0 (спасибо!): Исправлен аргумент -n + 3, и обратите внимание, что это не будет работать должным образом, если имена файлов содержат символы новой строки и / или каталог содержит подкаталоги.
Автор: Mark Размещён: 25.07.2013 05:518 плюса
Игнорирование новых строк игнорирует безопасность и хорошее кодирование. У wnoise был единственный хороший ответ. Вот вариант его, который помещает имена файлов в массив $ x
while IFS= read -rd ''; do
x+=("${REPLY#* }");
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )
Автор: Ian Kelling
Размещён: 13.06.2009 12:03
4 плюса
Если имена файлов не имеют пробелов, это будет работать:
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 плюса
С зш
Предполагая, что вы не заботитесь о существующих каталогах и у вас будет не более 999 файлов (выберите большее число, если хотите, или создайте цикл while).
[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])
В *(.om[6,999])
, на .
средства файлов, то o
средство порядок сортировки вверх, m
средства по дате модификации (положить a
на время доступа или c
для изменения инода), то [6,999]
выбирает диапазон файла, поэтому не Р.М. 5 первых.
2 плюса
Я понимаю, что это старая ветка, но, возможно, кому-то это поможет. Эта команда найдет файлы в текущем каталоге:
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
Это может быть более многословная команда, но мне повезло больше, когда я смог нацелиться на условные файлы и выполнить с ними более сложные команды.
Автор: TopherGopher Размещён: 10.01.2017 10:551 плюс
нашел интересный cmd в Sed-Onliners - удалите последние 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
Автор: tim
Размещён: 01.09.2016 09:09
1 плюс
Удаляет все, кроме 10 последних (большинство последних) файлов
ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm
Если менее 10 файлов, файл не будет удален, и у вас будет: error head: недопустимое количество строк - 0
Автор: fabrice Размещён: 29.05.2017 05:141 плюс
Мне нужно было элегантное решение для busybox (роутера), все решения xargs или array были для меня бесполезны - такой команды там не было. 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 "VarName="$1
установить переменную VarName
в значение $1
. Несколько переменных могут быть созданы за один раз. Это VarName
становится нормальной переменной sh и впоследствии может быть использовано в скрипте или оболочке. Итак, чтобы создать переменные с помощью awk и вернуть их обратно в оболочку:
eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\"" }'); echo "$VarName"
Автор: Pila
Размещён: 03.10.2018 05:48
0 плюса
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
Автор: Pavel Tankov
Размещён: 07.06.2013 07:34
0 плюса
Я сделал это в скрипт оболочки 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
Автор: Bulrush
Размещён: 03.02.2016 05:47
Вопросы из категории :
- bash Как разрешить символические ссылки в сценарии оболочки
- bash Удалить все, кроме самых последних файлов X в Bash
- bash Распараллелить скрипт Bash с максимальным количеством процессов
- bash Как мне написать цикл для Bash
- bash Как обнулить числа в именах файлов в Bash?
- bash Get the source directory of a Bash script from within the script itself
- unix Эквивалент Windows «хороший»
- unix Использование X Window X Window Server через VPN
- unix Как вы делаете нечувствительный к регистру поиск с использованием модификатора шаблона, используя меньше?
- unix Проверьте, существует ли каталог в сценарии оболочки
- unix Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?
- scripting Удалить все таблицы, имена которых начинаются с определенной строки
- scripting В SQL Server, как мне сгенерировать оператор CREATE TABLE для данной таблицы?
- scripting Как я могу получить путь и имя файла, который выполняется в настоящее время?
- scripting Как создать пользовательский тип в PowerShell для использования в моих сценариях?