Использование внешних утилит.

В sed-скриптах можно (как и большинстве скриптовых языков) не только выполнять команды самой sed, но и использовать команды оболочки. Для этого имеется команда e, которую я почти никогда не использую, а так-же модификатор e для команды s. Принцип действия предельно прост: выполняя команду s///e sed запускает оболочку, которая выполняет то, что будет находится в буфере после замены. Но это только в том случае, если замена произошла. Если замены не было, то ничего не выполнится. Результат работы команды (то, что она выводит в stdout) после выполнения команды окажется в буфере, для дальнейшего анализа. Конечно этот результат вы можете вывести в выходной поток с помощью модификатора ep, при этом модификатор pe так-же допустим, однако работает он по другому.

Замечание

Модификатор pe работает следующим образом: сначала буфер выводится в выходной поток, а затем он выполняется.

Внимание

Все эти команды и модификаторы ЧРЕЗВЫЧАЙНО ОПАСНЫ! Злоумышленник может добавить в текст какой-нибудь вредоносный код, а sed легко передаст его на выполнение оболочке. (Пример атаки)

Замечание

Конечно это относится ко всем скриптовым языкам, как-то perl, php, да и сама оболочка может выполнять такие команды (например eval).

Особую опасность представляет парсинг текстов из Сети, и применение UTF-8. С первым понятно, а вот UTF опасно своими НЕСИМВОЛАМИ:

Предположим мы выполняем команду

s/.*/echo '&'/ep

Предполагая, что тут нет никакой опасности: скрипт выведет в выходной поток содержимое буфера. Но не тут-то было! Предположим, что в тексте есть НЕСИМВОЛ, тогда выражение /.*/ захватит только начало буфера до этого НЕСИМВОЛА, а оставшийся хвост уедет оболочке, и будет выполнятся...

Добавление в архив

Ну казалось-бы, что может быть проще? Выполняем

$ tar -cf arc.tar *

и всё добавляется! Это так... Вот только мне необходимо было отобрать файлы по особым признакам, что умеет разве-что find, а потом ещё и отсортировать, для максимального сжатия необходимо что-бы похожие файлы лежали рядом, что find ни с какими ключами нам не обеспечит. Что-ж, это сложнее, но именно для этого у нас есть замечательная команда xargs, вот рекомендуемый многими гуру вариант:

$ find . -type f | sort | xargs tar -cf arc.tar

Настоящие гуру не будут советовать всякую непроверенную ерунду, они сначала эту ерунду проверят, и лишь за тем посоветуют... И это работает! В тестовых примерах... Беда в том, что часто файлов довольно много (10,000 файлов - это на самом деле не столь-уж и много, бывает и больше), а размер командной строки жёстко ограничен. Хотя и гуру и убеждали меня в том, что у них всё работает, лично я убедился в обратном. Хорошо ещё, я вовремя заметил, что в архиве не хватает многих файлов, тут сложность в том, что при этом переполнении никаких сообщений не выдаётся, всё работает "нормально", но только с виду - если кильнуть "сохранённые" файлы, вам потом придётся кусать локти, или вешаться (в зависимости от ценности этих файлов).

Замечание

Справедливости ради надо сказать, что xargs действительно в большинстве случаев не ошибается - она скармливает команде параметры максимально возможными дозами. Особенно предпочтительно использование её с именами файлов которые оканчиваются нулевым байтом (см. man xargs на предмет опции -0).

