Unir dos archivos con un identificador único

9

Tengo dos archivos con aproximadamente 12900 y 4400 entradas respectivamente, que quiero unir. Los archivos contienen información de ubicación para todas las estaciones de observación meteorológica terrestres en todo el mundo. El archivo más grande se actualiza quincenalmente y el más pequeño una vez al año más o menos. Los archivos originales se pueden encontrar aquí ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm y http://weather.rap.ucar.edu/surface/ estaciones.txt ). Los archivos que tengo ya están manipulados por mí con un script mixto de awk, sed y bash. Utilizo los archivos para visualizar datos utilizando el paquete GEMPAK, que está disponible gratuitamente en Unidata. El archivo más grande funcionará con GEMPAK, pero solo no con toda su capacidad. Para esto se necesita una unión.

El archivo 1 contiene información de ubicación de las estaciones de observación meteorológica, donde los primeros 6 dígitos son el identificador único de la estación. Los diferentes parámetros (número de estación, nombre de estación, código de país, longitud de latitud y elevación de estación) se definen solo por su posición en la línea, es decir, sin pestañas.

         060090 AKRABERG FYR                        DN  6138   -666     101
         060100 VAGA FLOGHAVN                       DN  6205   -728      88
         060110 TORSHAVN                            DN  6201   -675      55
         060120 KIRKJA                              DN  6231   -631      55
         060130 KLAKSVIK HELIPORT                   DN  6221   -656      75
         060160 HORNS REV A                         DN  5550    786      21
         060170 HORNS REV B                         DN  5558    761      10
         060190 SILSTRUP                            DN  5691    863       0
         060210 HANSTHOLM                           DN  5711    858       0
         060220 TYRA OEST                           DN  5571    480      43
         060240 THISTED LUFTHAVN                    DN  5706    870       8
         060290 GROENLANDSHAVNEN                    DN  5703   1005       0
         060300 FLYVESTATION AALBORG                DN  5708    985      13
         060310 TYLSTRUP                            DN  5718    995       0
         060320 STENHOEJ                            DN  5736   1033      56
         060330 HIRTSHALS                           DN  5758    995       0
         060340 SINDAL FLYVEPLADS                   DN  5750   1021      28

El archivo 2 contiene el identificador único en el archivo 1 y un segundo identificador de 4 caracteres (localizador de la OACI).

060100 EKVG
060220 EKGF
060240 EKTS
060300 EKYT
060340 EKSN
060480 EKHS
060540 EKHO
060600 EKKA
060620 EKSV
060660 EKVJ
060700 EKAH
060780 EKAT

Quiero unir los dos archivos, para que el archivo resultante tenga el identificador de 4 caracteres en las primeras 4 posiciones en la línea, es decir, el identificador debe reemplazar los 4 espacios.

         060090 AKRABERG FYR                        DN  6138   -666     101
EKVG     060100 VAGA FLOGHAVN                       DN  6205   -728      88
         060110 TORSHAVN                            DN  6201   -675      55
         060120 KIRKJA                              DN  6231   -631      55
         060130 KLAKSVIK HELIPORT                   DN  6221   -656      75
         060160 HORNS REV A                         DN  5550    786      21
         060170 HORNS REV B                         DN  5558    761      10
         060190 SILSTRUP                            DN  5691    863       0
         060210 HANSTHOLM                           DN  5711    858       0
EKGF     060220 TYRA OEST                           DN  5571    480      43
EKTS     060240 THISTED LUFTHAVN                    DN  5706    870       8
         060290 GROENLANDSHAVNEN                    DN  5703   1005       0
EKYT     060300 FLYVESTATION AALBORG                DN  5708    985      13
         060310 TYLSTRUP                            DN  5718    995       0
         060320 STENHOEJ                            DN  5736   1033      56
         060330 HIRTSHALS                           DN  5758    995       0
EKSN     060340 SINDAL FLYVEPLADS                   DN  5750   1021      28

