Como adicionar uma linha após a ocorrência nth de uma palavra-chave usando sed?

3

Usando sed, quero adicionar check após a nth ocorrência

Entrada:

DCR
DCR
DCR

Saída:

DCR
DCR
check
DCR

É possível usar sed?

    
por Menon 21.04.2015 / 16:55

7 respostas

3

Com o GNU sed:

sed -z 's/DCR/&\ncheck/2' <input >output

Para versões não atualizadas:

sed '/DCR/{p;s/.*/1/;H;g;/^\(\n1\)\{2\}$/s//check/p;d}' <input >output

Se houver mais de 1 ocorrência DCR na linha:

sed '
/DCR/{p
      x                               # tests if already have met pattern
      /^\(\n\a\)\{2\}/!{              #+apropriate times and, if so, cancel
        x                             #+the rest of commands
        s/DCR/\a/g                    # exchange DCR by \a symbol
        s/^[^\a]*\|[^\a]*$//g         # delete everything before & after it  
        s/[^\a]\+/\n/g                # substitute everything between by \n
        H
        g
        /^\(\n\a\)\{2\}/s/.*/check/p} # add 'check' for double pattern
      d}' <input >output
    
por 21.04.2015 / 17:43
5

Com o GNU sed, você pode substituir o padrão n th em uma linha

$ echo "foofoofoofoo" | sed 's/foo/&\nbar/2'
foofoo
barfoofoo

Mas para a linha n th que contém o padrão, o awk é mais fácil:

awk -v n=2 -v patt=foo '{print} $0 ~ patt && ++count == n {print "bar"}' <<END
foo1
foo2
foo3
foo4
END
foo1
foo2
bar
foo3
foo4
    
por 21.04.2015 / 17:07
2

Eu não tenho uma resposta direta no sed. No awk, por outro lado, é fácil:

echo -e "DCR\nDCR\nDCR" |\
awk 'BEGIN {t=0}; { print }; /DCR/ { t++; if ( t==2) { print "check" } }'
    
por 21.04.2015 / 17:06
2

sed GNU

sed não é adequado para essa tarefa, mas é claro que você ainda pode fazer isso. Aqui está uma maneira de salvar uma string que é n long no espaço de espera e usa isso para contar o número de DCR ocorrências:

n=2

((yes | head -n$n | tr -d \n; echo); cat infile) | 
sed '
  1 {h;d}            # save counting string
  /DCR/ {            #
    x; s/.//; x      # n--
    T chk            # if n=0 goto "chk"
  }
  P;D 
  :chk               # insert check
  i\check
  :a; N; ba          # print rest of file
'

awk

Como notado por glenn , o awk é muito mais limpo, aqui está uma versão de golfe, mas uma lógica semelhante:

<infile awk '!n { print "check" } /DCR/ { n-- } 1' n=2
    
por 21.04.2015 / 17:19
2

Você pode fazer isso com sed em uma pilha ...

sed '/match$/N
     s/\n/&INSERT&/3;t
     $n;N;P;D'

Isso inseria INSERT após cada terceira ocorrência não sequencial de match na entrada. É a maneira mais eficiente que eu sei fazer isso com sed porque ele não tenta armazenar todas as linhas que ocorrem entre diferentes matches , nem requer trocas de buffer ou comparações back-ref , mas simplesmente incrementa o único meio de contagem de sed - seu número de linha através de seu ciclo de linha.

Há alguma sobrecarga adicionada, é claro - com cada espaço de padrão de jogo ficando um pouco maior - mas ainda é o mesmo fluxo, e não há back-tracking. É apenas primeiro a entrar, primeiro a sair - o que, na minha opinião, é um método muito adequado para sed . De fato, em vez de voltar para verificar se há uma correspondência, sed pode avançar mais à frente para cada partida. Estou um pouco orgulhosa disso e não sei porque nunca pensei nisso antes.

A versão acima, no entanto, iria apertar repetições até certo ponto, porque funciona apenas uma linha atrás da entrada. E a solução para isso é avançar ainda mais e requer apenas um pouco de complexidade adicional na forma de um loop de curto-circuito b ranch :l abel dentro do loop N;P;D para mantê-lo atualizado.

Funciona assim:

seq 100000| sed -ne':n
            s/\n/&\tCheck&\t/5p;t
            N;/0000$/bn'  -eD

... que, para mim, imprime ...

49995
49996
49997
49998
49999
    Check
    50000
99995
99996
99997
99998
99999
    Check
    100000

Veja, para manter a contagem, ele incrementa seu buffer de linha para cada ocorrência de match e insere outra linha em sua janela deslizante no espaço padrão. Dessa forma, tudo o que é necessário para verificar se a correspondência foi encontrada é tentar substituir o caractere de ewline s/// nth \n no espaço padrão. Se isso puder ser feito, encontraremos n matches até o momento, e t est poderá desvincular-nos da iteração atual e limpar o incremento completamente.

No exemplo acima, o buffer é incrementado uma vez para cada padrão de espaço que termina com a string 0000 . Quando 5 desses são encontrados, sed imprime o espaço de padrão atual - e todo o buffer - e limpa o contador.

Para você:

printf DCR\n| tee - - - - - |
sed -e:n -e's/\n/&\tCheck&\t/2;t
     $n;    N;/DCR$/bn' -eP\;D

DCR
DCR
    Check
    DCR
DCR
DCR
    Check
    DCR

Agora, se você quiser marcar apenas a ocorrência nth , também é fácil:

printf DCR\n        |
tee - - - - - - - - -|
sed -e:n -e's/\n/&\tCheck&\t/3;te
     $n;  N;/DCR$/bn' -e'P;D;:e
     n;be'

... se você realmente olhar para isso, pode ocorrer a você que nós apenas mal arranhamos a superfície aqui ...

DCR
DCR
DCR
    Check
    DCR
DCR
DCR
DCR
DCR
DCR
DCR
    
por 29.06.2015 / 06:07
0
    sed '2 a\
    check
    ' file

Anexe após a linha 2 com uma nova linha e adicione a palavra "check" com outra nova linha e imprima o arquivo inteiro como padrão.

    
por 21.04.2015 / 17:07
-1

A solução AWK é muito fácil de ler para este tipo de tarefas, aqui é apenas uma correção para a solução do steviethecat (o; não funciona para o awk, precisa substituí-lo por uma nova linha):

echo -e "DCR\nDCR\nDCR" | awk 'BEGIN {t=0}

{ print }

/DCR/ { t++; if ( t==2) { print "check" } }'
    
por 29.06.2015 / 02:46

Tags