sed: como adicionar texto às primeiras x ocorrências

sed: como adicionar texto às primeiras x ocorrências

Estou tentando adicionar texto ao final de uma linha nas primeiras x vezes que isso ocorre. Eu sei como fazer isso globalmente e para a ocorrência n. Não consigo descobrir como fazer isso nas primeiras enésimas ocorrências. Um exemplo seria um arquivo text.txt que contém:

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

E quero adicionar um '.' no final das três primeiras vezes que ocorre "Isto é um teste". A saída que estou tentando obter é:

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

Responder1

This.*testé o regex correto. O asterisco significa "0 ou mais vezes o caractere anterior", portanto This*testnão corresponderia a nenhuma de suas linhas.

Agora, Sed é ruim em aritmética. Para algo elegante sugiro Awk:

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

Acho que basta dizer que c, como qualquer variável não definida no Awk, é tratada como zero, mas deixe-me saber se precisar de mais esclarecimentos.

Responder2

Outra variante que evita fazer a correspondência de regexp após todas as 3 ocorrências já terem sido encontradas:

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

Especificamente sed, você poderia fazer algo como:

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

Onde rastreamos o número de .s para anexar como número correspondente de .s no espaço de espera.

Nem é preciso dizer que sedé muito menos apropriado para esse tipo de tarefa. Se a razão para querer sedfor a -iextensão para edição no local encontrada em algumas implementações (emprestada de perl), observe que a implementação GNU de awktambém pode fazer isso com -i /usr/share/awk/inplace.awk¹, ou você pode usar a versão real:

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

Se você quisesse adicionar um .após cada ocorrência de This is a testem vez de todas as linhas que contêm pelo menos uma ocorrência de This is a test, perltambém seria a melhor escolha:

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

¹não use-i inplaceas gawktenta carregar primeiro a inplaceextensão (como inplaceou inplace.awk) do diretório de trabalho atual, onde alguém poderia ter plantado malware. O caminho da inplaceextensão fornecida gawkpode variar de acordo com o sistema, consulte a saída degawk 'BEGIN{print ENVIRON["AWKPATH"]}'

Responder3

Com perlpoderíamos fazer como mostrado

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

Os elefantes também podem dançar, embora em passos simples. Usando GNU seda escrita em seu modo regex estendido, -E podemos armazenar a contagem como o número de novas linhas na espera.

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

informação relacionada