Содержание
Здесь предоставлены разные примеры из info sed.
Пример 7.1.
#!/usr/bin/sed -f # Загрузка 80и пробелов в буфер2 1 { x s/^$/ / s/^.*$/&&&&&&&&/ x } # удаление табуляции, начальных и конечных пробелов y/tab/ / s/^ *// s/ *$// # добавление 80 пробелов в конец строки G # извлечение первых 81го символа (80 + a '\n') s/^\(.\{81\}\).*$/\1/ # Теперь в буфере у нас m символов, затем \n, а потом 80-m пробелов # и мы ищем совпадение пробелов с пробелами (после \n) # т.к. * у нас жадная, то она захватывает максимальное число пробелов, # т.е. ровно половину(если 80-m чётное, иначе захватывается (80-m-1)/2 # пробелов). # вот эти (80-m)/2 пробелов мы переносим в начало строки. s/^\(.*\)\n\(.*\)\2/\2\1/
Пример 7.2.
#!/usr/bin/sed -rf /[^0-9]/ d # Заменяем последние девятки на '_' (подойдёт любой другой символ, # кроме цифр). # {drBatty} пришлось делать циклом - если использовать s///g, # то поменяется все девятки, а надо только последние, их можно найти # s/9+$/но, на что менять?/ :d s/9(_*)$/_\1/ td # тут увеличивается только последняя цифра. (не считая оконечных девяток) # если строка пустая, или в ней только ____ тогда она меняется на "1____" # остальные цифры просто увеличиваются на 1, и хвост из "_" сохраняется. # # tn команды не обязательны, но мы думаем, что так будет быстрее. # {drBatty} щаз! В первой строке обязательно tn надо - а то получится # 9 + 1 = 20 :) s/^(_*)$/1\1/; tn s/8(_*)$/9\1/; tn s/7(_*)$/8\1/; tn s/6(_*)$/7\1/; tn s/5(_*)$/6\1/; tn s/4(_*)$/5\1/; tn s/3(_*)$/4\1/; tn s/2(_*)$/3\1/; tn s/1(_*)$/2\1/; tn s/0(_*)$/1\1/; tn # 99999 -> _____ -> 00000 :n y/_/0/
Это чрезвычайно важный пример для русского программера; кроме того, что тут показано, как обрабатывать вывод одной команды передачей его другой, вы так-же поймёте как можно переименовывать файлы
Далее приводится оригинальный скрипт:
Пример 7.3.
#! /bin/sh # rename files to lower/upper case... # # usage: # move-to-lower * # move-to-upper * # or # move-to-lower -R . # move-to-upper -R . # help() { cat << eof Usage: $0 [-n] [-r] [-h] files... -n do nothing, only see what would be done -R recursive (use find) -h this message files files to remap to lower case Examples: $0 -n * (see if everything is ok, then...) $0 * $0 -R . eof } apply_cmd='sh' finder='echo "$|" tr " " "\n"' files_only= while : do case "$1" in -n) apply_cmd='cat' ;; -R) finder='find "$-"type f';; -h) help ; exit 1 ;; *) break ;; esac shift done if [ -z "$1" ]; then echo Usage: $0 [-h] [-n] [-r] files... exit 1 fi LOWER='abcdefghijklmnopqrstuvwxyz' UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ' case `basename $0` in *upper*) TO=$UPPER; FROM=$LOWER ;; *) FROM=$UPPER; TO=$LOWER ;; esac eval $finder | sed -n ' # удаляем все слеши в конце имени s/\/*$// # добавляем ./ если нет пути, а есть только имя. /\//! s/^/.\// # сохраняем путь + имя h # удаляем путь s/.*\/// # преобразование имени # тут скрипт прерывается, между кавычками не скрипт, # а шелл, там можно переменные писать. y/'$FROM'/'$TO'/ # после команды x буфер будет содержать путь + имя, # а буфер2 будет содержать новое имя. x # добавим новое имя к пути + имя. G # проверяем: если новое имя такое-же как старое - ничего не делаем # (скрипт завершается) /^.*\/\(.*\)\n\1/b # теперь, трансформируем # путь/старое_имя\nновое_имя в # mv путь/старое_имя путь/новое_имя # и распечатываем s/^\(.*\/\)\(.*\)\n\(.*\)$/mv \1\2 \1\3/p ' | $apply_cmd # ппц. с sed-то мне понятно, но зачем такой скрипт накрутили...
Честно говоря, мне этот мутный скрипт не понравился - уж больно он запутанный и непонятный. Конечно хаять - самое простое, потому я оставляю это любопытное дело для вас, а сам я уже написал свой вариант, в нём основную часть скрипта мне удалось свернуть с 9и, до четырёх команд моей любимой sed. Это мне удалось благодаря во первых префиксам \L и \U, о которых рассказано выше (ими очень удобно оперировать регистром букв, к тому-же они работают и с русскими буквами). И кроме того, я использовал GNUтый модификатор e, позволяющий не только найти, и поменять строку, но и выполнять её (без всяких извратов вроде eval, как в оригинале).
Идеи построения данных скриптов, думаю пригодятся для решения широкого класса задач (особенно когда файлов много, а правила переименования не тривиальны).
Пример 7.4.
#!/bin/bash # rename files to lower/upper case... # # usage: # move-to-lower * # move-to-upper * # or # move-to-lower -R . # move-to-upper -R . # help() { cat << eof Использование: $0 [-n] [-r] [-h] files... -n Ничего не делает, только показывает, что будет делать. -R Рекурсивный обход каталогов (переименовывает только файлы) -h Это сообщение. files Файлы подлежащие переименованию. Примеры: $0 -n * (попробуйте сначала так, если понравится...) $0 * $0 -R . eof } finder="" modif="ep" while : do case "$1" in -n) modif="p" ;; -R) finder="find" ;; -h) help ; exit 1 ;; *) break ;; esac shift done if [ -z "$1" ]; then echo "Использование: $0 [-h] [-n] [-r] files..." exit 1 fi case `basename $0` in *upper*) prefix="\\U" ;; *) prefix="\\L" ;; esac # поиск файлов if [ -z "$finder" ]; then finder=`echo "$@" | sed -r 's/\s+/\n/g'` else finder=`$finder $@ -type f` fi echo "$finder" | sed -rn ' # удаление слешей в конце строки. s!/+$!! # если нет слешей, то в начеле приписываем "./" /\//! s!^!./! # вот и всё: # достаточно отделить имя от пути. # имя это \2, а путь \1 # переносим этот файл, перед новым именем # ставим префикс s!^(.*)(/[^/]+)$!\1\2\n\1'$prefix'\2! # не, ещё проверить надо /^(.*)\n\1$/ ! s/^(.*)\n(.*)/mv -v \1 \2/'$modif' '
Пример 7.5.
#!/bin/sh set | sed -n ' :x # Если не совпадает с "=()", печать, и загрузка след. строки /=()/! { p; b; } / () $/! { p; b; } # начинается секция с функцией # сохраняем строку с заголовком в буфере2 h # если след. строка начинается на {, то выходим - дальше ничего # интересного нет. n /^{/ q # вывод прошлой строки x; p # переход на обработку текущей. x; bx '
$ echo "ABCDEFGHJ" | ./reverseline.sed JHGFEDCBA
Пример 7.6.
#!/usr/bin/sed -rf # если в строку нет 2х символов (иначе, длинна строки меньше двух), # то ничего не делаем /../! b # Реверс строки # сначала в начало и в конец строки добавляются символы \n. s/^.*$/\ &\ / # Обмен первого и последнего символа местами, символы меняются # вместе с маркёрами \n, причём относительный порядок символа и маркёра # не меняется. Поэтому маркёры оказываются "внутри" - например: # \n F .* E \n --- до замены # E \n .* \n F --- после # Цикл продолжается пока внутри не останется 1 или 0 символов. tx :x s/(\n.)(.*)(.\n)/\3\2\1/ tx # Удаление маркёров. s/\n//g
Пример 7.7.
#!/usr/bin/sed -nf # обращение строк входного файла, т.е. первая строка становится последний и т.д.. # примечание: тут *весь* файл гоняется из области удержания и обратно для каждой строки, # например если у нас 100,000 строк по 80 байт, нам надо перенести 8,000,000 из памяти в память # :-( это конечно очень медленно, и может привести к переполнению памяти для некоторых # реализаций sed... # начиная со второй строки, мы *добавляем* область удержания к буферу # (область удержания содержит сейчас все строки, кроме текущей в обратном порядке) 1! G # если строка последняя, то мы распечатываем все строки $ p # сохранение области удержания h
s/(.*)\n.*/\1/а для получения новой строчки достаточно выполнить
s/.*\n//Однако в данном примере и так используется данный приём - к буферу добавляется загруженная строка - командами x; G; h. После печати строки с номером, мы вырезаем из неё номер, для того, чтобы его увеличить на единицу.
Этот пример работает, но работает он медленно. Намного быстрее не мучиться, а использовать для таких целей две команды sed, конвейером (используя sed-команду = для получения номера строки). Кроме того, использование cat -n ещё быстрее, ведь это не скрипт, а специально созданная программа.
Кроме того, увеличивать номер строки быстрее с помощью bash'а, этот язык намного лучше справляется с арифметикой. Сам по себе bash умеет выполнять арифметические действия, например так bash увеличивает число на единицу (инкремент):
echo "123" | sed -r 's/^[0-9]+$/x=&; ((x++)); echo $x/e'
Тут я сначала загоняю число в bash-переменную $x, которую затем увеличиваю на 1 ((используя двойные скобки, эта непереносимая конструкция, и работает только в bash'е. Для переносимости лучше использовать например let, хотя лучше вообще ничего не использовать ;) )).
Число, которое будет найдено sed подставится в новую строчку, которая отправится через пайп (pipe) на выполнение оболочке. Это три команды bash'а. После выполнения этих команд в буфере окажется увеличенное на единицу число. Тут оно просто выведется на экран, однако его можно редактировать и проводить с ним другие действия.
Если средств bash'а вам недостаточно, то можно использовать калькулятор bc - он способен выполнять намного более сложные арифметические действия: например извлечение квадратного корня с точностью в 50 знаков после запятой.
$ echo "scale=50; sqrt(3)" |bc 1.73205080756887729352744634150587236694280525381038
Тут команда echo отправляет по конвейеру команды для калькулятора bc, и это всё можно встроить в sed, которая с лёгкостью отправит эти команды bash'у. Конечно это долго, но всё-же намного быстрее, чем вычислять корни средствами самой sed (учитывая, что никаких средств у sed для этого нет).
echo 'ed2k://|file|Zlo.tvorimoe.ludjmi_SATRip.1984.(emule-rus.net).avi |915735510|CA2DAD0B6FE305058D9296147FC096A5|h=ZI357V3JSPJGKKTB5A2CR4EYYAUZJJJC|/ '| sed -r 's/.*\|([[:xdigit:]]{32})\|.*/echo "ibase=16; \1" | bc/e' 268741218270185578931233294203517703845выделит из ED2K ссылке хеш файла, а затем переведёт его в десятичную систему счисления. Хеш записан в шестнадцатеричной системе счисления, и для его извлечения я использую выражение /[[:xdigit:]]{32}/.
Пример 7.9.
#!/usr/bin/sed -nrf # Извлекаем номер строки, если он равен "" (пустая строка), # то устанавливаем его в 1 (такое бывает для первой строки). x /^$/ s/^.*$/1/ # Добавляем номер строки в её начало G h # Форматирование и печать s/^/ / s/^ *(......)\n/\1 /p # Извлекаем номер строки из области удержания, g s/\n.*$// # Если номер начинается с девятки, добавляем перед ней ноль. /^9*$/ s/^/0/ # тут я даже и не знаю как объяснить: короче, перед последней цифрой и (возможно) # девятками после неё добавляем букву х, например для 123545 получится 1234х5, # а для 12999 получится 1х2999, т.е. буква х - граница изменения, будем менять то, # что *после* неё. s/.9*$/x&/ # сохраняем число h # вырезаем ту часть, которая не поменяется s/^.*x// # в изменяемой части меняем 0 на 1, 1 на 2, и т.д... y/0123456789/1234567890/ # теперь опять берём всё число x # однако теперь берём неизменную часть s/x.*$// # осталось объединить части G s/\n// # и сохранить следующий номер в области удержания. h
/^$/ { p; b}Это нужно для пропуска пустой строки.
Пример 7.10.
#!/usr/bin/sed -nf /^$/ { p b } # Same as cat -n from now x /^$/ s/^.*$/1/ G h s/^/ / s/^ *\(......\)\n/\1 /p x s/\n.*$// /^9*$/ s/^/0/ s/.9*$/x&/ h s/^.*x// y/0123456789/1234567890/ x s/x.*$// G s/\n// h
Пример 7.11.
#!/usr/bin/sed -nf # Для начала, все символы мы превращаем в `a' s/./a/g # и добавляем к нашему счётчику, напомню, a - это единица. H x # перенос строки - тоже символ. s/\n/a/ # а так реализован перенос разрядов, для свёртки единиц в десятки и т.д.. # Автор упоминает о том, что команды перехода тут необязательны, # однако он полагает, что так будет быстрее. Если честно - я не знаю, # думаю что разница не будет слишком большой. b a : a; s/aaaaaaaaaa/b/g; t b; b done : b; s/bbbbbbbbbb/c/g; t c; b done : c; s/cccccccccc/d/g; t d; b done : d; s/dddddddddd/e/g; t e; b done : e; s/eeeeeeeeee/f/g; t f; b done : f; s/ffffffffff/g/g; t g; b done : g; s/gggggggggg/h/g; t h; b done : h; s/hhhhhhhhhh//g # Если строка не последняя, то мы сохраняем наш счётчик, # и завершаем обработку этой строки. : done $! { h b } # Для последней строки необходима конвертация в десятичную # систему счисления. : loop # след. команда s выполнится тогда, когда в счётчике нет букв a, # а это бывает тогда, и только тогда, когда число кратно 10, # в этом частном случае, мы добавляем к числу справа 0, # при этом следующие 9 s не выполняются (так-как "a" нету). /a/! s/[b-h]*/&0/ # для не кратных 10 чисел мы меняем последние буквы a на нужную цифру # догадайтесь сами, почему они будут последними :-) s/aaaaaaaaa/9/ s/aaaaaaaa/8/ s/aaaaaaa/7/ s/aaaaaa/6/ s/aaaaa/5/ s/aaaa/4/ s/aaa/3/ s/aa/2/ s/a/1/ : next # Эта хитрая команда: она делит счётчик на 10, например "db" у нас равно 1010, # а после замены будет "ca", т.е. - 101. y/bcdefgh/abcdefg/ # и так до тех пор, пока все буквы не перейдут в цифры. /[a-h]/ b loop p
/УСЛОВИЕ/ b
Ну тут наверное самое время выяснить, что лучше и быстрее.
Как вы уже наверное догадались, в оригинальном info об этом не сказано, уж не знаю почему. Наверное потому, что авторам info sed не хватило времени на это. Впрочем они и без этого много сделали.
На самом деле, оба способа перехода по своему хороши: переход с помощью адресного выражения и с помощью команды b обычно намного удобнее:
/XXX/{ s/// }Тут команда s повторно ищет /XXX/ и его удаляет. Это намного быстрее, т.к. не требуется второй раз компилировать регулярное выражение.
К сожалению, часто использования ветвления адресным выражением и командой b невозможно: дело в том, что в адресном выражении недопустимы обратные ссылки, и потому там нельзя использовать многие RE.
Кроме того, команда s сама реализует ветвление - замена состоится только если RE будет найдено. При этом замена вовсе не обязательно подразумевает только замену символов - это может быть и запись в файл, или даже выполнение внешней команды.
В итоге, мы видим что в sed-скриптах можно производить несколько типов условных переходов:
Решение за вами. Каждый способ обладает своими преимуществами и недостатками.
Пример 7.12.
#!/usr/bin/sed -nf # Convert words to a's s/[ tab][ tab]*/ /g s/^/ / s/ [^ ][^ ]*/a /g s/ //g # Append them to hold space H x s/\n// # From here on it is the same as in wc -c. /aaaaaaaaaa/! bx; s/aaaaaaaaaa/b/g /bbbbbbbbbb/! bx; s/bbbbbbbbbb/c/g /cccccccccc/! bx; s/cccccccccc/d/g /dddddddddd/! bx; s/dddddddddd/e/g /eeeeeeeeee/! bx; s/eeeeeeeeee/f/g /ffffffffff/! bx; s/ffffffffff/g/g /gggggggggg/! bx; s/gggggggggg/h/g s/hhhhhhhhhh//g :x $! { h; b; } :y /a/! s/[b-h]*/&0/ s/aaaaaaaaa/9/ s/aaaaaaaa/8/ s/aaaaaaa/7/ s/aaaaaa/6/ s/aaaaa/5/ s/aaaa/4/ s/aaa/3/ s/aa/2/ s/a/1/ y/bcdefgh/abcdefg/ /[a-h]/ by p
Здесь текущая строка прибавляется к области удержания, а начиная с одиннадцатой, из этой области удаляется та строка, которая там была первой.
Быстрее должен быть (проверьте, мне лень) другой подход - использовать буфер, из которого начиная с одиннадцатой строки удаляется первая, для файлов длиннее 10 строк у нас будет всегда 11 циклов, первые 10 строк загрузятся в первых 10 циклах, а в 11ом загрузятся все остальные строки, при этом грузится они будут командой N, которая догружает строку из потока в буфер, а потом первая строка удалится командой D, которая ничего не выводит. В конце файла мы просто прервём скрипт, и он распечатает буфер, т.е. последние 10 строк.
Пример 7.17.
#!/usr/bin/sed -rf h :b # если строка последняя - печать и выход. $b # загружаем следующую строку N /^(.*)\n\1$/ { # сл. строка такая-же как прошлая, выгружаем # прошлую из области удержания, как-бы отменяя действие команды N g bb } # если команда N загрузила посл. строку, печатаем две посл. строки сразу. $b # строки разные, мы сначала печатаем первую из них P # потом её удаляем D # и при окончании этого цикла печатается следующая строка.
Пример 7.18.
#!/bin/sed -rnf $b N /^(.*)\n\1$/ { # печать первой повторяющейся строки s/.*\n// p # цикл, читаем строки до тех пор, пока они повторяются :b $b N /^(.*)\n\1$/ { s/.*\n// bb } } # последняя строка не может повторятся $b # мы нашли две разные строки, мы удаляем первую, а вот следующую # необходимо анализировать дальше D
Пример 7.19.
#!/usr/bin/sed -rf # поиск линий, которые не повторяются, до тех пор пока сл. строка # отличается от предыдущей, печатаем те, которые отличаются. $b N /^(.*)\n\1$/ ! { P D } :c # Сейчас у нас две одинаковые строки в буфере, # если это последние строки - просто выходим. $d # иначе отрезаем одну из одинаковых строк, добавляем ещё одну, # и если строки одинаковые - переходим к метке :с. s/.*\n// N /^(.*)\n\1$/ { bc } # Удаляем "одинаковую" строку, и переходим # к началу скрипта. D
Последний пример посвящён быстрому удалению пустых строк (как cat -s).
для начала соберём все пустые строки вместе и заменим их одной пустой строкой:
Пример 7.20.
#!/usr/bin/sed -f # для пустых строк выполняется команда N, # которая их читает в этом цикле # примечание: в регулярном выражении использована звёздочка # для задания любого числа '\n', которые вставляет команда N # (во время первого прохода этого символа в строке вообще нет) :x /^\n*$/ { N bx } # сейчас у нас имеется не пустая строка, перед которой стоят несколько # символов '\n' # все эти символы мы заменим на один перевод строки s/\n*/\ /
К сожалению, этот скрипт вставляет пустые строки и между не пустыми, что нам не нужно.
Вот следующий вариант:
Пример 7.21.
#!/usr/bin/sed -f # Удаление всех начальных пустых строк # примечание от drBatty: непонятно почему написано /^./, # ИМХО можно и просто /./ 1,/^./{ /./!d } # для пустых строк мы грузим следующую, и если она тоже пустая, # мы удаляем загруженный командой N символ '\n' # а потом опять загружаем следующую. :x /./!{ N s/^\n$// tx }
В оригинале написано, что этот скрипт недостаточно быстр...
А этот скрипт вроде как побыстрее...
Пример 7.22.
#!/usr/bin/sed -nf # удаление всех (начальных) пустых строк /./!d # теперь у нас имеется не пустая строка :x # печатаем её p # извлекаем следующую n # есть символы? тогда продолжаем печатать не пустые строки /./bx # символов нет - эта строка пустая :z # вынимаем следующую строку, если её нет (текущая - последняя) # во время выполнения n скрипт завершает работу n # ещё одна пустая? тогда мы её игнорируем, и переходим к следующей # этот скрипт удаляет ВСЕ пустые строки /./!bz # все пустые строки были удалены/проигнорированы # и теперь мы добавляем ещё одну пустую строку ПЕРЕД той, что сейчас напечатается i\ bx
Ну мы сейчас проверим, создадим файл из 100000 тестовых файлов командой
for ((i=1; i<10000; i++)); do cat test.txt >> big.txt; done
и посмотрим...
Действительно, третий вариант немного быстрее второго, 1.47сек против 1.62. Хотя разница не слишком велика. (для файла в 17Мб). Если взять файл в 5 раз больше (85 248 295 байт), то картина та-же: 7.08сек против 7.88сек. Однако следует учесть, что если строки длинные и кодировка UTF-8, то разница будет намного больше (наверное на ~50%). По той причине, что в третьем варианте нет команды s///, которая очень медленно работает для длинных строк (особенно в кодировке UTF-8).
На http://unixforum.org мы сравнивали быстродействие cut, awk, sed и perl'а - как выяснилось - sed самая медленная. Но perl слишком сложен для меня, а cut и awk работают только с полями, и часто их невозможно использовать (к примеру в этом случае это не получится). В bash'е так-же имеется возможность работы с текстом. Я её даже не рассматриваю, потому как даже для создания тестового файла в 85Мб потребовалось несколько минут, видимо для его обработки потребуется десятки минут, а может и часы.
Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.