сопоставить повторяющийся символ в sed на mac

Я пытаюсь найти все экземпляры из 3 или более новых строк и заменить их только двумя новыми строками (представьте себе файл, в котором слишком много пробелов). Я использую sed, но согласен с ответом, использующим awk или тому подобное, если это проще.

примечание: у меня Mac, поэтому sed немного отличается от Linux (BSD против GNU)

Моя настоящая цель - новые строки, но я не могу заставить ее работать вообще, поэтому для простоты я пытаюсь сопоставить 3 или более повторений bla и заменить их на BLA.

Создайте пример файла с именем глупо.txt:

$ cat stupid.txt

blablabla
$

Насколько я понимаю, вы сопоставляете i или больше вещей, используя синтаксис регулярного выражения thing{i,}.
Я пробовал варианты этого, чтобы сопоставить 3 bla, но безуспешно:

cat stupid.txt | sed 's/bla{3,}/BLA/g'      # simplest way
cat stupid.txt | sed 's/bla\{3,\}/BLA/g'    # escape curly brackets
cat stupid.txt | sed -E 's/bla{3,}/BLA/g'   # use extended regular expressions
cat stupid.txt | sed -E 's/bla\{3,\}/BLA/g' # use -E and escape brackets

Теперь у меня нет идей, что еще попробовать!


person rrr    schedule 01.05.2018    source источник


Ответы (4)


Если приемлем весь файл:

perl -0777pe 's/(\n){3,}/\n\n/g' newlines.txt

Где вы должны заменить \n на любую подходящую последовательность новой строки.

-0777 указывает Perl не разбивать каждую строку на отдельную запись, что позволяет работать регулярному выражению, работающему между строками.

Если вы удовлетворены результатом, -i заставляет perl заменить файл на месте, а не выводить на стандартный вывод:

perl -i -0777pe 's/(\n){3,}/\n\n/g' newlines.txt

Вы также можете сделать так: -i~ для создания файла резервной копии с заданным суффиксом (в данном случае ~).

Если глотание всего файла неприемлемо:

perl -ne 'if (/^$/) {$i++}else{$i=0}print if $i<3' newlines.txt

Это печатает любую строку, которая не является третьей (или выше) последовательной пустой строкой. -i работает с этим так же.

ps -- MacOS поставляется с установленным Perl.

person zzxyz    schedule 01.05.2018
comment
Я не уверен, что означает прихлебывать вне контекста вкусной тарелки с лапшой... но это сработало! Чтобы заменить много пустых строк только двумя, я заменил, как вы предложили: perl -0777pe 's/(\n){3,}/\n\n\n/g' - person rrr; 15.07.2018
comment
:-) slurp на самом деле означает всасывать весь файл за один раз в память, как лапшу. Это может быть отличный метод, как здесь, но иногда это не лучший подход к большим файлам, где предпочтительным является подход на основе потока, например sed редактор потока. - person Mark Setchell; 15.07.2018

thing{3,} соответствует thinggg. Используйте (..) для группировки вещей, чтобы квантификатор применялся к тому, что вы хотите:

$ echo blablabla | sed -E 's/(bla){3}/BLA/g'
BLA
person that other guy    schedule 01.05.2018

sed -E 's/bla{3,}/BLA/g' 

Вышеприведенное соответствует bl, за которым следует три или более повторений a. Это не то, что вы хотите. Похоже, вы действительно хотите три или более повторений bla. Если это так, то замените:

$ sed -E 's/bla{3,}/BLA/g' stupid.txt
blablabla

С:

$ sed -E 's/(bla){3,}/BLA/g' stupid.txt
BLA

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

Замена новых строк

Давайте рассмотрим этот файл, в котором есть 3 символа новой строки между 1 и 2:

$ cat file.txt

1



3

Чтобы заменить любое появление трех или более новых строк одной новой строкой:

$ sed -E 'H;1h;$!d;x; s/\n{3,}/\n/g' file.txt

1
3

Как это устроено:

  • H;1h;$!d;x

    Эта сложная последовательность команд считывает весь файл. Вероятно, проще всего думать об этом как об идиоме. Если вы действительно хотите знать кровавые подробности:

    • H - Append current line to hold space
    • 1h - Если это первая строка, перезаписать ею пробел
    • $!d - Если это не последняя строка, удалить шаблон и перейти к следующей строке.
    • x — Обмен местами хранения и шаблона, чтобы поместить весь файл в пространство шаблона
  • s/\n{3,}/\n/g

    Это заменяет все последовательности из трех или более новых строк одной новой строкой.

