sed: как добавить текст к первым x вхождениям

sed: как добавить текст к первым x вхождениям

Я пытаюсь добавить текст в конец строки в первые x раз, когда он встречается. Я знаю, как это сделать глобально и для n-го вхождения. Я не могу понять, как это сделать для первых n-х вхождений. Примером может служить файл text.txt, который содержит:

This is a test
junk
This is a test
More junk
This is a test
This is a test
This is a test

И я хочу добавить '.' в конце первых трех раз, когда встречается "This is a test". Вывод, который я пытаюсь получить, следующий:

This is a test.
junk
This is a test.
More junk
This is a test.
This is a test
This is a test

решение1

This.*testявляется правильным регулярным выражением. Звездочка означает "0 или более раз предыдущий символ", поэтому This*testне будет соответствовать ни одной из ваших строк.

Теперь, Sed плох в арифметике. Для чего-то элегантного я предлагаю Awk:

awk '/This.*test/{c++};{print $0 (c<4 ? "." : "")}' file

Думаю, достаточно сказать, что c, как и любая неустановленная переменная в Awk, она рассматривается как ноль, но дайте мне знать, если вам потребуются дополнительные разъяснения.

решение2

Другой вариант, который избегает выполнения сопоставления с регулярным выражением после того, как все 3 вхождения уже найдены:

awk -v n=3 'n && /This is a test/ {n--; $0 = $0 "."}; {print}'

В sedчастности, вы могли бы сделать что-то вроде:

sed '
  1 {
    x
    s/^/.../
    x
  }
  /This is a test/ {
    s/$/./
    x
    s/.//
    /./ {
      x
      b
    }
    g
    :1
    $! {
      n
      b 1
    }
  }'

Где мы отслеживаем количество .добавляемых s как соответствующее количество .s в удерживаемом пространстве.

Само собой разумеется, что sedэто гораздо менее подходит для такого рода задач. Если причина желания sedзаключается в -iрасширении для редактирования на месте, найденном в нескольких реализациях (заимствованном из perl), обратите внимание, что реализация GNU awkтакже может делать это с -i /usr/share/awk/inplace.awk¹, или вы можете использовать настоящую вещь:

perl -lpi -e '
  if ($n < 3 && /This is a test/) {
    $n++;
    $_ .= ".";
  }' your-file

Если вы хотите добавить .после каждого вхождения , This is a testа не ко всем строкам, которые содержат хотя бы одно вхождение This is a test, perlтакже будет лучшим выбором:

perl -pi -e 's{This is a test\K}{$n++ < 3 ? "." : ""}ge' your-file

¹не использовать-i inplaceas сначала gawkпытается загрузить inplaceрасширение (как inplaceили inplace.awk) из текущего рабочего каталога, где кто-то мог разместить вредоносное ПО. Путь расширения, inplaceпоставляемого с, gawkможет различаться в зависимости от системы, см. выводgawk 'BEGIN{print ENVIRON["AWKPATH"]}'

решение3

С perlмы могли бы сделать как показано

perl -lpe '
  $_ = $k == 3 ? next : s/This is a test(?{$k++}).*\K/./r;
' file

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

K=3
sed -Ee '
  /This is a test/!b
  G
  /(.*\n){'"$K"'}.*\n/!{
    s/\n+/./p;z;H;d
  }
  s/\n+//
  :a;n;ba
' file

Связанный контент