Журнал LinuxFormat - перейти на главную

LXF148:tut5

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

Команды: GNU/Linux и смекалка

Тихон Тарнавский рассказывает, как средствами командной строки

навести порядок в коллекции фотографий.


На этот раз мы займемся картинками. Нет, обрабатывать изображения я не предлагаю. Хотя для этого тоже есть утилиты командной строки, но работа с ними весьма специфична и, говоря прямо, к навыкам решения более общих задач в командной строке имеет очень мало отношения. Работать будем не с отдельной картинкой, а с коллекцией картинок. Наверное, почти у каждого на компьютере есть такая коллекция, собранная из многих разных источников: с сайтов в интернете, с фотоаппарата и так далее. Обычно она все время пополняется, и хотя бы изредка ее приходится упорядочивать. Конечно, распределить фотографии по тематике часто только вручную и можно, но есть и другие, более механические задачи. Например, если вы хотя бы раз сохраняли целиком web-страницы с картинками или загружали к себе на компьютер целые сайты, то, помимо нужных вам изображений, получили огромное количество других файлов. Потом эти файлы и под руками путаются, и в то же время обойти многочисленные каталоги и все почистить – работа нудная и заниматься ею никогда не хочется. Думаю, идея уже ясна: возложить эту нудную работу на наших «слуг проворных, удалых» – командную оболочку и связанные ею отдельные команды и программы.

Для начала поставим задачу в целом. Нам нужно избавиться от разнообразных вспомогательных файлов, вроде самих web-страниц, скриптов, стилей и тому подобное – то есть от всего, кроме изображений. Кроме того, нужно вычистить все оформительские и рекламные элементы; а это обычно изображения небольшого размера (по крайней мере, по одной из сторон). Тогда и должно остаться только требуемое нам.

(thumbnail)
Рис. 1. Вывод имен файлов изображений с помощью «двойного» grep.

Отделяем зерна от плевел

Начнем с удаления побочных файлов. Первым делом нужно найти все обычные файлы в заданном каталоге (как это сделать, мы уже рассматривали: см. статью в LXF145). Затем отсеем все прочие файлы кроме картинок. Как мы можем отличить «прочий файл» от картинки? По расширению? Ответ неверный. Во-первых, человек не обязан знать все расширения, которые бывают у картинок. Во-вторых, имя файла может быть не совсем правильным, а поскольку все неподходящие файлы мы будем удалять, досадно будет ошибиться. А уж проверять содержимое файла на то, что это действительно картинка, человек не только не может, но и не должен – тогда теряется весь смысл затеи. А то, что не должен делать человек, за человека должна сделать программа. И в GNU/Linux есть специальная программа, выдающая тип файла по его содержимому. Называется она более чем очевидно: file. Проверим, как выглядит ее вывод, в любом каталоге с картинками (здесь * – это специальный шаблон, который раскрывается командной оболочкой в полный список файлов в текущем каталоге):

$ file *
1_002.jpg: JPEG image data, JFIF standard 1.01
1.jpg: JPEG image data, EXIF standard, baseline, precision 0, 4360x902
11.js: HTML document text
12.gif: GIF image data, version 89a, 167 x 9
14.js: HTML document text
2_002.jpg: JPEG image data, JFIF standard 1.01
2.jpg: JPEG image data, EXIF standard, baseline, precision 0, 4360x902
3_002.jpg: JPEG image data, JFIF standard 1.01
3.gif: GIF image data, version 89a, 56 x 57
3.jpg: JPEG image data, EXIF standard, baseline, precision 0, 4360x902
4_002.jpg: JPEG image data, JFIF standard 1.01
4.jpg: JPEG image data, EXIF standard, baseline, precision 0, 4360x902
common.js: ISO-8859 text, with CRLF line terminators
full.png: PNG image data, 2000 x 1000, 8-bit colormap, non-interlaced
help.css: ASCII text, with CRLF line terminators
main1.css: ISO-8859 assembler program text, with CRLF line terminators
page_btn.gif: GIF image data, version 89a, 31 x 31

Как видите, каждая строка с файлом изображения содержит фразу “image data”. У команды file есть и немного другой режим работы – в нем она выдает не текстовые описания типов файлов, а так называемые «mime-типы», то есть строки вида “text/html” или “image/gif”. Казалось бы, логичнее использовать именно его и отбирать файлы по строке “image” в описании. Но увы, к тому же mime-типу image относится, к примеру, и подтип image/djvu, который картинкой в интересующем нас сейчас понимании не является. А вот его описание в текстовом виде выглядит как “DjVu multiple page document”, то есть строки “image data” не содержит. Так что будем обрабатывать текстовые описания. С командой grep, отфильтровывающей нужные строки текста, мы уже знакомы. Но раньше нам нужно было оставлять строки, содержащие заданный текст, а сейчас – наоборот: те, в которых заданного текста нет. В man grep нетрудно найти нужную опцию – v. Значит, передадим список найденных файлов команде file, а полученные описания отфильтруем командой grep -v:

