forked from emulek/sed
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch07.html
793 lines (697 loc) · 66.3 KB
/
ch07.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Глава 7. Примеры из info sed.</title><link rel="stylesheet" href="chs/default.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.1"><link rel="home" href="index.html" title="Краткий учебник по sed."><link rel="up" href="index.html" title="Краткий учебник по sed."><link rel="prev" href="ch06s04.html" title="GNU Расширения регулярных выражений."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Глава 7. Примеры из info sed.</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch06s04.html">Пред.</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> </td></tr></table><hr></div><div class="chapter" title="Глава 7. Примеры из info sed."><div class="titlepage"><div><div><h2 class="title"><a name="id2538358"></a>Глава 7. <a name="ch5"></a>Примеры из info sed.</h2></div></div></div><div class="toc"><p><b>Содержание</b></p><dl><dt><span class="section"><a href="ch07.html#id2538366"></a></span></dt><dd><dl><dt><span class="section"><a href="ch07.html#id2538377">Центрирование строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538426">Увеличение числа.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538494">Переименование файлов.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538806">Вывод окружения bash.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538851">Инверсия порядка символов в строке.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538906">Реверс строк в файле.</a></span></dt><dt><span class="section"><a href="ch07.html#id2538950">Нумерация строк</a></span></dt><dt><span class="section"><a href="ch07.html#id2539014">Нумерация строк с использованием счётчика в области удержания.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539296">Нумерация не пустых строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539328">Подсчёт символов.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539466">Подсчёт слов.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539763">Подсчёт строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539781">Вывод первых 10 строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2539800">Вывод последних строк</a></span></dt><dt><span class="section"><a href="ch07.html#id2539962">Вывод не повторяющихся строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2540024">Печать только строк, которые повторяются - uniq -d</a></span></dt><dt><span class="section"><a href="ch07.html#id2540049">Удаление всех повторяющихся строк.</a></span></dt><dt><span class="section"><a href="ch07.html#id2540093">Удаление пустых строк и быстродействие.</a></span></dt></dl></dd></dl></div><div class="section"><div class="titlepage"></div><p>
Здесь предоставлены разные примеры из <span class="command"><strong>info sed</strong></span>.
</p><div class="section" title="Центрирование строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2538377"></a><a name="info_center"></a>Центрирование строк.</h4></div></div></div>
Этот скрипт центрирует строки в 80колоночный формат, если 80 колонок вам не нравится, поменяйте число пробелов в первом блоке {...}
<div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
А ещё дальше в скрипте встречается число 81 - его тоже надо поменять.
</div><div class="example"><a name="id2538402"></a><p class="title"><b>Пример 7.1. </b></p><div class="example-contents"><pre class="programlisting">#!/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/</pre></div></div><br class="example-break"></div><div class="section" title="Увеличение числа."><div class="titlepage"><div><div><h4 class="title"><a name="id2538426"></a>Увеличение числа.</h4></div></div></div>
Этот скрипт демонстрирует арифметику в sed. Далее, будут описаны и более быстрые примеры, этот уж откровенно тормозной, впрочем для подсчёта 1-1000 элементов и он подойдёт.
<div class="example"><a name="id2538442"></a><p class="title"><b>Пример 7.2. </b></p><div class="example-contents"><pre class="programlisting">#!/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/</pre></div></div><br class="example-break">
`sed' гуру Greg Ubben написал калькулятор `dc' !
<div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>{drBatty} - совсем крыша поехала у человека... Хотя там не очень сложно... Но всё одно - писать устанешь.</div></div><div class="section" title="Переименование файлов."><div class="titlepage"><div><div><h4 class="title"><a name="id2538494"></a><a name="info_ren"></a>Переименование файлов.</h4></div></div></div><p>
Это чрезвычайно важный пример для русского программера; кроме того, что тут показано, как обрабатывать вывод одной команды передачей его другой, вы так-же поймёте как можно переименовывать файлы
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
на Руси эта задача встречается сплошь и рядом - всё из-за того, что
<div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">большинство Linux-систем считают правильной кодировку UTF-8,</li><li class="listitem">Однако, старые *nux системы (например моя) работают в KOI8-R,</li><li class="listitem">Windows работает в кодировке cp1251</li><li class="listitem">Та-же венда использует в своей консоли cp866 (а так-же эта кодировка обычно на CD дисках|образах)</li><li class="listitem">В почте используется традиционно KOI8-R</li><li class="listitem">Многие программы и БД используют какую-то стандартную (в Америке) Русскую(sic!) кодировку ISO-непомню-сколько</li><li class="listitem">На русских сайтах и форумах вообще чёрт ногу сломит (они в нескольких кодировках).</li><li class="listitem">В некоторых случаях(например в реестре) венда решила догнать и перегнать, и как всегда, ректальным путём - используя UTF-16</li><li class="listitem">можно ещё много чего весёлого вспомнить... )</li></ol></div>
Что касается содержимого файлов, то с ними проще - достаточно прогнать их через iconv -f КОДИРОВКА, и текст преобразуется из чужеродной КОДИРОВКИ в нашу, родную (не пишу какую именно, у вас может быть другая)
</div><p>
</p><p>
Далее приводится оригинальный скрипт:
</p><div class="example"><a name="id2538592"></a><p class="title"><b>Пример 7.3. </b></p><div class="example-contents"><pre class="programlisting">#! /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-то мне понятно, но зачем такой скрипт накрутили...</pre></div></div><p><br class="example-break">
</p><p>
Честно говоря, мне этот мутный скрипт не понравился - уж больно он запутанный и непонятный. Конечно хаять - самое простое, потому я оставляю это любопытное дело для вас, а сам я уже написал свой вариант, в нём основную часть скрипта мне удалось свернуть с 9и, до четырёх команд моей любимой sed. Это мне удалось благодаря во первых префиксам \L и \U, о которых рассказано выше (ими очень удобно оперировать регистром букв, к тому-же они работают и с русскими буквами). И кроме того, я использовал GNUтый модификатор e, позволяющий не только найти, и поменять строку, но и выполнять её (без всяких извратов вроде eval, как в оригинале).
</p><p>
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Оба скрипта имеют ограничение: нельзя переименовывать файлы с \n в имени. (впрочем и пробелы тоже не допустимы, точнее их надо экранировать, а ещё точнее - хрен его знает, проверять лень). Кроме того, я-бы не хотел, что-бы скрипт был слишком перегружен незначительными деталями.
</div><p>
</p><p>
Идеи построения данных скриптов, думаю пригодятся для решения широкого класса задач (особенно когда файлов много, а правила переименования не тривиальны).
</p><p>
</p><div class="caution" title="Предостережение" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Предостережение</h3>
Хотя в имени файла допустимы любые байты кроме \0 и '/', для sed не все байты являются СИМВОЛАМИ, несмотря на это, я всё-же писал скрипты, которые корректно обрабатывают и строки с НЕСИМВОЛАМИ. (Явно об этом нигде не сказано, но НЕСИМВОЛЫ появляются лишь при использовании кодировки UTF-8, в обычных кодировках всё работает (вроде-бы) нормально, однако, и там нужно быть очень внимательным).
<div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Сказано. В каталоге с исходниками, в файле <a class="link" href="ch02s04.html#bugs">BUGS</a>.
</div></div><p>
</p><p>
</p><div class="example"><a name="id2538744"></a><p class="title"><b>Пример 7.4. </b></p><div class="example-contents"><pre class="programlisting">#!/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'
'</pre></div></div><p><br class="example-break">
</p></div><div class="section" title="Вывод окружения bash."><div class="titlepage"><div><div><h4 class="title"><a name="id2538806"></a>Вывод окружения bash.</h4></div></div></div>
Цель этого примера: показать как sed умеет заглядывать вперёд в тексте. То, что начинается функция будет ясно только в след. строке, а решать, печатать или нет текущую строку надо прямо сейчас, потому sed приходится запоминать строку, загружать сл. и в зависимости от результата проверки печатать или не печатать текущую.
<div class="example"><a name="id2538821"></a><p class="title"><b>Пример 7.5. </b></p><div class="example-contents"><pre class="programlisting">#!/bin/sh
set | sed -n '
:x
# Если не совпадает с "=()", печать, и загрузка след. строки
/=()/! { p; b; }
/ () $/! { p; b; }
# начинается секция с функцией
# сохраняем строку с заголовком в буфере2
h
# если след. строка начинается на {, то выходим - дальше ничего
# интересного нет.
n
/^{/ q
# вывод прошлой строки
x; p
# переход на обработку текущей.
x; bx
'</pre><div class="caution" title="Предостережение" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Предостережение</h3>
Обратите внимание: это вообще-то скрипт на shell (на bash), однако в нём присутствует вставка на sed. Сам sed-скрипт заключён в 'простые кавычки'.
</div></div></div><br class="example-break"></div><div class="section" title="Инверсия порядка символов в строке."><div class="titlepage"><div><div><h4 class="title"><a name="id2538851"></a>Инверсия порядка символов в строке.</h4></div></div></div>
Этот скрипт меняет последовательность символов в строке на противоположную, например
<pre class="screen">$ echo "ABCDEFGHJ" | ./reverseline.sed
JHGFEDCBA</pre><div class="example"><a name="id2538867"></a><p class="title"><b>Пример 7.6. </b></p><div class="example-contents"><pre class="programlisting">#!/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</pre></div></div><br class="example-break"></div><div class="section" title="Реверс строк в файле."><div class="titlepage"><div><div><h4 class="title"><a name="id2538906"></a>Реверс строк в файле.</h4></div></div></div><div class="example"><a name="id2538911"></a><p class="title"><b>Пример 7.7. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -nf
# обращение строк входного файла, т.е. первая строка становится последний и т.д..
# примечание: тут *весь* файл гоняется из области удержания и обратно для каждой строки,
# например если у нас 100,000 строк по 80 байт, нам надо перенести 8,000,000 из памяти в память
# :-( это конечно очень медленно, и может привести к переполнению памяти для некоторых
# реализаций sed...
# начиная со второй строки, мы *добавляем* область удержания к буферу
# (область удержания содержит сейчас все строки, кроме текущей в обратном порядке)
1! G
# если строка последняя, то мы распечатываем все строки
$ p
# сохранение области удержания
h</pre></div></div><br class="example-break"></div><div class="section" title="Нумерация строк"><div class="titlepage"><div><div><h4 class="title"><a name="id2538950"></a>Нумерация строк</h4></div></div></div>
Этот скрипт заменяет команду cat -n, т.е. не просто выводит файлы в stdout, но и ещё добавляет к ним номера строк.
<div class="tip" title="Подсказка" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Подсказка</h3>
Мы можем воспользоваться shell-скриптом, для того, что-бы объединить 2 команды sed: первая будет выводить номера строк и сами строки для второй, которая объединит и отформатирует вывод (попробуйте например просто набрать sed '=' test_file.txt).
</div><div class="example"><a name="id2538975"></a><p class="title"><b>Пример 7.8. </b></p><div class="example-contents"><pre class="programlisting">#! /bin/sh
sed -e "=" $@ | sed -e '
s/^/ /
N
s/^ *\(......\)\n/\1 /
'</pre></div></div><br class="example-break">
Тут для склейки строк с номером используется команда N. В оригинале написано, что этот скрипт для примера, а не для обучения, однако, позволю себе заметить, что такой скрипт на длинном файле отработал за 4 секунды, а следующий пример работает уже минут 10, и вовсе не собирается останавливаться! Т.о. в данном конкретном случае использовать одну sed - плохая идея, используйте две ;-). А вся беда в том, что sed может уметь выводить номер строки только в выходной поток, по какой-такой причине не сделали за 30+ лет вывод в буфер - мне неведомо... Вот и приходится извращаться, номер вывести можно, можно использовать как адрес (в т.ч. и как диапазон или с каким-то шагом), а вот использовать - нельзя.
</div><div class="section" title="Нумерация строк с использованием счётчика в области удержания."><div class="titlepage"><div><div><h4 class="title"><a name="id2539014"></a>Нумерация строк с использованием счётчика в области удержания.</h4></div></div></div>
Следующий алгоритм увеличивает десятичное число используя два буфера, которые имеются у sed, при этом, конечно, содержимое области удержания необратимо теряется. Опять-таки - отсебятина: во-первых это и не важно обычно, во-вторых, можно и тут извернуться, и сохранить содержимое буфера.
<div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Как-же сохранить буфер? Это довольно просто: надо загружать в буфер строчки командой <span class="command"><strong>H</strong></span>, тогда мы сможем отрезать старое содержимое буфера командой
<pre class="programlisting">s/(.*)\n.*/\1/</pre>
а для получения новой строчки достаточно выполнить
<pre class="programlisting">s/.*\n//</pre>
Однако в данном примере и так используется данный приём - к буферу добавляется загруженная строка - командами <span class="command"><strong>x; G; h</strong></span>. После печати строки с номером, мы вырезаем из неё номер, для того, чтобы его увеличить на единицу.
</div><div class="caution" title="Предостережение" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Предостережение</h3><p>
Этот пример работает, но работает он медленно. Намного быстрее не мучиться, а использовать для таких целей две команды sed, конвейером (используя sed-команду <span class="command"><strong>=</strong></span> для получения номера строки). Кроме того, использование cat -n ещё быстрее, ведь это не скрипт, а специально созданная программа.
</p><p>
Кроме того, увеличивать номер строки быстрее с помощью bash'а, этот язык намного лучше справляется с арифметикой. Сам по себе bash умеет выполнять арифметические действия, например так bash увеличивает число на единицу (инкремент):
</p><pre class="programlisting">echo "123" | sed -r 's/^[0-9]+$/x=&; ((x++)); echo $x/e'</pre><p>
Тут я сначала загоняю число в bash-переменную $x, которую затем увеличиваю на 1 ((используя двойные скобки, эта непереносимая конструкция, и работает только в bash'е. Для переносимости лучше использовать например let, хотя лучше вообще ничего не использовать ;) )).
</p><p>
Число, которое будет найдено sed подставится в новую строчку, которая отправится через пайп (pipe) на выполнение оболочке. Это три команды bash'а. После выполнения этих команд в буфере окажется увеличенное на единицу число. Тут оно просто выведется на экран, однако его можно редактировать и проводить с ним другие действия.
</p><p>
</p><div class="warning" title="Внимание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Внимание</h3>
Обратите внимание на то, как я фильтрую число - тут можно было-бы написать и просто /.*/, и всё-бы работало. Однако, я пишу намного более сложное выражение: /^[0-9]+$/. Это связано с безопасностью: злоумышленник может подсунуть мне какую-нибудь гадость, которую sed отправит по конвейеру на выполнение shell. В данном случае это не получится: если подсунуть скрипту что-то отличающееся от цифр, то команда s///e попросту не сработает. Это не паранойя, это обычная практика.
</div><p>
</p><p>
Если средств bash'а вам недостаточно, то можно использовать калькулятор <span class="command"><strong>bc</strong></span> - он способен выполнять намного более сложные арифметические действия: например извлечение квадратного корня с точностью в 50 знаков после запятой.
</p><pre class="programlisting">$ echo "scale=50; sqrt(3)" |bc
1.73205080756887729352744634150587236694280525381038</pre><p>
</p><p>
Тут команда <span class="command"><strong>echo</strong></span> отправляет по конвейеру команды для калькулятора <span class="command"><strong>bc</strong></span>, и это всё можно встроить в sed, которая с лёгкостью отправит эти команды bash'у. Конечно это долго, но всё-же намного быстрее, чем вычислять корни средствами самой sed (учитывая, что никаких средств у sed для этого нет).
</p><p>
</p><div class="tip" title="Подсказка" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Подсказка</h3>
Арифметические задачи встречаются намного чаще, чем принято думать. Конечно вычисление корней нужно довольно редко, но есть и другие задачи. К примеру перевод в другие системы счисления. К примеру эта команда
<pre class="programlisting">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</pre>
выделит из ED2K ссылке хеш файла, а затем переведёт его в десятичную систему счисления. Хеш записан в шестнадцатеричной системе счисления, и для его извлечения я использую выражение /[[:xdigit:]]{32}/.
</div><p>
</p></div><div class="example"><a name="id2539257"></a><p class="title"><b>Пример 7.9. </b></p><div class="example-contents"><pre class="programlisting">#!/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</pre></div></div><br class="example-break"></div><div class="section" title="Нумерация не пустых строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2539296"></a>Нумерация не пустых строк.</h4></div></div></div>
Следующий алгоритм отличается от предыдущего разве-что отсутствием комментариев, единственное отличие - это в начале
<pre class="programlisting">/^$/ { p; b}</pre>
Это нужно для пропуска пустой строки.
<div class="example"><a name="id2539315"></a><p class="title"><b>Пример 7.10. </b></p><div class="example-contents"><pre class="programlisting">#!/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</pre></div></div><br class="example-break"></div><div class="section" title="Подсчёт символов."><div class="titlepage"><div><div><h4 class="title"><a name="id2539328"></a>Подсчёт символов.</h4></div></div></div>
Этот скрипт подсчитывает количество символов, он нужен для иллюстрации другого пути реализации арифметики в sed-скриптах.
<div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Так делал Робинзон, подсчитывая дни: он отмечал каждый день маленькой чёрточкой, которые затем, раз в неделю перечёркивал одной большой. Так-же поступим и мы: будем отмечать единицы буквой a, и как их скопится 10 штук, заменим их на b, и так далее.
</div><div class="example"><a name="id2539354"></a><p class="title"><b>Пример 7.11. </b></p><div class="example-contents"><pre class="programlisting">#!/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</pre></div></div><br class="example-break"><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Некоторые версии sed имеют ограничение 199 команд на скрипт (об этом сказано в оригинальном info sed). Я не нашёл в исходных текстах своей версии такого ограничения. Не вижу никаких причин ограничивать свои скрипты, если конечно вы не планируете использовать sed от necro$oft. Т.к. в OS Windows никакой sed нет, и её придётся ставить дополнительно, то даже в этой <span class="quote">«<span class="quote">ОС</span>»</span> можно писать скрипты из любого числа команд...
</div></div><div class="section" title="Подсчёт слов."><div class="titlepage"><div><div><h4 class="title"><a name="id2539466"></a>Подсчёт слов.</h4></div></div></div>
Этот скрипт практически не отличается от предыдущего, разве что, тут все слова превращаются сначала в "a", а затем они считаются. Кроме того, несколько изменена схема переносов: сам алгоритм не поменялся, но вместо условного перехода <span class="command"><strong>t</strong></span> используется переход
<pre class="programlisting">/УСЛОВИЕ/ b</pre><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3><p>
Ну тут наверное самое время выяснить, что лучше и быстрее.
</p><p>
Как вы уже наверное догадались, в оригинальном info об этом не сказано, уж не знаю почему. Наверное потому, что авторам info sed не хватило времени на это. Впрочем они и без этого много сделали.
</p><p>
На самом деле, оба способа перехода по своему хороши: переход с помощью адресного выражения и с помощью команды <span class="command"><strong>b</strong></span> обычно намного удобнее:
</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">
Переход командой b с адресом не затрагивает флаг перехода, который постоянно глючит.
</li><li class="listitem">
Адресное выражение компилируется, и в дальнейшем может использоваться повторно. К примеру так:
<pre class="programlisting">/XXX/{
s///
}</pre>
Тут команда <span class="command"><strong>s</strong></span> повторно ищет /XXX/ и его удаляет. Это намного быстрее, т.к. не требуется второй раз компилировать регулярное выражение.
</li></ol></div><p>
</p><p>
К сожалению, часто использования ветвления адресным выражением и командой <span class="command"><strong>b</strong></span> невозможно: дело в том, что в адресном выражении недопустимы обратные ссылки, и потому там нельзя использовать многие RE.
</p><p>
Кроме того, команда <span class="command"><strong>s</strong></span> сама реализует ветвление - замена состоится только если RE будет найдено. При этом <span class="emphasis"><em>замена</em></span> вовсе не обязательно подразумевает только замену символов - это может быть и запись в файл, или даже выполнение внешней команды.
</p><p>
В итоге, мы видим что в sed-скриптах можно производить несколько типов условных переходов:
</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">
Можно использовать адресное выражение, которое выполнит команду (или блок команд в {фигурных скобках}) только при выполнении определённого условия.
</li><li class="listitem">
Можно использовать условный переход с помощью команды <span class="command"><strong>b</strong></span> с адресом.
</li><li class="listitem">
Также можно производить множество действий с помощью команды <span class="command"><strong>s</strong></span>, которая выполняет заданные действия только если будет найдено хотя-бы одно совпадение заданного RE.
</li><li class="listitem">
Ну и на конец, возможно использовать условные переходы, командами <span class="command"><strong>t</strong></span> и <span class="command"><strong>T</strong></span>.
</li></ol></div><p>
</p><p>
Решение за вами. Каждый способ обладает своими преимуществами и недостатками.
</p><p>
</p><div class="caution" title="Предостережение" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Предостережение</h3>
Как вы сами наверное знаете, нельзя использовать GOTO - команду безусловного перехода. Вот только в sed-скриптах у нас нет другого выхода - кроме GOTO у нас ничего нет. По этой причине следует очень внимательно форматировать свои скрипты и при этом фанатично соблюдать свой <span class="emphasis"><em>стиль написания программ</em></span>. Конечно, вы можете забить на эту мою рекомендацию, но это приведёт только к тому, что вы сами не сможете разобраться в своих-же скриптах. Оно вам надо? Если надо (например из соображений безопасности) - используйте другие (компилируемые) языки, либо компилируйте sed-скрипты.
<div class="warning" title="Внимание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Внимание</h3>
Однако, напомню вам простую истину: всегда можно сломать <span class="quote">«<span class="quote">закрытый код</span>»</span>. Если вы не способны разобраться в закрытой программе, это вовсе не значит, что вскрыть код невозможно и программа <span class="quote">«<span class="quote">вполне безопасна</span>»</span>. Лично я многократно взламывал чужой код - в этом нет ничего архи-сложного. Потому не следует закрывать свой код - есть множество других способов защиты.
</div></div><p>
</p></div><div class="example"><a name="id2539739"></a><p class="title"><b>Пример 7.12. </b></p><div class="example-contents"><pre class="programlisting">#!/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</pre></div></div><br class="example-break"></div><div class="section" title="Подсчёт строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2539763"></a>Подсчёт строк.</h4></div></div></div>
Тут вообще не о чём думать!!! Как не странно, но sed это делает свободно:
<div class="example"><a name="id2539773"></a><p class="title"><b>Пример 7.13. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -nf
$=</pre></div></div><br class="example-break"></div><div class="section" title="Вывод первых 10 строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2539781"></a>Вывод первых 10 строк.</h4></div></div></div>
Так-же просто реализовать head: достаточно прервать работу sed после вывода нужного числа строк.
<div class="example"><a name="id2539792"></a><p class="title"><b>Пример 7.14. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -f
10q</pre></div></div><br class="example-break"></div><div class="section" title="Вывод последних строк"><div class="titlepage"><div><div><h4 class="title"><a name="id2539800"></a>Вывод последних строк</h4></div></div></div>
Эмуляция tail намного сложнее: sed не понимает, что такое "строка номер 10 с конца", потому-что файл просматривается построчно всего 1 раз. Мы должны сохранять где-то последние десять строк, проще всего: в области удержания:
<div class="example"><a name="id2539815"></a><p class="title"><b>Пример 7.15. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -nf
1! {; H; g; }
1,10 !s/[^\n]*\n//
$p
h</pre></div></div><br class="example-break"><p>
Здесь текущая строка прибавляется к области удержания, а начиная с одиннадцатой, из этой области удаляется та строка, которая там была первой.
</p><p>
Быстрее должен быть (проверьте, мне лень) другой подход - использовать буфер, из которого начиная с одиннадцатой строки удаляется первая, для файлов длиннее 10 строк у нас будет всегда 11 циклов, первые 10 строк загрузятся в первых 10 циклах, а в 11ом загрузятся все остальные строки, при этом грузится они будут командой N, которая догружает строку из потока в буфер, а потом первая строка удалится командой D, которая ничего не выводит. В конце файла мы просто прервём скрипт, и он распечатает буфер, т.е. последние 10 строк.
</p><div class="example"><a name="id2539868"></a><p class="title"><b>Пример 7.16. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -f
1h
2,10 {; H; g; }
$q
1,9d
N
D</pre></div></div><p><br class="example-break">
</p><p>
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
тут используется точка с запятой, для отделения команд. Это допустимо, но НЕ обязательно.
</div><p>
</p><div class="note" title="Замечание" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Замечание</h3>
Я всё-же проверил: получилось для первого скрипта 18.5 сек., а для второго 6.4 сек. Таким образом второй скрипт работает примерно в 3 раза быстрее. Для сравнения: tail сработала за 2.3 секунды (ага - эта задача явно не для sed, sed вообще ни в коем случае нельзя использовать, когда есть другая утилита, заточенная именно на эту задачу - что-же вы хотели, та-же tail выделяет в самом начале памяти под 10 строк, и просто читает туда данные, нам-же приходится для каждой строки удалять первую - для чего необходимо сдвинуть остальные - т.о. нам нужно не только прочитать N байт(одну строку), переместить N*10 байт! Если-бы файл был в памяти, то sed сработала-бы не в три раза, а в 11 раз медленнее! (кстати, похоже он и был в памяти(файл в 18Мб, свободно около 200), видимо для сдвига строк (D) sed применяет следующую оптимизацию: вместо того, что-бы сдвигать весть буфер, она просто считает, что после команды D буфер начинается не с первой, а со второй строки, надо исходник глянуть). В любом случае, двигать буфер на каждой строке без особой на то необходимости - плохая идея.) Проблема sed в данном случае: невозможность загружать строки в произвольное место буфера, мы можем только добавлять в конец буфера, или полностью заменить строку. С другой стороны, значительного увеличения быстродействия можно было-бы достичь другим путём: прочитать все строки, а после этого вернутся на 10 строк назад, если известна максимальная длинна строки(M), то это сделать довольно просто: посчитать все строки, а затем вернутся на M*10 байтов назад и найти десятую строчку, я, правда не знаю, чем возвращаться, разве что той-же tail с ключом -c.
</div><p>
</p></div><div class="section" title="Вывод не повторяющихся строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2539962"></a><a name="info_uniq"></a>Вывод не повторяющихся строк.</h4></div></div></div>
Этот скрипт иллюстрирует работу команд <a class="link" href="ch06s03.html#info_pp">P</a>, <a class="link" href="ch06s03.html#info_dd">D</a>, и <a class="link" href="ch06s03.html#info_nn">N</a>.
<div class="example"><a name="id2539991"></a><p class="title"><b>Пример 7.17. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -rf
h
:b
# если строка последняя - печать и выход.
$b
# загружаем следующую строку
N
/^(.*)\n\1$/ {
# сл. строка такая-же как прошлая, выгружаем
# прошлую из области удержания, как-бы отменяя действие команды N
g
bb
}
# если команда N загрузила посл. строку, печатаем две посл. строки сразу.
$b
# строки разные, мы сначала печатаем первую из них
P
# потом её удаляем
D
# и при окончании этого цикла печатается следующая строка.</pre></div></div><br class="example-break"></div><div class="section" title="Печать только строк, которые повторяются - uniq -d"><div class="titlepage"><div><div><h4 class="title"><a name="id2540024"></a>Печать только строк, которые повторяются - uniq -d</h4></div></div></div><div class="example"><a name="id2540030"></a><p class="title"><b>Пример 7.18. </b></p><div class="example-contents"><pre class="programlisting">#!/bin/sed -rnf
$b
N
/^(.*)\n\1$/ {
# печать первой повторяющейся строки
s/.*\n//
p
# цикл, читаем строки до тех пор, пока они повторяются
:b
$b
N
/^(.*)\n\1$/ {
s/.*\n//
bb
}
}
# последняя строка не может повторятся
$b
# мы нашли две разные строки, мы удаляем первую, а вот следующую
# необходимо анализировать дальше
D</pre></div></div><br class="example-break"></div><div class="section" title="Удаление всех повторяющихся строк."><div class="titlepage"><div><div><h4 class="title"><a name="id2540049"></a>Удаление всех повторяющихся строк.</h4></div></div></div>
Этот скрипт удаляет все строки которые повторяются.
<div class="example"><a name="id2540058"></a><p class="title"><b>Пример 7.19. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -rf
# поиск линий, которые не повторяются, до тех пор пока сл. строка
# отличается от предыдущей, печатаем те, которые отличаются.
$b
N
/^(.*)\n\1$/ ! {
P
D
}
:c
# Сейчас у нас две одинаковые строки в буфере,
# если это последние строки - просто выходим.
$d
# иначе отрезаем одну из одинаковых строк, добавляем ещё одну,
# и если строки одинаковые - переходим к метке :с.
s/.*\n//
N
/^(.*)\n\1$/ {
bc
}
# Удаляем "одинаковую" строку, и переходим
# к началу скрипта.
D</pre></div></div><br class="example-break"></div><div class="section" title="Удаление пустых строк и быстродействие."><div class="titlepage"><div><div><h4 class="title"><a name="id2540093"></a>Удаление пустых строк и быстродействие.</h4></div></div></div><p>
Последний пример посвящён быстрому удалению пустых строк (как cat -s).
</p><p>
для начала соберём все пустые строки вместе и заменим их одной пустой строкой:
</p><div class="example"><a name="id2540114"></a><p class="title"><b>Пример 7.20. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -f
# для пустых строк выполняется команда N,
# которая их читает в этом цикле
# примечание: в регулярном выражении использована звёздочка
# для задания любого числа '\n', которые вставляет команда N
# (во время первого прохода этого символа в строке вообще нет)
:x
/^\n*$/ {
N
bx
}
# сейчас у нас имеется не пустая строка, перед которой стоят несколько
# символов '\n'
# все эти символы мы заменим на один перевод строки
s/\n*/\
/
</pre></div></div><p><br class="example-break">
К сожалению, этот скрипт вставляет пустые строки и между не пустыми, что нам не нужно.
</p><p>
Вот следующий вариант:
</p><div class="example"><a name="id2540157"></a><p class="title"><b>Пример 7.21. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -f
# Удаление всех начальных пустых строк
# примечание от drBatty: непонятно почему написано /^./,
# ИМХО можно и просто /./
1,/^./{
/./!d
}
# для пустых строк мы грузим следующую, и если она тоже пустая,
# мы удаляем загруженный командой N символ '\n'
# а потом опять загружаем следующую.
:x
/./!{
N
s/^\n$//
tx
}
</pre></div></div><p><br class="example-break">
В оригинале написано, что этот скрипт недостаточно быстр...
</p><p>
А этот скрипт вроде как побыстрее...
</p><div class="example"><a name="id2540184"></a><p class="title"><b>Пример 7.22. </b></p><div class="example-contents"><pre class="programlisting">#!/usr/bin/sed -nf
# удаление всех (начальных) пустых строк
/./!d
# теперь у нас имеется не пустая строка
:x
# печатаем её
p
# извлекаем следующую
n
# есть символы? тогда продолжаем печатать не пустые строки
/./bx
# символов нет - эта строка пустая
:z
# вынимаем следующую строку, если её нет (текущая - последняя)
# во время выполнения n скрипт завершает работу
n
# ещё одна пустая? тогда мы её игнорируем, и переходим к следующей
# этот скрипт удаляет ВСЕ пустые строки
/./!bz
# все пустые строки были удалены/проигнорированы
# и теперь мы добавляем ещё одну пустую строку ПЕРЕД той, что сейчас напечатается
i\
bx
</pre></div></div><p><br class="example-break">
</p><p>
Ну мы сейчас проверим, создадим файл из 100000 тестовых файлов командой
</p><pre class="screen">for ((i=1; i<10000; i++)); do cat test.txt >> big.txt; done</pre><p>
и посмотрим...
</p><p>
Действительно, третий вариант немного быстрее второго, 1.47сек против 1.62. Хотя разница не слишком велика. (для файла в 17Мб). Если взять файл в 5 раз больше (85 248 295 байт), то картина та-же: 7.08сек против 7.88сек. Однако следует учесть, что если строки длинные и кодировка UTF-8, то разница будет намного больше (наверное на ~50%). По той причине, что в третьем варианте нет команды s///, которая очень медленно работает для длинных строк (особенно в кодировке UTF-8).
</p><p>
На <a class="ulink" href="http://unixforum.org" target="_top">http://unixforum.org</a> мы сравнивали быстродействие cut, awk, sed и perl'а - как выяснилось - sed самая медленная. Но perl слишком сложен для меня, а cut и awk работают только с полями, и часто их невозможно использовать (к примеру в этом случае это не получится). В bash'е так-же имеется возможность работы с текстом. Я её даже не рассматриваю, потому как даже для создания тестового файла в 85Мб потребовалось несколько минут, видимо для его обработки потребуется десятки минут, а может и часы.
</p></div><div class="blockquote"><blockquote class="blockquote"><p>
Вы можете обсудить этот документ на <a class="ulink" href="http://emulek.tk/forum/viewtopic.php?f=19&t=5026" target="_top">форуме</a>. Текст предоставляется по лицензии <a class="ulink" href="http://www.gnu.org/licenses/fdl.html" target="_top">GNU Free Documentation License</a> (<a class="ulink" href="http://forum.lorcode.org/viewtopic.php?f=15&t=30" target="_top">Перевод лицензии GFDL</a>).
</p><p>
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт <span class="command"><strong>41001666004238</strong></span> для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.
</p></blockquote></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch06s04.html">Пред.</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> </td></tr><tr><td width="40%" align="left" valign="top">GNU Расширения регулярных выражений. </td><td width="20%" align="center"><a accesskey="h" href="index.html">Начало</a></td><td width="40%" align="right" valign="top"> </td></tr></table></div></body></html>