bash awk outer-join

bash: limpie la combinación externa de tres archivos, conservando la membresía de archivos



awk outer-join (4)

Código para GNU awk :

{ if ($1=="id") { v[i++]=$3; next } b[$1,$2]=$1" "$2 c[i-1,$1" "$2]=$3 } END { printf ("id name") for (x in v) printf (" %s", v[x]); printf ("/n") for (y in b) { printf ("%s", b[y]) for (z in v) if (c[z,b[y]]==0) {printf (" 0")} else printf (" %s", c[z,b[y]]) printf ("/n") } }

$cat file? id name in1 1 jon 1 2 sue 1 id name in2 2 sue 1 3 bob 1 id name in3 2 sue 1 3 adam 1 $awk -f prog.awk file? id name in1 in2 in3 3 bob 0 1 0 3 adam 0 0 1 1 jon 1 0 0 2 sue 1 1 1

Considere los siguientes tres archivos con encabezados en la primera fila:

archivo1:

id name in1 1 jon 1 2 sue 1

archivo2:

id name in2 2 sue 1 3 bob 1

archivo3:

id name in3 2 sue 1 3 adam 1

Quiero fusionar estos archivos para obtener el siguiente resultado, merged_files:

id name in1 in2 in3 1 jon 1 0 0 2 sue 1 1 1 3 bob 0 1 0 3 adam 0 0 1

Esta solicitud tiene varias características especiales que no he encontrado implementadas de manera práctica en grep / sed / awk / join, etc. Edición: Puede suponer, por simplicidad, que los tres archivos ya han sido ordenados.


Este script awk hará lo que quieras:

$1=="id"&&$2=="name"{ ins[$3]= 1; lastin = $3; } $1!="id"||$2!="name" { ids[$1] = 1; names[$2] = 1; a[$1,$2,lastin]= $3 used[$1,$2] = 1; } END { printf "id name" for (i in ins) { printf " %s", i } printf "/n" for (id in ids) { for (name in names) { if (used[id,name]) { printf "%s %s", id, name for (i in ins) { printf " %d", a[id,name,i] } printf "/n" } } } }

Suponiendo que sus archivos se llaman list1 , list2 , etc., y el archivo awk es script.awk , puede ejecutarlo así

$ cat list* | awk -f script.awk id name in1 in2 in3 1 jon 1 0 0 2 sue 1 1 1 3 bob 0 1 0 3 adam 0 0 1

Estoy seguro de que es una forma mucho más corta y sencilla de hacerlo, pero esto es todo lo que pude hacer a la 1:30 a.m. :)


Escribí esto hace un tiempo. Lo publiqué en línea y lo publiqué aquí, así que la próxima vez que lo busque puedo encontrarlo. Es un poco complicado, pero admite combinaciones externas, izquierdas, exclusivas, etc., manejo duplicado (eliminar o multiplicar), etc.

https://code.google.com/p/ea-utils/source/browse/trunk/clipper/xjoin

TODO: maneje los encabezados mejor, maneje la entrada de transmisión.

Usage: xjoin [options] [:]<operator> <f1> <f2> [...*] Joins file 1 and file 2 by the first column, suitable for arbitratily large files (disk-based sort). Operator is one of: # Pasted ops, combines rows: in[ner] return rows in common le[ft] return rows in common, left joined ri[ght] return rows in common, right joined ou[ter] return all rows, outer joined # Exclusive (not pasted) ops, only return rows from 1 file: ex[clude] return only those rows with nothing in common (see -f) xl[eft] return left file rows that are not in right file xr[ight] return right file rows that are not in left file Common options: -1,-2=N per file, column number to join on (def 1) -k=N set the key column to N (for both files) -d STR column delimiter (def tab) -q STR quote char (def none) -h [N] files have headers (optionally, N is the file number) -u [N] files may contain duplicate entries, only output first match -s [N] files are already sorted, don''t sort first -n numeric sort key columns -p prefix headers with filename/ -f prefix rows with the input file name (op:ex only)


Esto es muy similar al problema resuelto en el script Bash para encontrar filas coincidentes de varios archivos CSV . No es idéntico, pero es muy similar. (Tan similar que solo tuve que eliminar tres comandos de sort , cambiar los tres comandos sed ligeramente, cambiar los nombres de los archivos, cambiar el valor ''que falta'' de no a 0 y cambiar el reemplazo en el sed final de la coma al espacio).

El comando join con sed (generalmente también sort , pero los datos ya están lo suficientemente ordenados) son las principales herramientas necesarias. Suponga que : no aparece en los datos originales. Para registrar la presencia de una fila en un archivo, queremos un campo 1 en el archivo (casi está allí); Tendremos join suministrar el 0 cuando no hay una coincidencia. El 1 al final de cada línea que no sea el encabezado debe convertirse en :1 , y el último campo en el encabezado también debe estar precedido por : Luego, usando la sustitución de procesos de bash , podemos escribir:

$ sed ''s/[ ]/([^ ]*/)$/:/1/'' file1 | > join -t: -a 1 -a 2 -e 0 -o 0,1.2,2.2 - <(sed ''s/[ ]/([^ ]*/)$/:/1/'' file2) | > join -t: -a 1 -a 2 -e 0 -o 0,1.2,1.3,2.2 - <(sed ''s/[ ]/([^ ]*/)$/:/1/'' file3) | > sed ''s/:/ /g'' id name in1 in2 in3 1 jon 1 0 0 2 sue 1 1 1 3 adam 0 0 1 3 bob 0 1 0 $

El comando sed (tres veces) agrega : antes del último campo en cada línea de los archivos. Las uniones son muy simétricas. El -t: especifica que el separador de campo es el dos puntos; a -a 1 y -a 2 significan que cuando no hay una coincidencia en un archivo, la línea todavía se incluirá en la salida; el -e 0 significa que si no hay una coincidencia en un archivo, se genera un 0 en la salida; y la opción -o especifica las columnas de salida. Para la primera unión, -o 0,1.2,2.2 la salida es la columna de unión (0), luego la segunda columna (la 1 ) de los dos archivos. La segunda unión tiene 3 columnas en la entrada, por lo que especifica -o 0,1.2,1.3,2.2 . El argumento - en sí mismo significa ''leer entrada estándar''. La notación <(...) es ''sustitución de proceso'', donde se proporciona un nombre de archivo (generalmente /dev/fd/NN ) al comando join, y contiene el resultado del comando dentro de los paréntesis. La salida se filtra a través de sed una vez más para reemplazar los dos puntos con espacios, produciendo la salida deseada.

La única diferencia con la salida deseada es la secuencia de 3 bob después de 3 adam ; no está particularmente claro sobre qué base los ordenó en reversa en su resultado deseado. Si es crucial, se puede idear un medio para resolver el orden de manera diferente (por ejemplo, sort -k1,1 -k3,5 , excepto que ordena la línea de etiqueta después de los datos; de ser necesario, hay soluciones provisionales).