find -type f -print0 | xargs -0 file | grep -v “ image data”

Из полученных строк нужно вычленить имена файлов. На первый взгляд это можно сделать командой cut, использовав в качестве разделителя полей двоеточие; на самом деле не все так просто. Ведь если двоеточие встретится в имени файла, то мы получим лишь часть имени до этого двоеточия. Нужно оставить все поля, кроме последнего. Но как это сделать, не зная их количества? Самый очевидный способ – перевернуть строку, затем выделить все поля, кроме первого, затем перевернуть обратно. Команда cut позволяет задавать диапазоны полей (два номера через дефис); в том числе и незамкнутые на конце (номер после дефиса опускается), что означает «отсюда и до конца строки». Осталось только перевернуть строку. А с этим нам поможет команда rev (от англ. reverse – переворачивать) из пакета util-linux. Этот пакет скорее всего уже установлен в вашей системе, так что можно нужную команду сразу использовать. Используется она очень просто: не принимает вообще никаких опций и только то и делает, что переставляет символы в каждой строке в обратном порядке.

find -type f -print0 | xargs -0 file | grep -v “ image data” | rev | cut -d : -f 2- | rev

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

Первый, более очевидный, подходит, когда вы хотите проверить, что будет удалено: временно вставить в полученный конвейер вместо команды удаления команду вывода списка файлов – ls.

Второй удобен для просмотра того, что останется: предварительно скопировать обрабатываемый каталог. Не бойтесь, что это будет долго: копировать мы будем тоже в стиле unix-way. В Unix-совместимых системах содержимое файла и его имя можно рассматривать как два отдельных, хотя и взаимосвязанных объекта. Имя всего лишь ссылается на содержимое, а таких ссылок может быть несколько. То есть один и тот же файл может иметь одновременно несколько имен. Называются эти ссылки-имена жесткими ссылками [hard links] – кроме них, есть еще ссылки символические [symbolic links], которые мы скорее всего тоже будем использовать в следующих статьях. Само содержимое файла считается удаленным только после того, как на него не осталось ни одной жесткой ссылки. Понятно, чем жесткие ссылки удобны в нашем случае: на создание дополнительной ссылки по сравнению с копированием содержимого файла, можно сказать, вообще не тратится время. Думаете, опять придется что-то искать и передавать куда-то по конвейеру? Ничего подобного, все уже предусмотрено: у команды копирования cp есть ключ l, который заставляет ее вместо копирования содержимого создавать на него дополнительную жесткую ссылку. Достаточно скомбинировать этот ключ с ключом r, отвечающим за рекурсивное копирование каталогов со всеми вложенными подкаталогами и файлами – и вы получите во временном каталоге полный дубликат всех подкаталогов и жестких ссылок (имен файлов) без копирования самого содержимого файлов (только убедитесь предварительно, что каталога или файла с таким же именем еще нет в месте назначения):

cp -rl ~/media/images ~/tmp

Теперь можете экспериментировать с копией сколько угодно, а если что-то пойдет не так, выполнить эту команду заново. Подстелив соломки, можем смело «прыгать» – добавлять команду rm, удаляющую файлы:

find -type f -print0 | xargs -0 file | grep -v “ image data” | rev | cut -d : -f 2- | rev | xargs rm

Ловись, картинка большая

(thumbnail)
Рис. 2. Вывод команды identify по умолчанию.

Теперь, когда все, кроме картинок, мы удалили, осталось отобрать их по размеру. Здесь уже команда file не справится, так как для некоторых типов изображений она размер в точках не выдает. Но теперь мы уже знаем, что все оставшиеся файлы – картинки, поэтому логично будет обратиться к более специализированной программе. Пакет таких специализированных программ, работающих с изображениями из командной строки, называется imagemagic. Он состоит из одиннадцати программ, каждая из которых выполняет свой узкий набор задач (краткое описание можно найти в man ImageMagic). Нам из них понадобится программа под названием identify. В отличие от других программ из этого пакета, она никак не изменяет сами файлы изображений, а только выводит различную техническую информацию о них. По умолчанию, без опций identify выводит имя файла, тип изображения (который может не совпадать с «расширением»), разрешение и еще несколько параметров, которые нас сейчас не интересуют (рис. 2). Как видно на снимке экрана, в конце было выдано некое сообщение об ошибке, поэтому при добавлении identify в конвейер нужно не забыть перенаправить поток ошибок «в никуда».

