sed: считайте целый файл в пространство шаблона, не перестав работать на однострочном входе

Проверьте свои настройки выделенного дискового пространства согласно следующему официальному Debian HowTo.

9
01.02.2015, 10:26
3 ответа

Существует множество причин, по которым чтение всего файла в пространство шаблонов может пойти не так. Логическая проблема в вопросе, окружающем последнюю строку, является общей. Это связано со строковым циклом sed - когда строк больше нет и sed встречает EOF, он проходит - обработка прекращается. Итак, если вы находитесь на последней строчке и даете команду sed получить другую, она остановится прямо здесь и больше ничего не сделает.

Тем не менее, если вам действительно нужно прочитать весь файл в пространстве шаблонов, то, вероятно, в любом случае стоит подумать о другом инструменте. Дело в том, что sed одноименный редактор потока - он предназначен для работы с строкой или логическим блоком данных - за раз.

Есть много подобных инструментов, которые лучше приспособлены для обработки полных файловых блоков. ed и ex , например, могут делать многое из того, что может sed , и с аналогичным синтаксисом - и многое другое - но вместо того, чтобы работать только с входной поток, преобразуя его в выходной, как это делает sed , они также поддерживают временные файлы резервных копий в файловой системе. Их работа по мере необходимости буферизуется на диск, и они не завершаются внезапно в конце файла (и имеют тенденцию намного реже взламываться при нагрузке на буфер) . Более того, они предлагают множество полезных функций, которых нет в sed - из тех, которые просто не имеют смысла в контексте потока - таких как отметки строк, отмена, именованные буферы, соединение и многое другое. Основное преимущество

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

Что касается последнего пункта, кстати: хотя я понимаю, что пример s / a / A / g , скорее всего, всего лишь наивный пример и, вероятно, не тот сценарий, который вы хотите собрать в вход для, возможно, вам стоит ознакомиться с y /// .Если вы часто обнаруживаете, что g локально заменяете один символ другим, то y может быть очень полезным для вас. Это преобразование, в отличие от подстановки, и оно намного быстрее, поскольку не подразумевает регулярное выражение. Этот последний момент также может быть полезен при попытке сохранить и повторить пустые адреса // , потому что это не влияет на них, но может быть затронуто ими. В любом случае, y / a / A / является более простым средством достижения того же самого - и возможны также свопы, например: y / aA / Aa / , который поменял бы все верхний / нижний регистр, как в строке друг для друга.

Вы также должны заметить, что описываемое вами поведение на самом деле не является тем, что должно происходить в любом случае.

Из информации GNU sed в разделе ОБЫЧНО СООБЩАЕМЫЕ ОБ ОШИБКАХ :

  • N команда в последней строке

    • Большинство версий sed выйти без печати, когда команда N введена в последней строке файла. GNU sed печатает пространство шаблонов перед выходом, если, конечно, не указан командный переключатель -n . Этот выбор сделан намеренно.

    • Например, поведение sed N foo bar будет зависеть от того, четное или нечетное количество строк в foo.Или при написании сценария для чтения следующих нескольких строк после сопоставления с образцом традиционные реализации sed заставят вас написать что-то вроде / foo / {$! N; $! N; $! N; $! N; $! N; $! N; $! N; $! N; $! N} вместо просто / foo / {N; N; N; N; N; N; N; N; N; } .

    • В любом случае, самый простой обходной путь - использовать $ d; N в сценариях, основанных на традиционном поведении, или установить для переменной POSIXLY_CORRECT непустое значение. .

Переменная среды POSIXLY_CORRECT упоминается, потому что POSIX указывает, что если sed встречает EOF при попытке N , он должен выйти без вывода,но версия GNU в этом случае намеренно порывает со стандартом. Также обратите внимание, что даже если поведение оправдано выше, предполагается, что случай ошибки связан с потоковым редактированием, а не с захватом всего файла в память.

Стандарт определяет поведение N следующим образом:

  • N

    • Добавить следующую строку ввода за вычетом ее завершающей \ n ewline , в пространство шаблона, используя встроенную \ n прямую линию, чтобы отделить добавленный материал от исходного материала. Обратите внимание, что текущий номер строки изменится.

    • Если следующая строка ввода недоступна, команда N должна перейти в конец сценария и завершить работу без запуска нового цикла или копирования пространства шаблонов в стандартный вывод.

В связи с этим есть некоторые другие GNU-измы, продемонстрированные в вопросе - в частности, использование метки : , b ранчо и { скобки контекстной функции } . Как показывает практика, любая команда sed , которая принимает произвольный параметр, понимается как ограничивающая в сценарии \ n ewline. Таким образом, все команды ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

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

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

