Usando sed para editar qualquer uma das ocorrências de um padrão correspondente

6

Uma linha começando com Fred Flintstone deve ser anexada com alguma string. Procure a ocorrência especificada de Fred Flintstone e anexe-a.

Como eu uso este comando para qualquer uma das ocorrências de tal padrão? Eu tentei

sed '/Fred Flintstone/ s/$/ someString/2' filename

Aparentemente, o acima não está funcionando. Funciona bem em todas as ocorrências, mas não em uma ocorrência específica. (Digamos que eu queira substituir primeiro ou segundo ou terceiro [qualquer um deles])

Exemplo de arquivo1:

Fred Flintstone 
Johnson Stone
Fred Flintstone
Fred Flintstone
Michael Clark

Arquivo de saída desejado1:

Fred Flintstone 
Johnson Stone
Fred Flintstone someString
Fred Flintstone
Michael Clark
    
por DazzlerJay 17.05.2018 / 12:51

5 respostas

7

Embora você tenha mencionado sed , essas são uma espécie de awk -y tarefas:

awk -v pat="Fred Flintstone" '$0 ~ pat {count++;\
               if (count == 2) { $0 = $0" someString" ;} ;}; 1' file.txt
  • -v pat="Fred Flintstone" salva o padrão Regex para corresponder à variável pat a ser usada dentro de awk expressões

  • $0 ~ pat verifica o registro em relação a pat para uma correspondência; se for correspondida, a variável count será aumentada em 1 e se count for 2, o registro será redefinido como tendo o conteúdo atual mais someString ( {count++; if (count == 2) { $0 = $0" someString" ;} ;} )

  • 1 é uma expressão idiomática; como é truthy , todos os registros seriam impressos

Exemplo:

% cat file.txt
Fred Flintstone
Johnson Stone
Fred Flintstone
Fred Flintstone
Michael Clark

% awk -v pat="Fred Flintstone" '$0 ~ pat {count++; if (count == 2) { $0 = $0" someString" ;} ;}; 1' file.txt
Fred Flintstone
Johnson Stone
Fred Flintstone someString
Fred Flintstone
Michael Clark
    
por heemayl 17.05.2018 / 13:23
5

Este simples comando sed permite que você faça a mudança seletivamente sem usar loops (ele usa um branch-to-end) ou precisa de extensões GNU ou lê o arquivo inteiro de uma só vez:

sed -r '/Fred Flintstone/ {x; s/$/#/; /^#{2}$/ {x; s/.*/& someString/; b}; x}'

Explicação:

  • -r - use regex estendido
  • /Fred Flintstone/ - para linhas que correspondem a esse padrão:
    • x - troca espaço de padrão e mantém espaço (ative o contador)
    • s/$/#/ - adiciona um caractere ao contador
    • /^#{2}$/ - quando o contador tiver comprimento 2 (substitua qualquer valor)
      • x troca o espaço padrão e mantém espaço (ativa a linha de entrada contada)
      • s/.*/& someString/ - acrescenta a string à linha desejada
      • b - pula para o final do processamento desta linha para que possa ser impressa
    • x - troca o espaço do padrão e armazena o espaço (ativa as linhas que correspondem à string, mas não a contagem)

Níveis de recuo na explicação indicam níveis de aninhamento de chaves.

Todas as outras linhas passam sem processamento e são impressas.

    
por Dennis Williamson 18.05.2018 / 01:28
5

Resposta revisada para impedir a injeção de código em barras invertidas, bem como barras, citações ao usar variáveis awk awk -v variable ... ou um shell awk '...' variable="$value" desde que awk faz C Processamento de seqüência de escape em valores passados com -v variable= ou shell variable=$value ; então o shell variable='\n' mudará para \n dentro de awk ).

pattern="${patt//\/\\}" awk '
    $0 ~ ENVIRON["pattern"]{ seen++ } seen==2 { $0 = $0 "something"};1' infile

para a entrada abaixo e o padrão :

another break line
Johnson Stone
\"Fred //\Flintstone' SOMETHING will goes here ...
\"Fred //\Flintstone'
Michael Clark
O padrão

está em uma variável chamada patt

$ echo "$patt"
\"Fred //\Flintstone'

A saída é:

\"Fred //\Flintstone'
another break line
Johnson Stone
\"Fred //\Flintstone' SOMETHING will goes here ...something
\"Fred //\Flintstone'
Michael Clark