Что-же делать? Внимательно изучив `man tar' я обнаружил ключ -r, который добавляет файлы в существующий архив. Отправив вывод find|sort в файл, я прочитал shell-скриптом этот файл построчно, и добавил все файлы в архив. Вот это действительно работает. ...Но очень медленно. Некоторое время я с этим мирился, но потом мне это надоело, и я написал небольшой sed-скрипт: Сначала я создаю архив

1{
		s/.*/tar -cf arc.tar "\0"/ep
		b
}

Но это только для первого файла, а вот дальше я действую по иному: я добавляю сразу по 32 файла:

# если строка делится на 32
2~32	b add
# ...или если строка последняя, я добавляю сохранённые строки в архив
$		b add
# иначе я сохраняю имя файла
H
b

# сохранение
H
g
s/\n/" "/
s/.*/tar -rf arc.tar "\0"/ep
# очистка области удержания
s/.*//
x

Внимание

Если вы решите применить данный скрипт для бекапа - будьте осторожны: злоумышленник может повысить свои привилегии до супер-пользователя (см. здесь), создав у себя файл с кривым именем. Однако обычно это невозможно - в данном случае юникод обычно отключён (конечно это НЕОБХОДИМО проверить).

Этот скрипт НЕ работает ;) Впрочем, ошибки в нём можно исправить, я думаю вы с этим сами справитесь, проблема в другом: архив не всегда должен быть назван arc.tar, и как передать имя архива в sed-скрипт? Если, как в примере подсчёта слов, sed-скрипт завёрнут в shell-script, то это не составляет особого труда, например можно так:

#!/bin/sh
sed -r 's/.*/tar -rf '$ARC_NAME' "\0"/ep' file_list.lst

Однако у меня был самостоятельный sed-скрипт, и передавать так параметры в него я не мог. Потому я записал имя архива первой строчкой в файл-список, тогда стало проще: в начале я создаю архив как-то так:

1{
	h
	N
	s/(.*)\n(.*)/tar -cf "\1" "\2"/ep
	b
}

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

H
2~32 b add
$! b
:add
s/\n/" "/
s/.*/tar -rf "\0"/ep

Вот только очистка буфера немного поменялась - нам не надо стирать первую строку - имя архива, потому:

x
s/\n.*//
x

Вот это уже почти работает... Почему "почти"? Читаем далее...

Работа над ошибками.

А что будет, если при добавлении кончится место на диске? А ничего хорошего - скрипт будет работать как ни в чём на бывало, и завершится с кодом 0, типа "всё хорошо". Необходимо предусмотреть проверку на код ошибки возвращаемой tar'ом. Я это сделал так:

s/.*/tar -rf "\0" || echo "ERROR"/ep
/ERROR/ q 77

Вот теперь всё в порядке: если архиватор не сможет добавить файлы, он вернёт ненулевой код возврата, что приведёт к выполнению команды echo, и скрипт завершится с кодом 77. (ну я надеюсь, у вас нет файлов с именем ERROR? У меня - нет.)

Пример 4.3.

add32.sed
#!/bin/sed -rnf

# Этот скрипт добавляет файлы в архив по 32 штуки
# в первой строке содержится имя архива, а дальше - список файлов

1 {
	h
	N
	s/(.*)\n(.*)/tar -cpvf "\1" "\2"/ep
	b
}

2~32 b add
$ b add

H
b

:add
H
g
s/\n/" "/g
s/.*/tar -rpvf "&" || echo "ERROR"/ep
/ERROR/ q 77
x
s/\n.*//
x
b
					


Использование сортировки для сжатия каталогов

Пример 4.4.

#!/bin/sh

if [ $# != 2 ]; then
	echo "Использование: `basename`   ИМЯ_АРХИВА   КАТАЛОГ_ДЛЯ_СЖАТИЯ
Применяется утилита add32.sed
Программа тестировалась при использовании команды find . -type f
для составления списка файлов. Наверное можно и по другому, но я не пробовал
некоторые файлы могут иметь копии бекапов созданные командой cp --backup.

автор drBatt (http://emulek.tk).
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,
to the extent permitted by law."
	exit 67
fi

export LC_ALL=C
export LANG=C

TMP=`tempfile`
err=$?
if [ $err = 0 ]; then
	TMP2=`tempfile`
	err=$?
fi
if [ $err = 0 ]; then
	find "$2" -type f > $TMP
	err=$?
	if [ $err = 0 ]; then
		sed -ri '
			/\.~[0-9]+~$/! s/$/.~0~/
			s%^(.+)/([^/]+)$%\2/\1%
			t lb0
			:lb0
			s%^([^/]*)\.([^/.]*)\.(~[0-9]+~)/%\2.\1.\3/%
			t
			# у этого файла нет расширения
			s%^%/%
			' $TMP
		err=$?
		if [ $err = 0 ]; then
			cat $TMP | sort -f >$TMP2
			err=$?
			if [ $err = 0 ]; then
				sed     -ri '
					s%^/([^/]+)/(.+)$%\2/\1%
					t lb1
					s%^([^./]*)\.([^/]*)\.(~[0-9]+~)/(.+)$%\4/\2.\1.\3%
					:lb1
					s/\.~0~$//
					' $TMP2
				err=$?
				if [ $err = 0 ]; then
					echo "$1" > $TMP
					cat $TMP2 >> $TMP
					add32.sed $TMP
					err=$?
					if [ $err = 0 ]; then
						bzip2 -vv "$1"
						err=$?
					fi
				fi
			fi
		fi
	fi
fi

rm $TMP $TMP2
exit $err
				

Этот скрипт сортирует файлы перед добавлением их в архив. Конечно сортировка производится утилитой sort. Перед сортировкой имена файлов изменяются: сначала записывается расширение, а потом имя файла. Если у файла есть стандартный бекап-суффикс (который прибавляется командой cp --backup), то этот суффикс остаётся в конце имени.

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

Затем полученный tar-архив сжимается утилитой bzip2. За счёт такой сортировки файлов, во многих частных случаях, достигается намного большая степень сжатия (например, когда у нас много почти одинаковых файлов).

Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).

Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.