¿Es posible realizar esta tarea con algún script bash y / o awk?

Staffan Scherloff
fuente
¿Están ordenados los archivos por el campo ID?
milagro173

Respuestas:

8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1
usuario46911
fuente
Una solución elegante, que solo entiendo con algunas habilidades básicas. ¡Gracias!
Staffan Scherloff
El programa lee un archivo en la memoria antes de comenzar a funcionar. Si los archivos se hacen más grandes, eso puede disminuir significativamente el rendimiento. Para archivos más grandes, este método no se puede utilizar.
milagro173
7

Algunos de nosotros queríamos ver si solo podíamos resolver este problema join. Este es mi intento de hacer eso. Como funciona parcialmente, @Terdon me debe una cena 8-).

El comando

$ join -a1 -1 1 -2 1 -o 2.2 1.1 1.2 1.3 1.4 1.5 1.6 1.7 -e "N/A" \
     <(sort file1) <(sort file2)

Ejemplo

$ join -a1 -1 1 -2 1 -o 2.2 1.1 1.2 1.3 1.4 1.5 1.6 1.7 -e "N/A" <(sort file1) <(sort file2) | column -t
N/A   060090  AKRABERG          FYR         DN    6138  -666  101
EKVG  060100  VAGA              FLOGHAVN    DN    6205  -728  88
N/A   060110  TORSHAVN          DN          6201  -675  55    N/A
N/A   060120  KIRKJA            DN          6231  -631  55    N/A
N/A   060130  KLAKSVIK          HELIPORT    DN    6221  -656  75
N/A   060160  HORNS             REV         A     DN    5550  786
N/A   060170  HORNS             REV         B     DN    5558  761
N/A   060190  SILSTRUP          DN          5691  863   0     N/A
N/A   060210  HANSTHOLM         DN          5711  858   0     N/A
EKGF  060220  TYRA              OEST        DN    5571  480   43
EKTS  060240  THISTED           LUFTHAVN    DN    5706  870   8
N/A   060290  GROENLANDSHAVNEN  DN          5703  1005  0     N/A
EKYT  060300  FLYVESTATION      AALBORG     DN    5708  985   13
N/A   060310  TYLSTRUP          DN          5718  995   0     N/A
N/A   060320  STENHOEJ          DN          5736  1033  56    N/A
N/A   060330  HIRTSHALS         DN          5758  995   0     N/A
EKSN  060340  SINDAL            FLYVEPLADS  DN    5750  1021  28

Detalles

Lo anterior está haciendo uso de casi todas las opciones disponibles joinque le dicen a mi instinto que lo estamos usando mal, como en algún tipo de forma de Frankenstein, pero todos estamos aprendiendo aquí, así que está bien ... supongo.

El modificador -a1le dice a join que incluya cualquier línea que no tenga una coincidencia correspondiente del archivo2 en el archivo1. Entonces, esto es lo que está impulsando estas líneas para mostrarse:

N/A   060330  HIRTSHALS         DN          5758  995   0     N/A

Los -1 1y -2 1están diciendo en qué columnas unir las líneas de los 2 archivos, principalmente sus primeras columnas. El -o ...dice qué columnas de los 2 archivos para mostrar y en qué orden.

El -e "N/A"dice usar la cadena "N / A" como un valor de marcador de posición para imprimir para los campos que se consideran vacíos join.

Los últimos 2 argumentos están alimentando los 2 archivos, file1y file2según lo ordenado en el comando de unión.

Sea amable, ya que este es un trabajo en progreso y estamos tratando de demostrar cómo se resolvería este tipo de problema utilizando el joincomando, ya que este parece ser el tipo de problema para el que fue diseñado.

