В sed-скриптах можно (как и большинстве скриптовых языков) не только выполнять команды самой sed, но и использовать команды оболочки.
Для этого имеется команда e, которую я почти никогда не использую, а так-же модификатор e
для команды s.
Принцип действия предельно прост: выполняя команду s///e sed запускает оболочку, которая выполняет то, что будет находится в буфере после замены.
Но это только в том случае, если замена произошла.
Если замены не было, то ничего не выполнится.
Результат работы команды (то, что она выводит в stdout
) после выполнения команды окажется в буфере, для дальнейшего анализа.
Конечно этот результат вы можете вывести в выходной поток с помощью модификатора ep
, при этом модификатор pe
так-же допустим, однако работает он по другому.
pe
работает следующим образом: сначала буфер выводится в выходной поток, а затем он выполняется.
Все эти команды и модификаторы ЧРЕЗВЫЧАЙНО ОПАСНЫ! Злоумышленник может добавить в текст какой-нибудь вредоносный код, а sed легко передаст его на выполнение оболочке. (Пример атаки)
Особую опасность представляет парсинг текстов из Сети, и применение UTF-8. С первым понятно, а вот UTF опасно своими НЕСИМВОЛАМИ:
Предположим мы выполняем команду
s/.*/echo '&'/ep
Предполагая, что тут нет никакой опасности: скрипт выведет в выходной поток содержимое буфера. Но не тут-то было! Предположим, что в тексте есть НЕСИМВОЛ, тогда выражение /.*/ захватит только начало буфера до этого НЕСИМВОЛА, а оставшийся хвост уедет оболочке, и будет выполнятся...
Ну казалось-бы, что может быть проще? Выполняем
$ tar -cf arc.tar *
и всё добавляется! Это так... Вот только мне необходимо было отобрать файлы по особым признакам, что умеет разве-что find, а потом ещё и отсортировать, для максимального сжатия необходимо что-бы похожие файлы лежали рядом, что find ни с какими ключами нам не обеспечит. Что-ж, это сложнее, но именно для этого у нас есть замечательная команда xargs, вот рекомендуемый многими гуру вариант:
$ find . -type f | sort | xargs tar -cf arc.tar
Настоящие гуру не будут советовать всякую непроверенную ерунду, они сначала эту ерунду проверят, и лишь за тем посоветуют... И это работает! В тестовых примерах... Беда в том, что часто файлов довольно много (10,000 файлов - это на самом деле не столь-уж и много, бывает и больше), а размер командной строки жёстко ограничен. Хотя и гуру и убеждали меня в том, что у них всё работает, лично я убедился в обратном. Хорошо ещё, я вовремя заметил, что в архиве не хватает многих файлов, тут сложность в том, что при этом переполнении никаких сообщений не выдаётся, всё работает "нормально", но только с виду - если кильнуть "сохранённые" файлы, вам потом придётся кусать локти, или вешаться (в зависимости от ценности этих файлов).
-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.
#!/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://drbatty.ru). 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 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.