Бэкапы windows-шар через smbfs

Важное преимущество Linux, BSD и других никсов — наличие очень мощных базовых утилиты, с помощью которых можно автоматизировать многие необходимые задачи. В частности, ssh, rsync позволяют очень удобно и надёжно создавать, передавать (в том числе и за многие километры по интернету) и всячески управлять бэкапами, а с помощью архиваторов их можно и сжимать. Windows, к сожалению, не обладает таким функционалом "из коробки", да и сторонние решения не "из коробки" не всегда подходят по ряду параметров. Поэтому, логично организовать сервер резервных копий даже в Win-сети на основе Linux и стягивать на него в плановое время данные для бэкапа. Посмотрим, как это можно не слишком умудрённо сделать.

Будем изначально подходить наиболее экономным способом: с наименьшей конфигурацией исходных машин. Для этого монтируем нужную шару, делаем копию, размонтируем. Монтируем с помощью mkfs.cifs из пакета cifs-utils. Если на машину можно зайти анонимно, так и заходим, с помощью соответствующей опции

mount -t cifs //192.168.10.99/Data $data_dir -o user=guest,guest

Можно, конечно, добавить шару в fstab и держать постоянно смонтированной, но это скорее излишне, поэтому будем её монтировать только на время бэкапа. Также, на случай возможных ошибок, выбираем директорию каждый раз новую в /tmp, получить временную директорию можно

mktemp -d

Копию будем делать дифференциальную на основе жёстких ссылок. rsync позволяет делать делать hard-linked копии сам, на основе предыдущей копии, или их можно делать отдельно, а затем заменять новые файлы. Воспользуемся вторым способом: в случае обрыва бэкапа мы получим не обновлённый, но полный бэкап, который обновится в следующий раз. В первом случае его придётся загружать полностью, что не всегда разумно если канал тонкий.

С учётом всего вышеперечисленного пишем скрипт

#!/bin/sh

softmkdir() {
	if [ ! -d "$1" ] ; then
		mkdir "$1"
		chmod 0750 "$1"
	fi
}

# SETTINGS
host=`hostname -s`
backup_dir=/backup/$host/data_rsync
len=90

softmkdir "$backup_dir"

tmpname=new
tmp_dir="$backup_dir/$tmpname"
zero_dir="$backup_dir/d`printf "%02d" 0`"
prev_dir="$backup_dir/d`printf "%02d" 1`"
last_dir="$backup_dir/d`printf "%02d" $len`"

date=`date`
echo "================================="
echo "Starting backup: $date."

if [ -d "$zero_dir" ]; then
	echo "Rotating directories up to length of $len."

	# removing last directory
	if [ -d "$last_dir" ]; then
		rm -rf "$last_dir"
	fi

	# rotating
	for i in $(seq  $len -1 0); do
		src="$backup_dir/d`printf "%02d" $i`"
		if [ -d "$src" ]; then
			mv "$src" "$backup_dir/d`printf "%02d" $(($i+1))`"
		fi
	done
fi

if [ ! -d "$tmp_dir" ] ; then
	if [ -d "$prev_dir" ] ; then
		echo "Found previous backup. Making hard-linked copy."
		cp -al "$prev_dir" "$tmp_dir"
		echo "Hard-linked copy done."
		test -d "$tmp_dir" && touch "$tmp_dir"
	else
		echo "Previous backup not found. Creating init backup."
		softmkdir "$tmp_dir"
	fi
else
	echo "Found temporary directory (not finished last backup), resuming it."
fi

data_dir=`mktemp -d`
mount -t cifs //192.168.10.24/Data $data_dir -o user=guest,guest

echo "Starting rsync..."
rsync -azK --delete --ignore-errors --stats --numeric-ids $1 $data_dir/ "$tmp_dir/Data"

test -d "$tmp_dir" && touch "$tmp_dir"
echo "Rsync done."

# moving temporary to latest
echo "Moving temporary dir name to normal backup name."
mv "$tmp_dir" "$zero_dir"

umount $data_dir
rm -d $data_dir

echo "Backup finished: $date."
echo "================================="
echo ""

Проверяем, что всё работает корректно и добавляем в cron.

Смена кодировки файлов в utf8

В поддержке legacy-кода и обновлении скритов нередко возникает задача их перекодирования в utf-8. Это хорошо выполняется стандарной утилитой iconv, вот её использование для одного файла:

iconv -f cp1251 -t utf8 orig-in-cp1251.php -o out-in-utf8.php

При этом не удастся указать одинаковое входное и выходное названия: iconv начнёт писать до получения входа и получится файл нулевой длины. В этом случае станартный вывод полезно "замочить в губке" с помощью sponge. Сделаем однострочный скрипт, который делает всё сразу, он понадобится далее (назовём его to-utf8.sh):

#!/bin/sh
/usr/bin/iconv -f cp1251 -t utf8 "$1" | sponge "$1"

В качестве единственного аргумента он берет имя файла и рекодит его полностью.

