Sed: modifica todas as repetições de primeira palavra para cada palavra no texto

3

Eu preciso fazer algo assim usando sed?

qq    ab xyz     ab qq aa ab 

Torna-se:

qq    ab xyz     +ab+ +qq+ aa +ab+
    
por mma 03.12.2014 / 15:45

2 respostas

4

Se sua entrada não contiver caracteres < , > nor + , você poderia fazer:

sed '
  s/[[:alnum:]]\{1,\}/<&>/g;:1
  s/\(<\([^>]*\)>.*\)<>/++/;t1
  s/[<>]//g'

Se possível, você sempre pode escapar deles:

sed '
  s/:/::/g;s/</:{/g;s/>/:}/g
  s/[[:alnum:]]\{1,\}/<&>/g;:1
  s/\(<\([^>]*\)>.*\)<>/++/;t1
  s/[<>]//g
  s/:}/>/g;s/:{/</g;s/::/:/g'

Supõem que você queira fazer isso independentemente em cada linha. Se você quiser fazer isso em todo o arquivo, você precisa carregar o arquivo inteiro na memória primeiro (note que algumas sed implementações têm limitações de tamanho):

sed '
  :2
  $!{N;b2
  }
  s/:/::/g;s/</:{/g;s/>/:}/g
  s/[[:alnum:]]\{1,\}/<&>/g;:1
  s/\(<\([^>]*\)>.*\)<>/++/;t1
  s/[<>]//g
  s/:}/>/g;s/:{/</g;s/::/:/g'

Isso será bastante ineficiente e será muito mais fácil com perl :

perl -pe 's/\w+/$seen{$&}++ ? "+$&+" : $&/ge'

Baseado em linhas:

perl -pe 'my %seen;s/\w+/$seen{$&}++ ? "+$&+" : $&/ge'
    
por 03.12.2014 / 16:06
1

Aqui está outra abordagem: isso usa alguns sed s:

an='[:alnum:]' esc=$(printf '3\[')
sed "/[${an}]/!d;=;a\ }
    s/.*/ & /;s/[^${an}]\{1,\}/   /g
    s| \([${an}"']\{1,\}\) | \
    s/\([^+'"${an}"']\)\(\)\([^+'"${an}"']\)/\1+\2+\3/2|g
' <text |
sed '/^ /!N;s/\n */{/' |
sed -e 's/.*/ & /;s/+/ & /g' \
    -f - \
    -e "s/ //;s/ $//
        s/+[^+ ]\{1,\}+/${esc}38;5;35m&${esc}0m/g
        s/ + /+/g" text

Basicamente, os dois primeiros sed s se unem para escrever um script para o terceiro. O primeiro sed limpa tudo menos caracteres alfanuméricos de cada linha - e pula toda a linha sem um. Para todos os grupos de caracteres que permanecem, ele escreve uma declaração de substituição de que o terceiro acabará por ler ( e interpretar como seu script.

O segundo sed é necessário porque o primeiro escreve seu script por linha - e cada linha pode ter várias instruções s/// . O primeiro imprime seu número de linha para cada linha que contém um alfanumérico, mas que precisa ser emparelhado em um contexto de função para o terceiro sed - e assim o segundo faz isso.

Aqui está uma amostra de como o script se parece:

...
43{
    s/\([^+[:alnum:]]\)\(n\)\([^+[:alnum:]]\)/++/2  
    s/\([^+[:alnum:]]\)\(N\)\([^+[:alnum:]]\)/++/2  
    s/\([^+[:alnum:]]\)\(G\)\([^+[:alnum:]]\)/++/2  
 }
44{
    s/\([^+[:alnum:]]\)\(b\)\([^+[:alnum:]]\)/++/2  
    s/\([^+[:alnum:]]\)\(block\)\([^+[:alnum:]]\)/++/2  
 }
45{
    s/\([^+[:alnum:]]\)\(END\)\([^+[:alnum:]]\)/++/2  
    s/\([^+[:alnum:]]\)\(SEDSCRIPT\)\([^+[:alnum:]]\)/++/2  
 }

Existe um 2 para cada s/// - isto porque cada substituição é direcionada para a segunda ocorrência de cada padrão - se uma segunda ocorrência não existir, nada é substituído. O acima é o resultado de executá-lo em outro dos meus scripts sed - parece bastante imune a caracteres especiais ou similares.

Como eu estava escrevendo, descobri que era mais fácil dizer o que estava acontecendo se eu colori sua seleção - que é o que ...

s/+[^+ ]\{1,\}+/${esc}38;5;35m&${esc}0m/g

... essa linha faz. Você pode comentar ou removê-lo sempre que você usa isso e não quer ou precisa dele.

Aqui está o script que ele escreve para seus dados de exemplo:

1{
    s/\([^+[:alnum:]]\)\(qq\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(ab\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(xyz\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(ab\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(qq\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(aa\)\([^+[:alnum:]]\)/++/2 
    s/\([^+[:alnum:]]\)\(ab\)\([^+[:alnum:]]\)/++/2
 }

Veja o que é impresso:

qq    ab xyz     +ab+ +qq+ aa +ab+

E de alguns dos meus sed script de antes:

        s/\(\(.\)${bs}\)\{1,\}/${esc}38;5;35m&${+esc+}0m/g
        s/\(_${bs}[^_]\)\{1,\}/${esc}38;5;75m&${+esc+}0m/g
        s/.${bs}//g
        s/\(\(${esc}\)0m[^m]*+m+[_ ]\{,+2+\}\)\{+2+\}/_/g
n;      /./!N;G
    
por 03.12.2014 / 21:31