Удалить файлы, которых нет в списке шаблонов

У меня есть веб-сайт по продаже автомобилей для клиента. Они постоянно добавляют и убирают машины. Когда приходит новый, они добавляют пакет изображений, и веб-сайт генерирует миниатюру для каждого. На сайте хранится базовое имя файла (через которое я могу получить доступ как к миниатюре, так и к оригиналу). Вот пример:

5e1adcf7c9c1bcf8842c24f3bacbf169.jpg
5e1adcf7c9c1bcf8842c24f3bacbf169_tn.jpg
5e1de0c86e45f84b6d01af9066581e84.jpg
5e1de0c86e45f84b6d01af9066581e84_tn.jpg
5e2497180424aa0d5a61c42162b03fef.jpg
5e2497180424aa0d5a61c42162b03fef_tn.jpg
5e2728ac5eff260f20d4890fcafb1373.jpg
5e2728ac5eff260f20d4890fcafb1373_tn.jpg

Проблема возникает после удаления продукта. В моем существующем рабочем процессе нет простого способа удалить старые изображения. В течение нескольких месяцев мы получаем 10000 изображений, из которых только 10% живут.

Я могу искать в базе данных и генерировать список живых изображений :

5e1adcf7c9c1bcf8842c24f3bacbf169
5e2497180424aa0d5a61c42162b03fef

Я хочу удалить изображения, которые не начинаются с эти заглушки.

Обратите внимание, что время / пространство производительность также является проблемой здесь. Есть ~ 500 + заглушки в любой момент времени. Я пробовал использовать gres ls, например:

ls | grep -vf <(
    sqlite3 database.sqlite3 'select replace(images, CHAR(124), CHAR(10)) from cars_car'
)

Это работает, но это очень медленно (и вы не должны анализировать ls ). Запрос быстрый, так что это бит grep, который сводит все это на нет. Я хотел бы лучшие решения. Bash не нужен, но это то, чем я занимаюсь в большинстве своих сценариев обслуживания.

4
задан 22 March 2015 в 15:53

7 ответов

Долгосрочное решение, к которому я допускаю ошибку, является чем-то в конце моего сценария обновления (Python/Django). У меня есть список Автомобильных объектов — так больше базы данных querying—, который делает это еще быстрее. Это также происходит в точное время, старые изображения прекращают быть полезными.

я использую Python set, потому что это - вероятно, самый быстрый способ проверить. В это я добавляю все тупики изображений, которые я хочу сохранить, затем я выполняю итерации через миниатюры (легче к шарику) и удаляю файлы, которые не находятся в наборе.

# Generate a python "set" of image stubs
import itertools
imagehashes = set(itertools.chain(*map(lambda c: c.images.split('|'), cars)))

# Check which files aren't in the set and delete
import glob, os
for imhash in map(lambda i: i[25:-7], glob.glob('/path/to/images/*_tn.jpg')):
    if imhash in imagehashes:
        continue

    os.remove('/path/to/images/%s_tn.jpg' % imhash)
    os.remove('/path/to/images/%s.jpg' % imhash)

существует несколько приемов с map и itertools, чтобы сэкономить немного времени, но это главным образом сам объяснительное.

1
ответ дан 22 March 2015 в 15:53

запрос быстр, таким образом, это эти grep бит, который срывает все это.

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

Это не должно представлять различие в синхронизации вообще.

0
ответ дан 22 March 2015 в 15:53

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

Мы можем просто сделать полные поиски строки без regex, с помощью -F аргумент.

find | grep -vFf <(
    sqlite3 database.sqlite3 'select replace(images, CHAR(124), CHAR(10)) from cars_car'
) ### | xargs rm

вывод является тем же и работает в 0,045 с.
старый занял 14,211 с.

<час>

Одной из проблем с парсингом ls являются проблематичные имена файлов. комментарий muru ниже выделяет довольно достойный способ использовать нулевые символы через весь конвейер.

find -print0 | grep -vzFf <(
    sqlite3 database.sqlite3 'select replace(images, CHAR(124), CHAR(10)) from cars_car'
) ### | xargs -0 rm

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

3
ответ дан 22 March 2015 в 15:53

Я предположил бы, что это будет и более простым и быстрее, чтобы просто использовать GLOBIGNORE (предположение, что Ваша оболочка является ударом так или иначе):

   GLOBIGNORE
          A colon-separated list of patterns defining the set of filenames
          to be ignored by pathname expansion.  If a filename matched by a
          pathname expansion pattern also matches one of the  patterns  in
          GLOBIGNORE, it is removed from the list of matches.

Так, Вы могли просто считать шаблоны, которые Вы хотите из своего файла, добавляете * для создания их, шарики и преобразовать в двоеточие разделили список:

GLOBIGNORE=$(sqlite3 database.sqlite3 'select images from cars_car;' |
             sed 's/|/*:/g; s/$/*/')

Затем Вы можете всего rm все, и сбросить GLOBIGNORE (или просто закрыть текущий терминал):

rm * && GLOBIGNORE=""

