#!/bin/bash
#
# Скрипт для поиска и оптимизации картинок на сайте (под WordPress)
#
# Основные особенности программы:
# 1) Если картинка удаляется с сайта, Optimize находит и удаляет дочерние файлы не имеющие родителей
# 2) Для обработки картинок, обязательно имя файла должно заканчиваться на _mf.jpg (мульти форматный)
# 3) Если файл с именем pict_mf.jpg залить 2 раза, то Wordpress заменит имя "однофамильца" на pict_mf-1.jpg
# данный файл также обработается, но уже не как мультиформатный, а с одним разрешением, но в 2 форматах
# 4) Все созданные дочерние файлы сохраняются в папке modified, а _mf заменяется на разрешение
# изображения: 1200x750, 480x300 или 240x150
# 5) Ограничил количество запускаемых процессов, рекомендуется сделать 1 процесс, чтобы не грузить
# процессор или на сервере 1 ядро. В качестве примера в скрипте стоит ограничение на 2 процесса,
# чтобы показать возможность многопоточной обработки
# 6) Для начала обработки, процесс проверяет наличие файла (флага):
# /var/www/site.ru/www/wp-content/flag_optimize
# иначе процесс не запустится, а для удаления файлов, д.б. флаг:
# /var/www/site.ru/www/wp-content/flag_optimize_clear
#
# Событие 'move_to' происходит не только при перемещении файлов внутрь отслеживаемой папки, но и
# если перемещение будет внутри вложенных папок.
#
# Флаги (файлы) не сохраняю в папку /var/www/site.ru/www/wp-content/uploads/, так как за ней
# ведется наблюдение демоном, и создание/удаление флага - это еще одно событие, поэтому флаги
# сохраняю на уровень выше: /var/www/site.ru/www/wp-content/
#
# т.е. при размещении флага в папке слежения, при событии 'delete' (удалении файла или папки)
# получаю зацикливание процесса. Даже если исключить (настройками демона) определенные папки
# из наблюдения, то при удалении папки также происходит событие
#
# Версия скрипта
OPTIMIZE_VERSION="1.00"
# Сайты (домены) под WordPress, с которыми будет проводится работа
SITES=(
site.ru
site.com
site.net
)
# Wordpress создает промежуточные изображения, а также миниатюры. Массив исключений, чтобы их не обрабатывать
DOWNSIZE=(
120x120
200x200
200x125
)
EXCLUDE=""
for i in ${DOWNSIZE[@]};do
EXCLUDE+="\|${i}"
done
# На выходе получаем: \|200x200\|400x400
# Путь к папке рабочей директории
PATH_UPLOADS_PREFIX="/var/www"
PATH_UPLOADS_POSTFIX="www/wp-content"
# Проверяет запущен ли процесс более 2х раз, если да, то завершаем новый процесс
# Определяем имя файла данного скрипта, учитывается запуск как .sh так и симлинка
SCRIPT_NAME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
[ `pgrep -c $SCRIPT_NAME` -gt 2 ] && echo "Программа уже запущена" && exit 1
case $1 in
# Если параметры не заданы или -h, то показывает информацию по скрипту
""|-h)
echo -e "\e[96mВерсия $OPTIMIZE_VERSION\t\tСинтаксис:\e[39m optimize.sh [КЛЮЧ]\n"
echo -e "\e[96mСкрипт Optimize для поиска и оптимизации картинок на сайте под WordPress\e[39m"
echo -e "Скрипт находит файлы с именем *_mf.jpg, и создает дочерние jpg- и webp-файлы."
echo -e "Все созданные дочерние файлы сохраняются в папке modified, а _mf заменяется"
echo -e "на: 1200x750, 480x300 или 240x150, т.е. соответствует разрешению изображения"
echo -e "Находит и удаляет дочерние файлы, не имеющие родителя (при удалении картинки с сайта)\n"
echo -e "Процесс не запускатся, если нет флага на обработку или удаление:"
echo -e "\e[1;30m/var/www/site.ru/www/wp-content/flag_optimize\e[39m"
echo -e "\e[1;30m/var/www/site.ru/www/wp-content/flag_optimize_clear\e[39m\n"
echo -e "В своей работе Optimize использует планировщик и дополнительный bash-файл"
echo -e "Файл настройки ~/.watcher/jobs.yml, управление: watcher.py start|stop"
echo -e "Bash-файл запускаемый планировщиком: /usr/bin/optimize_init.sh\n"
echo -e "\e[96mКлючи:\e[39m"
echo -e "create - создает дочерние медиафайлы на основе родительских, удаляет осиротевшие файлы из папке /modified"
echo -e "delete - удаление директории modified (в конкретном сайте или all), не забываем отключить демон watcher.py иначе файлы будут созданы снова"
echo -e "\nEXAMPLE 1: create noflag - обработка без ключа"
echo -e "EXAMPLE 2: create noflag alltime - обработка без ключа и за все время, а не за 30 дней\n"
;;
# Удаляет с сайта папки modified
# Вторым параметром после delete указывается имя сайта или all (тогда удалит папки со всех сайтов из массива SITES)
delete)
if [[ -n $2 ]]; then
if [[ $2 = "all" ]]; then
# Удаление всех дочерних файлов, со всех сайтов массива SITES
for i in ${SITES[@]};do
echo "Удаление каталогов modified с сайта $i, не забывайте отключать демона: /usr/bin/wather.py stop, иначе файлы будет созданы снова."
`find "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/uploads" -type d -name modified -exec rm -rf {} \; -prune`
done
else
# Проверяем наличие указанного сайта в массиве SITES, если сайт есть, то modified удаляется
for i in ${SITES[@]};do
if [[ $2 = $i ]]; then
echo "Удаление каталогов modified с сайта $i, не забывайте отключать демона: /usr/bin/wather.py stop, иначе файлы будет созданы снова."
`find "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/uploads" -type d -name modified -exec rm -rf {} \; -prune`
# Создадим переменную, что сайт найден, и прервем поиск по массиву
SITE_FOUND=1
break
fi
done
# Если сайт не найден, выведем сообщение
if [ -z $SITE_FOUND ]; then
echo "Данный сайт отсутствует в массиве SITES, пожалуйста, отредактируется массив SITES, если нужно обработать данный сайт."
fi
unset SITE_FOUND
fi
else
echo -e "После delete, пожалуйста, укажите название сайта или параметр all (удалить со всех сайтов).\n"
fi
;;
# 1. Ищет флаг (файл flag_optimize) для начала работы над файлами сайта
# 2. Если флаг найден, ищет файлы заканчивающиеся на _mf
# 3. На основе найденных файлов - создает и оптимизирует дочерние
# ДОПОЛНИТЕЛЬНЫЕ КЛЮЧИ
# Если после "create" задать ключ "noflag", то файлы обработаются без проверки
# Если задать: "alltime" - проверка будет за все время, а не за 30 дней, как сейчас
create)
# Для noflag
if [ "$2" == "noflag" ] || [ "$3" == "noflag" ]; then
TEST_NOFLAG="noflag"
fi
# Для alltime, ключ не задан, значит файлы проверяем только за 30 дней, иначе все картинки
PERIOD_OF_TIME=""
if [ "$2" != "alltime" ] && [ "$3" != "alltime" ]; then
PERIOD_OF_TIME="-mtime -30"
echo "Ограничено 30 днями!"
fi
# Цикл выполняется пока переменная не задана
while [ -z $FLAG ]; do
# Задаем переменную, чтобы цикл не повторялся
FLAG=true
# Перебираем сайты из массива SITES и обрабатываем картинки
for i in ${SITES[@]};do
# Проверяем, есть ли файл flag_optimize на сайте
if [ -f "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize" ] || [ "$TEST_NOFLAG" == "noflag" ]; then
# Только для уведомления, что флаг найден или задана работа без флага
FLAG_FOUND=1
# Удаляем файл flag_optimize
rm -rf "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize"
# 1. Обрабатываем мультиформатные картинки _mf
for FILE in `find "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/uploads" -type f $PERIOD_OF_TIME -regextype posix-egrep -regex ".*_mf\.jpg"`; do
# Имя файла, из строки /var/www/site.ru/www/wp-content/uploads/2019/01/pict_23_mf.jpg, получаем pict_23_mf.jpg
FILE_FULLNAME=${FILE##*/}
# Имя файла до последнего "_", т.е. получаем: pict_23
FILE_NAME=${FILE_FULLNAME%_*}
# директория modified, в которой создаются измененные файлы
DIR_PATH="${FILE%/*}/modified"
# Проверяем существует ли папка modified, если нет, то создаем и задаем права
if ! [ -d $DIR_PATH ]; then
mkdir -p $DIR_PATH
chown -R nginx:nginx $DIR_PATH
fi
# Проверяем есть ли файл basename (без _mf) с расширением jpg, webp,.. если нет, создаем
WEBP240=$DIR_PATH"/"$FILE_NAME"_240x150.webp"
WEBP480=$DIR_PATH"/"$FILE_NAME"_480x300.webp"
WEBP1200=$DIR_PATH"/"$FILE_NAME"_1200x750.webp"
JPEG240=$DIR_PATH"/"$FILE_NAME"_240x150.jpg"
JPEG480=$DIR_PATH"/"$FILE_NAME"_480x300.jpg"
JPEG1200=$DIR_PATH"/"$FILE_NAME"_1200x750.jpg"
unset NOFIND240
unset NOFIND480
unset NOFIND1200
# Если файла 240x150 нет, то создаем его, и создаем переменную указывающаю, что файла не было
if ! [ -f $JPEG240 ]; then
`convert -resize 240x150! $FILE $JPEG240`
NOFIND240=1
fi
# Если файла 480x300 нет, то создаем его
if ! [ -f $JPEG480 ]; then
`convert -resize 480x300! $FILE $JPEG480`
NOFIND480=1
fi
# Если файла 1200x750 нет, то копируем его из родительского
if ! [ -f $JPEG1200 ]; then
`convert -resize 1200x750! $FILE $JPEG1200`
#cp $FILE $JPEG1200
NOFIND1200=1
fi
# Если файла 240x150.webp нет, то создаем его
if ! [ -f $WEBP240 ]; then
`/usr/bin/cwebp -q 80 -m 6 $JPEG240 -o $WEBP240`
fi
# Если файла 480x300.webp нет, то создаем его
if ! [ -f $WEBP480 ]; then
`/usr/bin/cwebp -q 80 -m 6 $JPEG480 -o $WEBP480`
fi
# Если файла 1200x750.webp нет, то создаем его
if ! [ -f $WEBP1200 ]; then
`/usr/bin/cwebp -q 80 -m 6 $JPEG1200 -o $WEBP1200`
fi
# Для оптимизации медиафалов, используем временный файл
TMP_FILE_240="$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/temp_$RANDOM.jpg"
TMP_FILE_480="$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/temp_$RANDOM.jpg"
TMP_FILE_1200="$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/temp_$RANDOM.jpg"
if [ $NOFIND240 ]; then
mv $JPEG240 $TMP_FILE_240
`/opt/mozjpeg/bin/cjpeg -optimize -quality 75 $TMP_FILE_240 > $JPEG240`
rm $TMP_FILE_240
unset NOFIND240
fi
if [ $NOFIND480 ]; then
mv $JPEG480 $TMP_FILE_480
`/opt/mozjpeg/bin/cjpeg -optimize -quality 75 $TMP_FILE_480 > $JPEG480`
rm $TMP_FILE_480
unset NOFIND480
fi
if [ $NOFIND1200 ]; then
mv $JPEG1200 $TMP_FILE_1200
`/opt/mozjpeg/bin/cjpeg -optimize -quality 75 $TMP_FILE_1200 > $JPEG1200`
rm $TMP_FILE_1200
unset NOFIND1200
fi
done
# 2. Обрабатываем картинки jpg, исключая: миниатюры (EXCLUDE) и _mf (мультиформатные)
### find /var/www/site.ru/www/wp-content/uploads" -type f ! -regex '.*\(modified\|_mf.jpg${EXCLUDE}\).*' -name "*.jpg" -o -name "*.png"
# *.png оставил для примера, в скрипте имем только jpg-формат
for FILE in `find "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/uploads" -type f $PERIOD_OF_TIME ! -regex ".*\(modified\|_mf\.${EXCLUDE}\).*" -name "*.jpg"`; do
# Имя файла, из строки /var/www/site.ru/www/wp-content/uploads/2019/01/pict1.jpg, получаем pict1.jpg
FILE_FULLNAME=${FILE##*/}
# Имя файла до последнего символа ".", т.е. получаем: pict1
FILE_NAME=${FILE_FULLNAME%.*}
# директория modified, в которой создаются измененные файлы
DIR_PATH="${FILE%/*}/modified"
# Проверяем существует ли папка modified, если нет, то создаем и задаем права
if ! [ -d $DIR_PATH ]; then
mkdir -p $DIR_PATH
chown -R nginx:nginx $DIR_PATH
fi
# Проверяем есть ли оптимизированные файлы basename с расширением jpg и webp, если нет - создаем
WEBP=$DIR_PATH"/"$FILE_NAME".webp"
JPEG=$DIR_PATH"/"$FILE_NAME".jpg"
unset NOFIND
# Если оптимизированного jpg файла нет - конвертируем его из родительского
# Конвертируем, а не копируем, потому что приводим к формату jpg. Т.е. родительский мог быть png или jpg - не важно!
if ! [ -f $JPEG ]; then
`convert $FILE $JPEG`
NOFIND=1
fi
# Если файла .webp нет - создаем его
if ! [ -f $WEBP ]; then
`/usr/bin/cwebp -q 80 -m 6 $FILE -o $WEBP`
fi
# Для оптимизации медиафалов, используем временный файл
TMP_FILE="$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/temp_$RANDOM.jpg"
if [ $NOFIND ]; then
mv $JPEG $TMP_FILE
`/opt/mozjpeg/bin/cjpeg -optimize -quality 75 $TMP_FILE > $JPEG`
rm $TMP_FILE
unset NOFIND
fi
done
fi
done
# Удаление "осиротевшие" файлы в директории /modified
for i in ${SITES[@]};do
# Ищем флаг на проведение очистки
if [ -f "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize_clear" ]; then
# Ставим флаг на еще один проход, если во время работы скрипта снова появилось задание
FLAG=1
# Удаляем флаги
rm -rf "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize"
rm -rf "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize_clear"
find "/var/www/$i/www/wp-content/uploads" -type f -regextype posix-egrep -regex ".*/modified/[^/]+\.(jpg|webp)" | while read -r path;do
# Имя файла, из "/var/www/test/filename.jpg" получаем "filename.jpg"
fullname="${path##*/}"
# Получаем имя файла без _240x150.jpg, т.е. из filename-3_480x300.jpg получим filename-3
# Ищем _ за которой будет маска из цифр XXXxXXX
name=`echo $fullname | grep -oP '^.+(?=_(?=\d{3,4}x\d{3}))'`
# Спрашивал на тостере: https://qna.habr.com/q/596749
# grep -oP '^.+(?=-(?=\d))|^.+(?=_(?=\d))' - выводит то что идет до - и затем цифры, или _ и затем цифры
# -o - совпадение с шаблоном
# -P подключает регурные выражение Perl
# ^ - начало строки
# . - любой символ
# + - повторяется 1 и более раз
# (?=\t) - слово за которым идет табуляция, в нашем случае - или _
# \d - цифра
# | - альтернатива ИЛИ
# Проверяем есть ли родитель:
# /var/www/site.ru/www/wp-content/uploads/2019/06/pict1_mf.jpg <= создает 3 копии разных размеров и 2 форматах
# /var/www/site.ru/www/wp-content/uploads/2019/06/pict1.jpg <= создает 2 формата одного размера
# если нет и того и другого, то удаляем
[[ ! -f "${path%modified/*}${name}_mf.jpg" && ! -f "${path%modified/*}${fullname%%.*}.jpg" ]] && { rm -r "$path"; echo -e "$(date +%d.%m.%Y\ %H:%M) File deleted (no parent): $path" >> "/var/www/deleted_files.log"; }
done
# Удаляем пустые папки в /uploads
find "/var/www/$i/www/wp-content/uploads" -mindepth 1 -type d -empty -delete
fi
done
# Когда обработали картинки на всех сайтах, еще раз пробежимся по всем сайтам и проверим нет ли флага, если есть то прогоняем цикл еще раз
for i in ${SITES[@]};do
if [ -f "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize" ]; then
unset FLAG
# Прерывам перебор, так как флаг уже найден, идем работать
break
fi
done
# Ищем файлы с 0 байт, и даляем их, затем отправляет снова на создание
for i in ${SITES[@]};do
find "/var/www/$i/www/wp-content/uploads" -type f -empty -regextype posix-egrep -regex ".*/modified/[^/]+\.(jpg|webp)" | while read -r path;do
echo -e "$(date +%d.%m.%Y\ %H:%M) File deleted (0 bytes): $path" >> "/var/www/deleted_files.log"
touch "$PATH_UPLOADS_PREFIX/$i/$PATH_UPLOADS_POSTFIX/flag_optimize"
rm -r "$path"
unset FLAG
done
done
done
# Если флаг обнаружен, то удаляем переменную, иначе сообщаем, если флаг не был обнаружен
if [ $FLAG_FOUND ]; then
unset FLAG_FOUND
else
echo "Флаг на обработку не обнаружен!"
fi
;;
esac
exit 0