Обычно требуется сконвертить все файлы определённого расширения в целой директории. Подключаем ещё find:

find /here-is-my-files/ -type f -iname "*.js" -exec to-utf8.sh "{}" \;

На часть файлов iconv может ругнуться в STDERR и эти файлы целесообразно проверить/перекодировать вручную.

Ещё более сложный (и достаточно частый) случай, когда есть файлы в разных кодировках и их необходимо перевести в одну. iconv в этом случае может сконвертировать ещё раз и испортить файл. То есть, нужно распознать исходную кодировку и рекодить только "не те" файлы.

Тогда помогает самый лучший вариант: утилиты enca/enconv. Из них первая только распознаёт кодировку, а вторая ещё и перекодирует.Интерфейс enconv до безобразия простой: в случае одного параметра она перекодирует файл из исходной (ЛЮБОЙ распознанной!) кодировки в ту, что указана в locale. Кодировку можно указать явно, тогда вся задача решается одной строкой

find /here-is-my-files/ -type f -iname "*.js" -exec enca "{}" \;

Для улучшения точности язык также можно "подсказать", с помощью параметра (-L ru).

Последний, enca-способ, похоже самый оптимальный для большинства задач.

grep без комментариев и пустых строк

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

grep -v '^$\|^\s*\#' some-config.conf

Или более коротко, не учитывая комментариев, начинающихся не с начала строки:

grep -v '^$\|^#' some-config.conf

Удаление очень больших директорий в Linux

Современные файловые системы вроде ext3/ext4, xfs позволяют создавать в директориях очень большое количество файлов. В ряде случаев (обычно по ошибке или недосмотру) это реализуется на практике и тогда некоторые операции с такой директорией становятся очень медленными. Это касается прежде всего операций, требующих сортировку списка содержимого директории. Такая сортировка, например, происходит при вызове листинга ls, который по умолчанию сортируется, что естественным образом приводит к очень медленному исполнению команды. Также, большая директория очень медленно удаляется rm, поскольку последний формирует листинг.

Чтобы ускорить (а иногда и просто осуществить) удаление большой директории нужно отключить сортировку. Команда rm при удалении берет полностью список файлов, поэтому удалять очень большую директорию "в лоб" с помощью "rm -rf" очень неоптимальный вариант. Большую директорию удобнее удалять частями: листингом без сортировки некоторого количества файлов и их удалением. Сортировка в ls отключается параметром -U, что приводит к такой команде частичного удаления

ls -UA -1 . | head -n 1000 | xargs rm -f

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

while [ true ]; do ls -UA -1 . | head -n 100 | xargs rm -f 2>/dev/null; sleep 1; done

Альтернативный способ — изменить ionice процесса удаления.

Полезные твики bash

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

Алиасы

Листинги

alias ll='ls -l'
alias l='ls -lA'

Часто эти опции уже включены в .bashrc, но закомментированы. В Debian, например, в конфиге это реализовано так

# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'

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

Часто используемая команда по поиску процесса с определенным именем:

alias pg='ps aux | grep '

Просмотр наибольших файлов/директорий в гигабайтах и мегабайтах соответственно:

alias dug='du -h | grep ^[0-9.]*G | sort -rn'
alias dum='du -h | grep ^[0-9.]*M | sort -rn'

Полезности для скриптов

Иногда бывает полезным поймать сигналы, посылаемые ОС скрипту. Например, при нажатии Ctrl-C.

trap control_c SIGINT
trap control_c SIGTERM

Часто используемые команды

Часто бывает необходимо найти файл, в котором имеется определённая подстрока. Сделать это можно с помощью стандарной утилиты grep командой:

grep -rl

Горячие клавиши bash

  • Ctrl+w — удаляет слово перед курсором в строке,
  • Ctrl+u — удаляет все символы до начала строки,
  • Ctrl+k — удаляет все символы до конца строки,
  • Ctrl+y — вставляет удалённые вышеуказанными сочетаниями символы,
  • Ctrl+r — производит обратный поиск по истории команд,
  • Ctrl+l — очищает экран,
  • Ctrl+z — останавливает исполнение текущей команды (продолжить можно с помощью bg или fg),
  • Ctrl+d — выход из сессии.

История команд bash

В администрировании Linux (да и UNIX тоже) очень удобно пользоваться командами из истории. Содержание истории просматривается с помощью вызова history. Команду из истории можно выполнить по её номеру в списке например так:

history | less
!45

При этом последняя команда вызывается более просто, без указания номера выполнением !!.

Обратный поиск по истории команд вызывается с помощью Ctrl+R, например:

(reverse-i-search)`smart': smartctl -x /dev/sdd | less

Взять аргументы из одной из предыдущих команд можно сочетанием Alt+..

По умолчанию длина истории небольшая и если нет повышенных требований к безопасности её можно несколько увеличить добавлением в .bashrc строки:

export HISTFILESIZE=5000

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

history -c

Избежать добавления команды в историю можно начав её с пробела. Тогда исполненная инструкция в историю не попадает.