Cuestiones pendientes

  1. 3ra columna

    La principal es cómo lidiar con la tercera columna, ya que es una combinación de valores de 1 palabra y 2 palabras. Esto parece ser un obstáculo importante joiny no puedo encontrar una forma de evitarlo. Cualquier orientación sería apreciada.

  2. Espaciado

    Se pierde todo el espacio original joiny tampoco veo una manera de mantenerlo. Por joinlo tanto, podría no ser la forma correcta de lidiar con este tipo de problemas después de todo.

  3. Aunque parece funcionar?

    Después de mucho doblar con la línea de comando, la solución general está ahí, por lo que parece que puede funcionar al menos parcialmente, por lo que podría usarse en el núcleo de una solución y luego usar otras herramientas como awky sedpara limpiarla. . "Si usted está limpiando para arriba con: la pregunta, sin embargo esto plantea awky sedde cualquier manera, entonces puede ser que también acaba de utilizar directamente?".

slm
fuente
@Terdon - mira esta respuesta, déjame saber lo que piensas.
slm
+1 Creo que esa es la herramienta Unix que se programó para resolver una tarea de este tipo
milagro173
por qué no -e "" en lugar de -e "N / A". ¿Esto no funciona (no puedo probarlo)?
milagro173
@ miracle173 - por supuesto, sí, el uso de un espacio como argumento. funciona también Opté por N / A para que fuera obvio lo que estaba haciendo.
slm
2
@terdon: sí, fue un problema divertido, sin embargo, disfruté trabajando juntos, espero que podamos trabajar juntos en algunos problemas futuros. Todavía creo que esta respuesta tendrá un propósito útil en el sitio, ya que no pude encontrar muchos ejemplos extremos, por joinlo que ahora Internet tiene esta. 8-)
slm
4

Esto debería ser posible usando joinpero no puedo encontrar la manera de imprimir espacios y campos vacíos correctamente. De todos modos, este pequeño script de Perl hará el truco:

#!/usr/bin/env perl

## Open file2, the one that contains the codes
## it is expected to be the 1st argument given to the script.
open($a,"$ARGV[0]"); 

## Read the number<=>code pairs into a hash (an associative array)
## called 'k'
while (<$a>) {
    chomp; @f=split(/\s+/); $k{$f[0]}=$f[1];
}

## Open file1, the one that contains the data
## it is expected to be the 2nd argument given to the script.
open($b,"$ARGV[1]"); 
## Go through the file
while (<$b>) {
    ## Split each line at white space into the array @f
    @f=split(/\s+/);  

    ## $f[1] is the 6 digit number that defines the different stations.
    ## If this number has an entry in the hash %k, if it was found
    ## in file2, replace the first 4 spaces with its value from the hash.
    s/^\s{4}/$k{$f[1]}/ if defined($k{$f[1]});

    ## Print each line of the file
    print; 
}

Guarde esto como foo.ply ejecute lo siguiente:

$ perl foo.pl file2 file1
         060090 AKRABERG FYR                        DN  6138   -666     101
EKVG     060100 VAGA FLOGHAVN                       DN  6205   -728      88
         060110 TORSHAVN                            DN  6201   -675      55
         060120 KIRKJA                              DN  6231   -631      55
         060130 KLAKSVIK HELIPORT                   DN  6221   -656      75
         060160 HORNS REV A                         DN  5550    786      21
         060170 HORNS REV B                         DN  5558    761      10
         060190 SILSTRUP                            DN  5691    863       0
         060210 HANSTHOLM                           DN  5711    858       0
EKGF     060220 TYRA OEST                           DN  5571    480      43
EKTS     060240 THISTED LUFTHAVN                    DN  5706    870       8
         060290 GROENLANDSHAVNEN                    DN  5703   1005       0
EKYT     060300 FLYVESTATION AALBORG                DN  5708    985      13
         060310 TYLSTRUP                            DN  5718    995       0
         060320 STENHOEJ                            DN  5736   1033      56
         060330 HIRTSHALS                           DN  5758    995       0