Альтернативный

Приведенное выше решение считывает весь файл сразу. Для больших (гигабайтных) файлов это может быть недостатком. Этот альтернативный подход позволяет избежать этого:

$ sed -E '/^$/{:a; N; /\n$/ba; s/\n{3,}([^\n]*)/\1/}' file.txt # GNU only

1
3

Как это устроено:

  • /^$/{...}

    Это выбирает пустые строки. Для пустых строк и только пустых строк выполняются команды в фигурных скобках, а именно:

  • :a

    Это определяет метку a.

  • N

    Это считывает следующую строку из файла в пространство шаблонов, отделяя от предыдущей новой строкой.

  • /\n$/ba

    Если последняя прочитанная строка пуста, выполните переход (переход) к метке a.

  • s/\n{3,}([^\n]*)/\1/

    Если мы не переходили, то выполняется эта замена, которая удаляет лишние символы новой строки.

Версия BSD: у меня нет системы BSD, чтобы протестировать это, но я предполагаю:

sed -E -e '/^$/{:a' -e N -e '/\n$/ba' -e 's/\n{3,}([^\n]*)/\1/}' file.txt
person John1024    schedule 01.05.2018
comment
Вместо всех этих команд sed в случае новых строк кто-то может использовать только sed -z, если во входном файле нет нулевых символов внутри. - person George Vasiliou; 02.05.2018
comment
@GeorgeVasiliou Это верно для всех, кто работает в Linux или иным образом использует GNU sed. Однако ОП сказал, что он был на Mac. - person John1024; 02.05.2018
comment
Когда я выполнил ваше первое предложение, новые строки были правильно идентифицированы, но, как ни странно, они были заменены на n вместо фактической новой строки. поэтому вывод из вашего примера был 1n3. Это кажется странным, поскольку он смог правильно интерпретировать первый \n. - person rrr; 15.07.2018
comment
Я также попробовал вторую версию BSD, но получил эту ошибку: sed: 1: "s/\n{3,}([^\n]*)/\1/} ": bad flag in substitute command: '}', так что, возможно, есть какая-то опечатка. Однако, когда я сгенерирую файл большего размера, он, скорее всего, будет на сервере Linux, поэтому пример GNU приветствуется! - person rrr; 15.07.2018

Чтобы сохранить только 2 новые строки, вы можете попробовать этот sed

sed '
  /^$/!b
  N
  /../b
  h
  :A
  y/\n/@/
  /^@$/!bB
  s/@//
  $bB
  N
  bA
  :B
  s/^@//
  /./ {
    x
    G
    b
  }
  g
' infile

/^$/!b Если это пустая строка, не печатать ее

N получить новую строку

/../b, если эта новая строка не пуста, вывести 2 строки

h сохранить 2 пустые строки в буфере хранения

: Метка А

На данный момент в буфере паттернов всегда есть 2 строки, и первая пуста.

y/\n/@/ замените \n на @ (вы можете выбрать другой символ, которого нет в вашем файле)

/^@$/!bB Если вторая строка не пуста, перейти к B

s/@// удалить @

$bB Если это последняя строка, перейти к B

На данный момент в пространстве шаблонов есть 1 пустая строка.

N получить последнюю строку

bA перейти к A

:B метка B

s/^@// убираем @ в начале строки

/./ { Если последняя строка не пуста

x обмен шаблонами и удержание буфера

G добавить буфер хранения в пространство шаблонов

б перейти к концу

}

g замените пространство шаблона (пустое) пространством удержания

распечатать пространство шаблона

person ctac_    schedule 02.05.2018
comment
В основном это работало, но по какой-то причине последняя строка текста удалена. Я сделал немного более длинный файл примера, чем в исходном вопросе, который состоял из нескольких строк текста, разделенных переменным количеством новых строк. И это сработало, за исключением того, что последняя строка текста также была удалена. - person rrr; 15.07.2018
comment
@rrr обновить ответ, чтобы сохранить последнюю строку, если она не пуста. - person ctac_; 15.07.2018