Сведения о вопросе

NOTtoday

18:11, 2nd August, 2020

Теги

bash   unix   scripting    

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

Просмотров: 770   Ответов: 17

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

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

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



  Сведения об ответе

FAriza

20:20, 5th August, 2020

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

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


  Сведения об ответе

screen

17:39, 18th August, 2020

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

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

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

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

Для справки, вот объяснение того, почему вообще не стоит разбирать вывод ls : http://mywiki.wooledge.org/ParsingLs


  Сведения об ответе

+-*/

18:11, 25th August, 2020

(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


  Сведения об ответе

JUST___

19:20, 16th August, 2020

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

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

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

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

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


  Сведения об ответе

nYU

08:51, 15th August, 2020

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, но обрабатывает файлы со встроенными новыми строками или пробелами.


  Сведения об ответе

DINO

08:46, 26th August, 2020

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

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

Этот:

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

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

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

  4. не подходит для пробелов в именах файлов (возможно, вы используете неправильный OS?)


  Сведения об ответе

+-*/

08:40, 3rd August, 2020

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

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

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


  Сведения об ответе

nYU

00:51, 29th August, 2020

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

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


  Сведения об ответе

LIZA

08:16, 27th August, 2020

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

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

Если в именах файлов есть пробелы, что-то вроде

ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh

Основная логика:

  • получить список файлов в порядке времени, один столбец
  • получить все, кроме первых 5 (n=5 для этого примера)
  • первый вариант: отправить это на РМ
  • вторая версия: gen скрипт, который удалит их должным образом


  Сведения об ответе

lesha

20:23, 16th August, 2020

С zsh

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

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

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


  Сведения об ответе

lourence

01:12, 1st August, 2020

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

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-й столбец (имя файла, а не timestamp):

awk '{ print $2; }'

А затем оберните все это в заявление для утверждения:

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

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


  Сведения об ответе

PROGA

10:27, 14th August, 2020

нашел интересное 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


  Сведения об ответе

DINO

12:47, 14th August, 2020

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

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

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

Для подсчета файлов с помощью bash


  Сведения об ответе

lesha

17:42, 16th August, 2020

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

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 и может быть использована в скрипте или shell впоследствии. Итак, чтобы создать переменные с awk и вернуть их обратно в shell:

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


  Сведения об ответе

appple

15:28, 20th August, 2020

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


  Сведения об ответе

LAST

09:07, 8th August, 2020

Я превратил это в сценарий bash shell. Использование: 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


  Сведения об ответе

lesha

19:36, 23rd August, 2020

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

что весьма раздражает..

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

Сценарий, который я запустил, чтобы удалить самый старый файл, был:

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться