sed: чтение всего файла в шаблонное пространство без сбоев при однострочном вводе

9

Чтение всего файла в шаблонное пространство полезно для замены строк новой строки, & c. и есть много случаев, рекомендующих следующее:

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

Однако, это терпит неудачу, если вход содержит только одну строку.

Например, при двухстрочном вводе каждая строка подвергается команде подстановки:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Но, с вводом одной линии, не замена не выполняется:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Как написать sedкоманду, чтобы прочитать все входные данные одновременно и не иметь этой проблемы?

dicktyr
источник
Я отредактировал ваш вопрос так, чтобы он содержал актуальный вопрос. Вы можете подождать других ответов, если хотите, но в конечном итоге пометьте лучший ответ как принятый (см. Кнопку перехода слева от ответа, прямо под кнопками со стрелкой вверх-вниз).
John1024
@ John1024 Спасибо, хорошо, что есть пример. Обнаружение такого рода вещей напоминает мне, что «все не так», но я рад, что некоторые из нас не сдаются. :}
dicktyr
2
Есть третий вариант! Используйте sed -zопцию GNU . Если ваш файл не имеет нулевого значения, он будет читать до конца файла! Найдено из этого: stackoverflow.com/a/30049447/582917
CMCDragonkai

Ответы:

13

Есть много причин, по которым чтение всего файла в пространство шаблонов может пойти не так. Логическая проблема в вопросе, касающемся последней строки, является обычной. Он связан 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 info 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, к пространству шаблона, используя встроенную \newline, чтобы отделить добавленный материал от исходного материала. Обратите внимание, что текущий номер строки изменяется.

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

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

: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оператор \nxecution заменяет разделитель ewline. Итак, где infoтекст GNU предполагает, что традиционная 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

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

Приведенная выше команда не рискует перебить ввод, потому что она просто делает несколько простых тестов, чтобы проверить, что она читает, когда она читает. Со Hстарым все строки добавляются в область удержания, но если строка соответствует, /foo/она перезаписывает hстарое пространство. Затем буферы xизменяются, и s///выполняется попытка условной замены, если содержимое буфера соответствует //последнему адресуемому шаблону. Другими словами, //s/\n/&/3pпопытка заменить третью новую строку в удерживающем пространстве на себя и вывести результаты, если удерживающее пространство в настоящее время совпадает /foo/. Если это tпрошло успешно, скрипт переходит на метку not delete - что делает look и оборачивает скрипт.

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

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

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

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

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


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

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

mikeserv
источник
1
Ставить безусловное на Hпервое место - это прекрасно.
до
@mikeserv Спасибо за ваш вклад. Я вижу потенциальную выгоду в сохранении цикла линии, но как это работает меньше?
dicktyr
@dicktyr хорошо, синтаксис использует несколько ярлыков, :a;$!{N;ba}как я уже упоминал выше - проще использовать стандартную форму в долгосрочной перспективе, когда вы пытаетесь запустить регулярные выражения в незнакомых системах. Но на самом деле это не то, что я имел в виду: вы реализуете замкнутый цикл - вы не можете так легко попасть в середину этого, когда захотите, как могли бы вместо этого, разветвив - обрезав ненужные данные - и позволив циклу произойти. Это как нисходящая вещь - все, что sedделает, является прямым результатом того, что он только что сделал. Может быть, вы видите это по-другому - но если вы попробуете это, вы обнаружите, что сценарий будет проще.
mikeserv
11

Это терпит неудачу, потому что Nкоманда прибывает перед соответствием шаблона $!(не последняя строка) и sed завершает работу перед выполнением любой работы:

N

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

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

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

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

  1. :a создать ярлык с именем «а»
  2. $! если не последняя строка, то
  3. Nдобавить следующую строку к пробелу шаблона (или выйти, если следующей строки нет) и baразветвить (перейти к) метку 'a'

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

sed 'H;1h;$!d;x; [commands...]'
dicktyr
источник
Я разместил это здесь, потому что я не нашел информацию в другом месте, и я хотел сделать ее доступной, чтобы другие могли избежать проблем с распространением :a;N;$!ba;.
dicktyr
Спасибо за публикацию! Помните, что принимать собственный ответ тоже хорошо. Вам просто нужно подождать некоторое время, прежде чем система позволит вам это сделать.
Тердон