Explicação:

  • Este pattern="${patt//\/\\}" escapa de todas as barras invertidas na variável $patt , porque [ " the '~' operator does pattern matching, treating the right hand operand as an (extended) regular expression, and the left hand one as a string " por muru ], você precisará escapar todos os caracteres que são especiais nos EREs .

  • Este $0 ~ ENVIRON["pattern"]{ seen++ } verifica se a linha atual corresponde ao pattern e, em seguida, incrementa o valioso seen++ uma vez.

  • Este seen==2 { $0 = $0 "something"}' verifica se houve correspondência de segunda linha com o pattern no resultado acima (agora seen==2 ) e, em seguida, acrescenta a sequência "somestring" no final dessa linha.

  • A 1 (ou qualquer declaração True) no final está ativando a impressão padrão awk .

mais tarde, você precisará passar something string como uma variável, e você precisará usar algo como edit=$somesting e usar ENVIRON["edit"] .

pattern="${patt//\/\\}" edit="$something" awk '
    $0 ~ ENVIRON["pattern"]{ seen++ } seen==2 { $0 = $0 ENVIRON["edit"]};1' infile
    
por devWeek 17.05.2018 / 13:25
4

Aqui está uma maneira de fazer isso em sed sem incluir o arquivo inteiro na memória:

  • encontre a primeira ocorrência do padrão
  • anexe a próxima linha N ao espaço padrão
  • tentativa de substituir a ocorrência segundo do padrão
  • ramifique-se e anexe outra linha se a correspondência falhar Ta

O $q encerra o loop se o final do arquivo for atingido sem uma correspondência.

Então

sed '/Fred Flintstone/ {:a; $q; N; s//& someString/2; Ta;}' File1
Fred Flintstone 
Johnson Stone
Fred Flintstone someString
Fred Flintstone
Michael Clark

Embora T seja uma extensão GNU, você pode fazer o mesmo em POSIX sed usando t; ba

Hmm ... depois de pensar um pouco mais sobre isso, ele irá acrescentar o novo texto a todas as outras ocorrências - não apenas a segunda . Se você realmente precisa usar substituir apenas a segunda ocorrência, a única maneira que eu posso pensar em fazer isso seria:

  • enderece e substitua a primeira instância por alguma string exclusiva
  • enderece e modifique a nova primeira instância
  • enderece e substitua a sequência exclusiva pelo padrão original

GNU sed fornece um truque para resolver a primeira instância de um padrão

0,/pattern/

Então

sed -e '0,/Fred Flintstone/ s//Barney Rubble/' \
    -e '0,/Fred Flintstone/ s//& someString/' \
    -e '0,/Barney Rubble/ s//Fred Flintstone/
' File1
Fred Flintstone
Johnson Stone
Fred Flintstone someString
Fred Flintstone
Michael Clark

Se você não se importar em usar o ed, poderá abordar a segunda instância mais diretamente, indo para a primeira linha e, em seguida, correspondendo de uma instância para a próxima:

ed -s File1 << \EOF                                                                                                              
1;#
/Fred Flintstone/,/Fred Flintstone/ s//& someString/
,p
q
EOF
Fred Flintstone
Johnson Stone
Fred Flintstone someString
Fred Flintstone
Michael Clark

ou como um one-liner

printf '1;#\n/Fred Flintstone/,/Fred Flintstone/ s//& someString/\n,p\n' | ed -s File1

Você pode tornar a versão do ed in-place substituindo ,p por w

    
por steeldriver 17.05.2018 / 14:34
2

Outro caso de uso para ex , o editor de arquivos especificado por POSIX

(É o predecessor de vi e ainda faz parte de vi .)

printf '%s\n' '0/Fred Flintstone///s/$/ someString/' x | ex filename

A mágica aqui é que, ao contrário das ferramentas Sed, Awk e similares, ex não executa código em todas as linhas. Você pode mover o cursor, dar comandos, etc.

Neste caso, damos um número de linha 0 (que garante que Fred Flintstone na primeira linha será levado em conta), seguido por um regex /Fred Flintstone/ que se refere à primeira linha no arquivo correspondente regex, seguido por outro regex // que, sendo vazio, reutiliza o último regex e, portanto, se refere à linha segundo no arquivo que corresponde; e depois usamos o comando s que você já conhece.

O comando x significa salvar as alterações e sair.

Usamos printf para enviar comandos para ex .

    
por Wildcard 18.05.2018 / 06:00