Concatenar líneas por primera columna por awk o sed

12

¿Cómo puedo usar awken la siguiente situación?

Quiero concatenar líneas que comienzan con la misma columna. Sólo la primera columna se mantiene después de la unión (en este caso aaa, www, hhh).

El archivo puede estar separado por espacios o tabulaciones.

Entrada de ejemplo:

aaa bbb ccc ddd NULL NULL NULL
aaa NULL NULL NULL NULL NULL NULL
aaa bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy
hhh 111 333 yyy ooo hyy NULL

Salida deseada:

aaa bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL

El trasfondo de esto es que quiero configurar una base de datos muy simple basada en archivos, donde la primera columna es siempre el identificador de la entidad. Todas las líneas basadas en la misma columna de identificador están concatenadas.

minúsculo
fuente
1
¿De dónde vino la uuulínea (en la salida)?
saeedn
Perdón, es mi culpa. Lo editaré
pequeño

Respuestas:

8

Para obtener las primeras columnas en cada línea usando awk, puede hacer lo siguiente:

< testfile awk '{print $1}'
aaa
aaa
aaa
www
hhh
hhh

Estas son sus claves para el resto de las líneas. Por lo tanto, puede crear una tabla hash, utilizando la primera columna como clave y la segunda columna de la línea como valor:

< testfile awk '{table[$1]=table[$1] $2;} END {for (key in table) print key " => " table[key];}'
www => yyy
aaa => bbbNULLbbb
hhh => 111111

Para obtener el resto de la línea, comenzando con la columna 2, debe recopilar todas las columnas:

< testfile awk '{line="";for (i = 2; i <= NF; i++) line = line $i " "; table[$1]=table[$1] line;} END {for (key in table) print key " => " table[key];}'
www => yyy hhh NULL NULL NULL NULL 
aaa => bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc    NULL NULL NULL NULL 
hhh => 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL 
binfalse
fuente
Hola, sí, realmente necesitaba un desglose de las tablas hash. ¡Gracias!
pequeño
2
@tiny: suponía que era necesario preservar el pedido. ¿No es este el caso (esta respuesta produce un orden correspondiente al mecanismo de hash, no su orden original)?
ire_and_curses
3

Alguien más puede responder en awk o sed, pero una versión de Python es sencilla y puede serle útil.

#!/usr/bin/env python

input_file = 'input.dat'
in_fh      = open(input_file, 'r')

input_order = []
seen        = {}
for line in in_fh:    
    # Remove the newline character...
    line = line[:-1]

    # Separate the first column from the rest of the line...
    key_col, sep, rest_of_line = line.partition(" ")
    rest_of_line = sep + rest_of_line  

    # If we've seen this key already, concatenate the line...
    if key_col in seen:
        seen[key_col] += rest_of_line
    # ...otherwise, record the ordering, and store the new info
    else:
        input_order.append(key_col)
        seen[key_col] = rest_of_line

in_fh.close()

# Dump the ordered output to stdout
for unique_col in input_order:
    print unique_col + seen[unique_col]
ire_and_curses
fuente
Muy genial. Con mi experiencia cero en Python, incluso logré editar el script que toma el primer argumento como nombre de archivo de entrada :)
pequeño
2

Esta es una aplicación más interesante de coreutils, sospecho que no es muy eficiente con una gran entrada, ya que invoca unir para cada línea en la entrada.

touch outfile
while read; do
  join -a1 -a2 outfile <(echo $REPLY) > tmp
  mv tmp outfile
done < infile

Para mejorar su eficiencia, puede ser útil ahorrar outfiley tmpen un disco RAM.

Editar

O sin archivos temporales:

out=""
while read; do
  out=$(join -a1 -a2 <(echo -n "$out") <(echo -n "$REPLY"))
done < infile

echo "$out"
Thor
fuente
2

Y aquí hay una línea PERL:

$ perl -e 'my %h; while(<>){chomp; @a=split(/\s+/); $k=shift(@a); $h{$k}.=join(" ", @a) . " "; } map{$h{$_}=~s/\s*$//; print "$_ $h{$_}\n}keys(%hash);' infile
terdon
fuente