Из вывода identify нам в первую очередь надо вычленить разрешение картинки. Тут нам на помощь снова придет команда grep: она умеет отфильтровывать не только целые строки, но и части строк. Для этого следует задать опцию o и специальный «шаблон», под который должна подойти нужная часть строки. На самом деле называть это шаблоном не совсем верно – лучше не смешивать с шаблоном имен файлов в командной оболочке (потому я и взял это слово в кавычки); правильное название – регулярное выражение [regular expression]. Регулярные выражения – очень гибкий инструмент, но именно поэтому не слишком простой, поэтому изучать их детально мы пока не будем. Упомяну только два обозначения, которые нам пригодятся, чтобы вычленить интересующую нас часть строки. А интересует нас разрешение, которое записано как «число, буква x, число» и окружено пробелами.

(thumbnail)
Рис. 3. Вывод размеров всех картинок в каталоге.

Квадратные скобки со списком символов внутри означают любой из перечисленных символов. Причем символы, идущие подряд, можно обозначать как диапазон «начало-конец». То есть понятие «любая цифра» переводится в регулярные выражения так: [0-9]. А число – это последовательность цифр произвольной длины. Поэтому следующий элемент регулярного выражения, который нам понадобится, это звездочка: она означает повторение того, что записано перед ней, любое количество раз (начиная от нуля). Значит, понятие «любое число» на языке регулярных выражений выглядит так: [0-9]*. А полностью искомое запишется так: ' [0-9]*x[0-9]* ' (рис. 3).

Правильнее всего будет отсеивать картинки не по какому-то одному из размеров, а по минимальному. Ведь среди элементов оформления web-страниц бывают и узкие полосы довольно большой длины. Поэтому напишем маленький вспомогательный скрипт, вычленяющий два числа из заданной строки и выводящий меньшее из них. Глядишь, и в будущем когда-нибудь пригодится! Назовем его ~/bin/min (не забудьте добавить к файлу право на исполнение). Вырезать числа из строки будем все тем же grep -o; а брать саму строку – из параметров командной строки. И сохранить найденные в строке числа тоже будет удобнее всего на месте параметров командной строки. Делается это командой set.

#!/bin/bash
set $(grep -o '[0-9]*')

