Дело было во FreeBSD. Настроил я sendmail, а заодно переадресацию почты одному пользователю. Все новые письма пересылаются, как положено, а вот старые, которые лежат в /var/mail/*, так там и лежат. И вот захотелось получить эти письма так же, как и новые: удалённо почтовым клиентом через IMAP.
Чувствуя пятой точкой, что должен быть до безобразия простой способ это сделать, я обратился на тематический форум, где помочь мне не смогли. Видимо действительно такого способа нет. Что ж, пришлось изобретать велосипед. Собственно, вот и он:
- #!/bin/sh
- my_sendmail="/usr/sbin/sendmail -v username@mail.server.xx"
- my_file="/var/mail/www"
- my_awk="/usr/bin/awk"
- my_cat="/bin/cat"
- my_sed="/usr/bin/sed -n"
- my_wc="/usr/bin/wc -l"
- while read str
- do
- i=`expr $str - 1`
- if [ ${i} -gt 0 ]; then
- ${my_sed} "${j},${i}p" ${my_file} | ${my_sendmail}
- fi
- j="${str}"
- done <<EOF
- `${my_cat} -n ${my_file} | ${my_awk} '/From / {print $1}'`
- EOF
- i=`${my_cat} -n ${my_file} | ${my_wc}`
- ${my_sed} "${j},${i}p" ${my_file} | ${my_sendmail}
А теперь по порядку.
В четвёртой строчке указана переменная, в которой хранится путь к файлу с письмами. В данном случае это почтовый ящик пользователя www. Именно эти письма мы и будем пересылать.
В третьей строчке устанавливается перменная, которая содержит в себе путь к исполняемому файлу sendmail с опциями. В качестве опций указан email, на который следует переслать все письма. Опция -v указана, чтобы не было скучно смотреть в пустую консоль. :) Впрочем, если вам не интересно работает sendmail или уже висит, то можете -v не указывать. Опция -t не указана, но если вам потребуется переслать письма туда же, куда они предназначались изначально, то следует указать -t (email в таком случае можно не указывать).
В восемнадцатой строке мы выводим содержимое файла с письмами с нумерацией строк. Затем передаём результат по конвейеру в awk, который оставляет только номера строк, в которых присутсвует «From » (без кавычек и с пробелом на конце). Такими строками начинаются все письма. Результат работы awk передаётся в цикл while (строка 10) для дальнейшей обработки.
С десятой строки начинается условие, которое выполняется для данных, взятых из строки 18. Код написан «задом–наперёд» не просто так. Таким хитрым образом мы заставляем цикл while работать в контексте основной программы. Исполняется while в другой дочерней оболочке и все данные, которые в нём будут сгенерированы, не будут доступны основной программе. Так как они нам всё–таки понадобятся, мы и используем heredoc–синтаксис, ну и придаём нашему скрипту немного задом–наперёдности. :) Итак, в цикл while передаётся номер строки.
Далее в строке 12 номер полученной строки уменьшается на еденицу и присваивается переменной i.
В строке 13 мы проверяем не равен–ли номер строки нулю. Это важно! Так–как на предыдущем шаге мы уменьшаем номер, у нас вполне может оказаться в обработке нулевая строка, что недопустимо.
На четырнадцатом шаге мы берём из файла с письмами диапазон строк, сооответствующий одному письму. Диапазон представлен номером конечной строки i, который мы только что определили, и номером начальной строки j, который мы определили на предыдущей итерации цикла while. Далее этот диапазон передётся по конвейеру сендмейлу, который их тут же отсылает в виде письма.
В шестнадцатой строке мы присваиваем перменной j номер начальной строки, который будет использован при следующей итерации цикла while.
В строке 21 мы высняем номер строк в файле с письмами. Это нужно, чтобы было возможно указать диапазон строк для письма находящегося в конце файла.
В 22–й строке мы делаем то же саме, что и в 14–й, но для последнего письма.
Логика работы.
После запуска программы, с помощью конструкции cat -n /var/mail/www, выводится содержимое файла с письмами с пронумерованными строками. Этот текст обрабатывается конструкцией awk '/From / {print $1}', в результате чего мы получаем номера строк с текстом "From " (без кавычек и с пробелом на конце). С такой строки начинается каждое письмо. Список номеров строк передаётся циклу while, условие работы которого задано конструкцией read str. Это означет, что список номеров строк будет читаться построчно по одному номеру за раз, который будет присвоен перменной str.
На первом шаге цикла while переменная str будет равна 1. Что означает, что письмо начинается с первой строки файла /var/mail/www. Это конечно хорошо, но мы не знаем где кончается письмо. Для этого нужно прогнать цикл while ещё раз и найти номер строки, с которой начинается следующее письмо и отнять от него еденицу, что мы и делаем в самом начале цикла while с помощью конструкции i=`expr $str - 1`. Так-как при первой итерации while конечная строка первого письма нам не известна, мы не можем задать диапазон строк, в пределах которых лежит первое письмо. Поэтому, с помощью условия if [ ${i} -gt 0 ], мы отсекаем попытку использовать данные первой итерации для разбора файла с письмами. Это возможно, так–как при первом запуске цикла while переменная i была равна еденице, а в самом начале цикла мы ещё уменьшили на еденицу. В результате условие if [ ${i} -gt 0 ] не выполнится. Зато выполнится следующая за ним конструкция j="${str}", которая присвоит переменной j номер первой строки, еденицу.
На втором шаге цикла while переменная str будет равна номеру, соотвествующему началу второго письма. Далее он будет понижен на еденицу конструкцией i=`expr $str - 1`, чтобы найти номер последней строки предыдущего письма. Теперь условие if [ ${i} -gt 0 ] выполнится, так–как переменная i, содержащая номер последней строки, будет больше нуля. Теперь в дело вступит конструкция sed "${j},${i}p" /var/mail/www, которая выведет диапазон строк между строкой номер j (началом письма) и строкой номер i (концом письма) и передаст его программе sendmail на отправку. Напомню, что значение переменной j мы определили на предыдущем шаге.
Те же действия, что и на втором шаге цикла while, повторятся для всех найденных писем, кроме последнего.
При достижении номера первой строки последнего письма, происходит последняя итерация цикла while, на котрой отправляется предпоследнее письмо из файла /var/mail/www. По концовке цикла у нас есть лишь номер первой строки последнего письма, который хранится в переменной j. Чтобы получить доступ к этой переменной вне цикла while и нужна была задом–наперёдная структура программы. :) Осталось получить номер последней строки последнего письма. Делается это с помощью конструкции i=`cat -n /var/mail/www | wc -l`. Далее конструкция sed "${j},${i}p" /var/mail/www выводит диапазон строк между строкой номер j (началом письма) и строкой номер i (концом письма) и передаёт его программе sendmail на отправку.
Вот и всё. Все письма отправлены.