EKSN     060340 SINDAL FLYVEPLADS                   DN  5750   1021      28
terdon
fuente
Esto funciona excelente. Muchas gracias. Realmente aprecio tu texto explicativo en el código.
Staffan Scherloff
@StaffanScherloff de nada. Si esto responde a su pregunta, márquela como aceptada para que la pregunta pueda marcarse como respondida.
terdon
@StaffanScherloff pensándolo bien, acepta el awk, es mucho mejor. - terdon hace 5 minutos
terdon
@terdon - ¿Ya te molestaste con este usando join? Parecía el camino a seguir, nunca usé su -ofunción antes, no funcionaba como era de esperar.
slm
@slm no, estaba pensando en una combinación de -oy -eno pude conseguir que imprima líneas que no tenían entrada en el archivo2. Buena suerte, me interesaría saber si es posible.
terdon
3

Bash lo hará.

#!/usr/bin/env bash

# ### create a psuedo hash of icao locator id's
# read each line into an array
while read -a line; do
  # set icao_nnnnnn variable to the value
  declare "icao_${line[0]}"=${line[1]}
done <file2


# ### match up icao id's from file1
# read in file line at a time
while IFS=$'\n' read line; do
  # split the line into array
  read -a arr <<< "$line"
  # if the icao_nnnnnn variable exists, it will print out
  var="icao_${arr[0]}"
  printf "%-8s %s\n" "${!var}" "$line"
done <file1

Vea esta respuesta SO para conocer los detalles de lo que está sucediendo con el "hash". Bash 4 admite la matriz asociativa de forma nativa, pero esto debería funcionar en 3 + 4 (¿tal vez 2?)

Es posible que deba recortar la línea del archivo1 para obtener el formato.

Mate
fuente
2

Aquí hay una manera simple de hacerlo join(+ un par de herramientas más) y preservar el espacio. Parece que ambos archivos están ordenados por número de estación, por lo que no se necesita una clasificación adicional:

join -j1 -a1 -o 2.2 -e "    " file1 file2 | paste -d' ' - <(cut -c6- file1)

La parte anterior a la tubería es muy similar a la que usó slm en su respuesta, por lo que no volveré a analizarla. La única diferencia es que estoy usando -e " ": una cadena de cuatro espacios como reemplazo de los campos de entrada faltantes y -o 2.2para generar solo el segundo campo del archivo2
Entonces join -j1 -a1 -o 2.2 -e " " file1 file2produce una columna de cuatro caracteres de ancho (no es visible a continuación, pero no hay nada después de EK ** y las líneas vacías son en realidad cuatro espacios):

EKVG







EKGF
EKTS

EKYT



EKSN

luego pasteesto (usando el espacio como delimitador) al archivo1 del cual tenemos cutlos primeros 5 caracteres | paste -d' ' - <(cut -c6- file1)
Resultado final:

         060090 AKRABERG FYR                        DN  6138   -666     101
EKVG     060100 VAGA FLOGHAVN                       DN  6205   -728      88
         060110 TORSHAVN                            DN  6201   -675      55
         060120 KIRKJA                              DN  6231   -631      55
         060130 KLAKSVIK HELIPORT                   DN  6221   -656      75
         060160 HORNS REV A                         DN  5550    786      21
         060170 HORNS REV B                         DN  5558    761      10
         060190 SILSTRUP                            DN  5691    863       0
         060210 HANSTHOLM                           DN  5711    858       0
EKGF     060220 TYRA OEST                           DN  5571    480      43
EKTS     060240 THISTED LUFTHAVN                    DN  5706    870       8
         060290 GROENLANDSHAVNEN                    DN  5703   1005       0
EKYT     060300 FLYVESTATION AALBORG                DN  5708    985      13
         060310 TYLSTRUP                            DN  5718    995       0
         060320 STENHOEJ                            DN  5736   1033      56
         060330 HIRTSHALS                           DN  5758    995       0
EKSN     060340 SINDAL FLYVEPLADS                   DN  5750   1021      28
revs don_crissti
fuente