Возможно, вас смутило, что команде grep ничего не передано на вход. Тут используется еще одна замечательная возможность командной оболочки: в этом случае первая команда в скрипте будет обрабатывать стандартный ввод скрипта, то есть сам скрипт можно будет использовать в конвейерах. Если в этом вводе встретится несколько чисел, то grep -o выведет их последовательно. А с помощью команды set они сохранятся в уже знакомых нам «переменных» $1, $2 и так далее. Впрочем, то, что далее, нас не интересует, так как сравнивать мы будем только первые два числа. Для сравнения используем не менее знакомую команду [(test) с параметром -lt (less than – меньше, чем). Почему условие «меньше, чем» записано буквами, а не привычным всем значком <? Да потому, что этот значок в командной оболочке уже задействован для перенаправления ввода.

#!/bin/bash
set $(grep -o '[0-9]*')
if [ “$1” -lt “$2” ]; then
 echo $1
else
 echo $2
fi

Теперь нам нужно снова искать все файлы и обрабатывать найденное построчно. Для этого передадим вывод find циклу while [пока] с командой read [читать] в качестве условия. Этот цикл повторяется до тех пор, пока не нарушено условие. А команда read читает одну строку, сохраняет ее в заданной переменной, а если читать больше нечего, завершается с ошибкой. Так что while read – это и есть построчная обработка ввода, пока он не закончится.

find -type f | while read f; do...

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

find -type f | while read f; do
 echo $(identify “$f” 2>/dev/null | grep -o ' [0-9]*x[0-9]* ' | min) $f
done

Теперь отсортируем полученный список числовой сортировкой и удалим само число (первое отделенное пробелом поле) командой cut, оставив только имя файла:

find -type f | while read f; do
 echo $(identify “$f” 2>/dev/null | grep -o ' [0-9]*x[0-9]* ' | min) $f
done | sort -n | cut -d ' ' -f 2-

Наконец, передадим отсортированный список файлов программе просмотра. Я рекомендую для просмотра использовать pqiv (pretty quick image viewer – очень быстрая смотрелка картинок), она в этом случае очень хорошо подходит: во-первых, действительно быстрая; во-вторых, позволяет проматывать картинки не только по одной (клавишами Space/BackSpace), но и по 10 (PageUp/PageDn); и в-третьих, может отображать информацию о файле, включая разрешение, прямо во время просмотра. Запустим ее в полноэкранном режиме и найдем нужную «грань»:

pqiv -f $(find -type f | while read f; do

echo $(identify “$f” 2>/dev/null | grep -o ' [0-9]*x[0-9]* ' | min) $f;

done | sort -n | cut -d ' ' -f 2-)

У меня эта грань получилась равна 220 точкам – ниже нее попали не только оформительские элементы сайтов, но и все «картинки предпросмотра». Найдя нужное число по своим файлам, остается немного модифицировать тот же цикл, добавив в него сравнение меньшего из размеров картинки с найденным числом и удаление файла при выполнении условия:

find -type f | while read f; do
 [ “$(identify “$f” 2>/dev/null | grep -o ' [0-9]*x[0-9]* ' | min)” -lt 220 ] && rm “$f”
done
Или – в одну строку:
find -type f | while read f; do [ “$(identify “$f” 2>/dev/null | grep -o ' [0-9]*x[0-9]* ' | min)” -lt 220 ] && rm “$f”; done

А по мне, они одинаковые

В достаточно большой коллекции вполне могут встретиться одинаковые картинки. Это могут быть либо полностью идентичные файлы, либо разные варианты одного изображения – например, в разном разрешении или в разных форматах. Хорошо бы от таких дубликатов коллекцию тоже почистить. Здесь уже никаких настоящих юниксовых приемов не будет – просто дополним уже сделанное еще двумя отдельными узкоспециализированными программами. Начнем с поиска совершенно одинаковых файлов. Это замечательно делает программа fdupes (find duplicates – находить дубликаты). Работает она очень быстро, так как сравнивает сначала по размеру, затем (только при совпадении размеров) по контрольной сумме, и уже лишь при совпадении контрольных сумм сверяет содержимое побайтово. Конечно, при росте количества файлов время все равно растет в геометрической прогрессии, так что если у вас очень большая коллекция, придется немного подождать. У программы достаточно много опций, позволяющих гибко настроить как сам поиск, так и поведение при нахождении дубликатов.

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

  1. fdupes -rdN .
  2. fdupes -rL .
  3. fdupes -rd .

Опция r отвечает за рекурсивную обработку всех подкаталогов и файлов в указанном месте (а указали мы точку, т. е. текущий каталог). Опция d, упомянутая сама по себе, приводит к «интерактивной» работе, запрашивая человека о каждой группе дубликатов. L заменяет копии ссылками. А N указывает, что удалять нужно в каждой группе все файлы, кроме первого, безо всяких запросов.

С одинаковыми файлами закончили, а второй случай, казалось бы, посложнее: тут уже надо найти не в точности совпадающие файлы, а «похожие» картинки. Но и такой поиск тоже можно осуществить одной-единственной программой, именно для этого и предназначенной: findimagedupes (находить дубликаты изображений). Она анализирует состав картинок и находит похожие с виду. Эта задача гораздо сложнее предыдущей, работает программа ощутимо медленнее, чем fdupes; поэтому и стоило от полностью одинаковых файлов избавиться заранее. Она тоже довольно гибко управляется, хотя подход к управлению использует несколько другой – большей частью через дополнительные скрипты. Но с похожими картинками все равно рискованно что-либо делать в полностью автоматическом режиме. Даже разрешение – не показатель, так как качество изображения зависит не только от него, да еще и субъективный фактор оценки нельзя сбрасывать со счетов. Кроме того, могут попасться не только разные варианты одной и той же картинки (даже по-разному обработанной), но и несвязанные похожие изображения: например, соседние фотоснимки из одной серии, с разницей в несколько секунд или минут. Поэтому на этот раз мы только подготовим список таких похожих картинок для дальнейшей обработки вручную. Наиболее удобный способ такой обработки – в программе просмотра картинок, умеющей работать с коллекциями (а не только с каталогами) и позволяющей в интерактивном режиме как сделать что-то с самими файлами (например, удалить), так и просто вычеркнуть их из коллекции. Здесь тоже ничего дополнительно делать не придется: findimagedupes умеет генерировать файл «коллекции» для одной такой программы (по сути, это просто список полных путей к файлам, только специально оформленный). Эта программа недавно была переименована в geeqie, а раньше называлась gqview (собственно, в man findimagedupes она и упоминается под старым названием). Так что для формирования коллекции похожих картинок и последующей ее ручной обработки достаточно дать две команды:

findimagedupes -R -c список.gqv . && geeqie список.gqv

Опция R опять-таки означает рекурсивный поиск (как и r у fdupes), а c задает имя файла коллекции.

И последний штрих. После всех этих удалений файлов могли остаться пустые каталоги. Их тоже хорошо бы подчистить. Здесь опять ничего комбинировать не нужно, достаточно одной только команды find. У нее есть опции empty [пустой] и delete [удалить]. Объединяем их с поиском каталогов (тип d, от слова directory) – и готово:

find -type d -empty -delete

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

Персональные инструменты
купить
подписаться
Яндекс.Метрика