, поскольку GLOBIGNORE будет теперь похож на это:

$ echo $GLOBIGNORE 
5e1adcf7c9c1bcf8842c24f3bacbf169*:5e2497180424aa0d5a61c42162b03fef*

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

3
ответ дан 22 March 2015 в 15:53

Если Вы используете bash в качестве своей оболочки, то shopt -s extglob может активировать еще некоторые опции в шаблонах шарика. Например

!(5e1adcf7c9c1bcf8842c24f3bacbf169*|5e2497180424aa0d5a61c42162b03fef*)

будет соответствовать всем именам, не запускающимся с одной из двух строк.

1
ответ дан 22 March 2015 в 15:53

Вы могли просто удалить изображения в рамках выполнения сценария удаления продукта. Таким образом, загрузка будет балансироваться через каждое удаление продукта со временем. Дополнительно Вы не должны будете волноваться о выполнении сценария для чистки их вообще, и целое приложение будет автономной системой. Не говоря уже о нем решил бы проблему пространства с этой целью.

я понятия не имею, о котором DBMS Вы используете, ни о котором языке сценариев Вы используете для управления им или о том, как структура базы данных похожа (никакая идея о пути изображений также), но например, принимая MySQL как DBMS, PHP как язык сценариев и Products таблица в 1-many отношениях с Images таблица, с путем изображений, указывающим img папка, помещенная под корневым каталогом, это было бы что-то вроде этого:

<?php
    // ...
    $imgPath = $SERVER['DOCUMENT_ROOT'].'/img/';
    $result = mysqli_query($link, "SELECT Images.basename FROM Products, Images WHERE Products.productId = Images.productId AND Products.productId = $productId)
    while($row = mysqli_fetch_assoc($result)) {
        unlink($imgPath.$row['Images.basename'].'.jpg');
        unlink($imgPath.$row['Images.basename'].'_tn.jpg');
    }
    // ...
?>

, Если Вы заинтересованы [приблизительно 117] действия, Вы могли бы всегда использовать:

<?php
    // ...
    $imgPath = $SERVER['DOCUMENT_ROOT'].'/img/';
    $result = mysqli_query($link, "SELECT Images.basename FROM Products, Images WHERE Products.productId = Images.productId AND Products.productId = $productId)
    while($row = mysqli_fetch_assoc($result)) {
        shell_exec("rm {$imgPath}{$row['Images.basename']}*");
    }
    // ...
?>

Опасения по поводу них решение могло бы быть о дополнительном запросе, который необходимо будет выполнить каждый раз, если Вы уже не вытягиваете от Images прежде в сценарии и если это - беспокойство вообще.

1
ответ дан 22 March 2015 в 15:53

Когда чистый удар не сокращает его (или становится излишне неловким), пора переключиться на надлежащий язык сценариев. Мой предпочтительный инструмент обычно является Perl, но Вы могли использовать Python или Ruby или, heck, даже PHP для этого, если Вы предпочтете.

Тем не менее вот простой сценарий Perl, который читает список префиксов от stdin (так как Вы не указывали точно, как Вы получаете этот список), один на строку, и удаляет все файлы в текущем каталоге с a .jpg суффикс, которые не имеют одного из этих префиксов:

#!/usr/bin/perl
use strict;
use warnings;

my @prefixes = <>;
chomp @prefixes;
# if you need to do any further input mangling, do it here

my $regex = join "|", map quotemeta, @prefixes;
$regex = qr/^($regex)/;   # anchor the regex and precompile it

foreach my $filename (<*.jpg>) {
    next if $filename =~ $regex;
    unlink $filename or warn "Error deleting $filename: $!\n";
}

Если Вы предпочли бы, можно сжать это вниз до остроты, например:

perl -e '$re = "^(" . join("|", map { chomp; "\Q$_" } <>) . ")"; unlink grep !/$re/, <*.jpg>'

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

my %hash;
undef @hash{@prefixes};   # fastest way to add keys to a hash

foreach my $filename (<*.jpg>) {
    my ($prefix) = ($filename =~ /^([0-9a-f]+)/);
    next if exists $hash{$prefix};
    unlink $filename or warn "Error deleting $filename: $!\n";
}

Однако даже при том, что этот метод масштабируется лучше асимптотически (по крайней мере на практике; в теории regex механизм мог оптимизировать соответствие для масштабирования, а также метод хеша), всего для 500 префиксов вообще нет никакого заметного различия.

По крайней мере, на текущих версиях Perl, однако, regex решение становится намного медленнее, после того как количество альтернатив превышает определенный предел. Для 32-байтовых префиксов мое тестирование показало крупный переход во время выполнения, когда количество альтернатив достигло 6553, но точный порог, по-видимому, также зависит от длины префиксов и на том, что еще, во всяком случае, содержит regex. Это - по-видимому, причуда Perl regex механизм и его оптимизатор, таким образом, другие regex реализации (даже PCRE) могут показать другое поведение.

1
ответ дан 22 March 2015 в 15:53

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

Похожие вопросы: