Unir linhas de texto com início repetido

6

Eu tenho um arquivo de texto longo (um arquivo de guia para editor de stardict) que consiste em linhas no seguinte formato:

word1  some text
word1  some other text
word2  more text
word3  even more

e gostaria de convertê-lo para

word1  some text<br>some other text
word2  more text
word3  even more

Isso significa que as linhas subseqüentes (o arquivo é classificado) que começam com a mesma palavra devem ser mescladas com uma única (aqui as definições são separadas por <br> ). Linhas com início igual também podem aparecer com mais freqüência do que apenas duas vezes. O caractere que separa palavra e definição é um caractere de tabulação e é exclusivo em cada linha. word1 , word2 , word3 são obviamente marcadores para algo arbitrário (exceto caracteres tab e newline) que eu não conheço antecipadamente.

Eu posso pensar em uma parte mais longa do código Perl que faz isso, mas me pergunto se existe uma solução curta em Perl ou algo para a linha de comando. Alguma idéia?

    
por highsciguy 01.04.2015 / 10:26

6 respostas

3

Este é um procedimento padrão para awk

awk '
{
  k=$2
  for (i=3;i<=NF;i++)
    k=k " " $i
  if (! a[$1])
    a[$1]=k
  else
    a[$1]=a[$1] "<br>" k
}
END{
  for (i in a)
    print i "\t" a[i]
}' long.text.file

Se o arquivo for classificado pela primeira palavra na linha, o script pode ser mais simples

awk '
{
  if($1==k)
    printf("%s","<br>")
  else {
    if(NR!=1)
      print ""
    printf("%s\t",$1)
  }
  for(i=2;i<NF;i++)
    printf("%s ",$i)
  printf("%s",$NF)
  k=$1
}
END{
print ""
}' long.text.file

Ou apenas bash

unset n
while read -r word definition
do
    if [ "$last" = "$word" ]
    then
        printf "<br>%s" "$definition"
    else 
        if [ "$n" ]
        then
            echo
        else
            n=1
        fi
        printf "%s\t%s" "$word" "$definition"
        last="$word"
     fi
done < long.text.file
echo
    
por 01.04.2015 / 10:50
3

com sed :

sed '$!N;/^\([^\t]*\t\)\(.*\)\(\n\)/!P;s//<br>/;D' <<\IN
word1  some text
word1  some other text
word1  some other other text
word2  more text
word3  even more
word3  and still more
IN

(nota: com muitos sed s, o escape \t acima é inválido e um caractere% <tab> literal deve ser usado em seu lugar)

E se você tiver o GNU sed , pode escrevê-lo um pouco mais fácil:

sed -E '$!N;/^(\S+\t)(.*)\n/!P;s//\n<br>/;D' <infile

Funciona empilhando gradualmente a entrada à medida que é lida. Se duas linhas consecutivas não começarem com a mesma cadeia de espaço não, então a primeira delas será P rinted. Caso contrário, a nova linha interveniente é realocada para o início da linha e a sequência correspondida imediatamente a seguir (para incluir a guia) é substituída pela string <br> .

Observe que o método empilhamento usado aqui poderia ter implicações de desempenho se a linha que sed monta cresce muito. Se crescer mais do que 8kb, excederá o tamanho mínimo do espaço de padrão especificado pelo POSIX.

Independentemente de qual das duas possibilidades ocorreu, o último de todos os sed D é o primeiro caractere \n ewline a aparecer no espaço padrão e recomeça com o que resta. E assim, quando duas linhas consecutivas não começam com sequências idênticas, a primeira é impressa e excluída, senão a substituição é executada e o D elete exclui apenas o \n ewline que anteriormente os separava.

E assim o comando acima imprime:

word1  some text<br>some other text<br>some other other text
word2  more text
word3  even more<br>and still more

Eu usei um <<\HERE_DOC para a entrada acima, mas você provavelmente deve largar tudo de <<\IN e usar </path/to/infile .

    
por 01.04.2015 / 12:43
2

Isso é de fato padrão para awk . Aqui está uma solução concisa que não altera os dados operacionais:

awk 'BEGIN { FS="\t" }
     $1!=key { if (key!="") print out ; key=$1 ; out=$0 ; next }
     { out=out"<br>"$2 }
     END { print out }'
    
por 01.04.2015 / 11:26
2
perl -p0E 'while(s/^((.+?)\t.*)\n\t/$1<br>/gm){}' 

(Demora 2s para processar um dicionário de 23MB, 1.5Mlines, no meu laptop de 6 anos de idade)

    
por 01.04.2015 / 12:29
1

Em python:

import sys

def join(file_name, join_text):
    prefix = None
    current_line = ''
    for line in open(file_name):
        if line and line[-1] == '\n':
            line = line[:-1]
        try:
            first_word, rest = line.split('\t', 1)
        except:
            first_word = None  # empty line or one without tab
            rest = line
        if first_word == prefix:
            current_line += join_text + rest
        else:
            if current_line:
                print current_line
            current_line = line
            prefix = first_word

    if current_line:  # do the last line(s)
        print current_line


join(sys.argv[2], sys.argv[1])

Isso espera que o separador ( <br> ) como o primeiro argumento para o programa e o nome do arquivo como o segundo argumento

    
por 01.04.2015 / 10:58
-1

tente

awk 'BEGIN { before="" } 
{ if ( $1 == before ) { $1="" ; printf "<br>%s",$0 ; } 
  else { printf "\n%s",$0 ;} ; before=$1 ; } 
END { printf "\n"  ;}'

que dão com sua entrada

word1  some text<br> some other text
word2  more text
word3  even more

tha awk basicamente lembra a primeira palavra na linha anterior e não imprime nova linha.

    
por 01.04.2015 / 11:24