То же самое верно для r , w , t , a , i и c (и, возможно, еще несколько, о которых я сейчас забыл) .Почти в каждом случае они также могут быть записаны:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... где новый оператор -e xecution заменяет разделитель строки \ n . Итак, если текст GNU info предлагает традиционную реализацию sed , вы должны будете сделать :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... скорее, это должно быть ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

] ... конечно, это тоже неправда. Писать сценарий таким образом немного глупо. Есть гораздо более простые способы сделать то же самое, например:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... который печатает:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... потому что команда t est - как и большинство команд sed - зависит от линейного цикла для обновления своего регистра возврата, и здесь линейному циклу разрешается выполнять большую часть работы. Это еще один компромисс, который вы делаете, когда проглатываете файл - цикл строки больше не обновляется, и многие тесты будут вести себя ненормально.

Вышеупомянутая команда не рискует выйти за пределы ввода, потому что она просто выполняет несколько простых тестов, чтобы проверить, что она читает, когда читает. С H old все строки добавляются в удерживаемое пространство, но если строка соответствует / foo / , она перезаписывает h старое пространство. Затем буферы изменяются e x , и выполняется попытка условной замены s /// , если содержимое буфера совпадает с последним адресованным шаблоном // . Другими словами, // s / \ n / & / 3p пытается заменить третью новую строку в удерживаемом пространстве на себя и распечатать результаты , если удерживаемое пространство в настоящее время совпадает с / foo / .Если это t будет успешным, скрипт перейдет к метке n ot d elete - что делает l ook и завершает сценарий .

В случае, если и / foo / , и третья новая строка не могут быть сопоставлены вместе в удерживаемом пространстве, тогда //! G перезапишет буфер, если / foo / не соответствует, или, если он совпадает, он перезапишет буфер, если \ n ewline не соответствует (тем самым заменяя / foo / с собой) . Этот небольшой тонкий тест предотвращает ненужное заполнение буфера на длинных отрезках no / foo / и гарантирует, что процесс остается быстрым, потому что ввод не накапливается. В случае отказа no / foo / или // s / \ n / & / 3p буферы снова меняются местами, и каждая строка, кроме последней, там удаляется.

Эта последняя - последняя строка $! D - это простая демонстрация того, как нисходящий sed скрипт можно заставить легко обрабатывать несколько случаев. Когда ваш общий метод состоит в том, чтобы отсечь нежелательные случаи, начиная с самых общих и работая над наиболее конкретными, тогда крайние случаи могут быть более легко обработаны, потому что им просто разрешено пропадать до конца скрипта с другими вашими требуемыми данными и когда все это завершает вас, у вас остаются только те данные, которые вам нужны. Однако необходимость извлекать такие крайние случаи из замкнутого цикла может быть намного сложнее.

И вот последнее, что я должен сказать: если вы действительно должны вытащить весь файл, тогда вы можете сделать немного меньше работы, полагаясь на линейный цикл, который сделает это за вас. Обычно вы должны использовать N ext и n ext для просмотра вперед -потому что они продвигаются вперед линейного цикла. Вместо избыточной реализации замкнутого цикла внутри цикла - поскольку линейный цикл sed в любом случае является простым циклом чтения - если ваша цель состоит только в беспорядочном сборе входных данных, то, вероятно, это проще сделать:

sed 'H;1h;$!d;x;...'

... который соберет весь файл или провалит попытку.


примечание о N и поведении последней строки ...

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

12
27.01.2020, 20:04

Это не удается, потому что команда n приходит перед матчем шаблона $! (не последняя строка) и SED-Quits, прежде чем делать любую работу:

n

Добавьте новую строку в пространство шаблона, затем добавьте следующую строку ввода в пространство шаблона. Если больше нет ввода, а затем SED выходит без обработки больше команд .

Это может быть легко закреплено для работы с однострочным входом, а также (и, действительно, более четко в любом случае), просто группируя команды n и B после Узор:

sed ':a;$!{N;ba}; [commands...]'

Это работает следующим образом:

  1. : A Создайте ярлык с именем «A»
  2. $! Если не последняя строка, то
  3. n добавляют следующую строку на шаблон (или выйти, если нет следующей строки) И BA BA ветвь (перейдите к) метку «A»

, к сожалению, это не портативно (поскольку он опирается на расширения GNU), но следующая альтернатива (предложенная @Mikeserv) является портативным:

sed 'H;1h;$!d;x; [commands...]'
11
27.01.2020, 20:04

Как исчерпывающе объясняет @mikeserv, N не подходит для этого.

Этот фрагмент будет аккумулировать весь файл, и вы сможете использовать его как префикс к остальной части скрипта:

H;$!d;x;s/^\n//

Он использует H для накопления файла, пока не будет прочитана последняя строка

Пример использования *протестирован с GNU sed ), обратите внимание на отсутствие завершающего \n:

$ printf 'a\nb\nc' | sed -e 'H;$!d;x;s/^\n//' -e 's/^/[/;s/$/]/'
[a
b
c]$ echo $?
0
0
21.09.2021, 09:10

Теги

Похожие вопросы