Joindre deux fichiers avec un identifiant unique

9

J'ai deux fichiers avec environ 12900 et 4400 entrées respectivement, que je veux joindre. Les fichiers contiennent des informations sur l'emplacement de toutes les stations d'observation météorologique terrestres du monde entier. Le plus gros fichier est mis à jour toutes les deux semaines, et le plus petit une fois par an environ. Les fichiers originaux peuvent être trouvés ici ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm et http://weather.rap.ucar.edu/surface/ stations.txt ). Les fichiers que j'ai sont déjà manipulés par moi avec un script mixte awk, sed et bash. J'utilise les fichiers pour visualiser les données à l'aide du package GEMPAK, qui est disponible gratuitement auprès d'Unidata. Le plus gros fichier fonctionnera avec GEMPAK, mais pas uniquement avec sa pleine capacité. Pour cela, une jointure est nécessaire.

Le fichier 1 contient des informations de localisation pour les stations d'observation météorologique, où les 6 premiers chiffres sont l'identifiant unique de la station. Les différents paramètres (numéro de station, nom de station, code de pays, longitude de latitude et altitude de station) sont définis uniquement par sa position sur la ligne, c'est-à-dire sans onglets.

         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

Le fichier 2 contient l'identifiant unique du fichier 1 et un deuxième identifiant à 4 caractères (localisateur 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

Je veux joindre les deux fichiers, afin que le fichier résultant ait l'identifiant à 4 caractères dans les 4 premières positions de la ligne, c'est-à-dire que l'identifiant devrait remplacer les 4 espaces.

         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

Est-il possible d'accomplir cette tâche avec un script bash et / ou awk?

Staffan Scherloff
la source
les fichiers sont-ils triés par le champ ID?
miracle173

Réponses:

8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1
user46911
la source
Une solution élégante, que je ne possède que quelques compétences de base, merci!
Staffan Scherloff
Le programme lit un fichier en mémoire avant de commencer à fonctionner. Si les fichiers deviennent plus volumineux, cela peut réduire considérablement les performances. Pour un fichier plus volumineux, cette méthode ne peut pas être utilisée.
miracle173
7

Quelques-uns d'entre nous voulaient voir si nous pouvions résoudre ce problème en utilisant joinuniquement. Ceci est ma tentative de le faire. Puisqu'il fonctionne partiellement, @Terdon me doit un dîner 8-).

La commande

$ 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)

Exemple

$ 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

Détails

Ce qui précède utilise à peu près toutes les options disponibles, joince qui indique à mon instinct que nous l'utilisons mal, comme dans un certain type de Frankenstein, mais nous apprenons tous ici, donc c'est OK ... Je suppose.

Le commutateur -a1indique à join d'inclure toutes les lignes qui n'ont pas de correspondance correspondante de file2 dans file1. Voici donc ce qui pousse ces lignes à s'afficher:

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

Les -1 1et -2 1disent sur quelles colonnes joindre les lignes des 2 fichiers, principalement leurs 1ères colonnes. Le -o ...dit quelles colonnes des 2 fichiers afficher et dans quel ordre.

Le -e "N/A"dit d'utiliser la chaîne "N / A" comme valeur d'espace réservé pour imprimer les champs qui sont considérés comme vides par join.

Les 2 derniers arguments alimentent les 2 fichiers file1et file2sont triés dans la commande join.

Veuillez être gentil, car c'est un travail en cours et nous essayons de montrer comment on pourrait résoudre ce type de problème en utilisant la joincommande, car cela semble être le type de problème auquel il était destiné.

Problèmes en suspens

  1. 3e colonne

    Le principal est de savoir comment composer avec la 3e colonne, car il s'agit d'un mélange de valeurs de 1 mot et de 2 mots. Cela semble être une pierre d'achoppement majeure joinet je ne peux pas trouver un moyen de le contourner. Tout conseil serait apprécié.

  2. Espacement

    Tout l'espacement d'origine est perdu avec joinet je ne vois pas non plus de moyen de le garder. Ce joinn'est donc peut-être pas la bonne façon de traiter ces types de problèmes après tout.

  3. Semble fonctionner cependant?

    Après beaucoup de flexions avec la ligne de commande, la solution générale est là, donc cela semble fonctionner au moins partiellement, donc cela pourrait être utilisé au cœur d'une solution, puis utiliser d'autres outils tels que awket sedpour la nettoyer . Cela soulève cependant la question: "Si vous le nettoyez avec awk& de sedquelque manière que ce soit, alors vous pourriez aussi bien les utiliser directement?".

slm
la source
@Terdon - voyez cette réponse, dites-moi ce que vous en pensez.
slm
+1 Je pense que c'est l'outil Unix qui a été programmé pour résoudre une telle tâche
miracle173
pourquoi pas -e "" au lieu de -e "N / A". cela ne fonctionne-t-il pas (je ne peux pas l'essayer)?
miracle173
@ miracle173 - par tous les moyens, oui l'utilisation d'un espace comme argument. fonctionne aussi. J'ai opté pour N / A pour qu'il soit évident de ce qu'il faisait.
slm
2
@terdon - oui, c'était quand même un problème amusant, j'ai aimé travailler ensemble, j'espère que nous pourrons aussi travailler ensemble sur de futurs problèmes. Je pense toujours que cette réponse servira un but utile sur le site car je n'ai pas pu trouver de nombreux exemples extrêmes joinalors maintenant Internet a celui-ci. 8-)
slm
4

Cela devrait être possible en utilisant joinmais je ne sais pas comment faire pour imprimer correctement les espaces et les champs vides. Quoi qu'il en soit, ce petit script Perl fera l'affaire:

#!/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; 
}

Enregistrez-le sous foo.plet exécutez comme suit:

$ 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
la source
Cela fonctionne très bien. Merci beaucoup - J'apprécie vraiment votre explication du texte dans le code.
Staffan Scherloff
@StaffanScherloff vous êtes les bienvenus. Si cela répond à votre question, veuillez la marquer comme acceptée afin que la question puisse être marquée comme répondue.
terdon
@StaffanScherloff sur la réflexion, acceptez celui awk, c'est beaucoup mieux. - terdon il y a 5 minutes
terdon
@terdon - avez-vous plus de problèmes avec celui-ci en utilisant join? Semblait comme la voie à suivre, n'a jamais utilisé sa -ofonctionnalité auparavant, ne fonctionnant pas comme je m'y attendais.
slm
@slm non, je pensais à une combinaison de -oet -emais je n'ai pas pu l'obtenir pour imprimer des lignes qui n'avaient pas d'entrée dans file2. Bonne chance, je serais intéressé de savoir si c'est possible.
terdon
3

Bash fera l'affaire.

#!/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

Voir cette réponse SO pour les détails de ce qui se passe avec le "hachage" Bash 4 supporte nativement les tableaux associatifs, mais cela devrait fonctionner en 3 + 4 (peut-être 2?)

Vous devrez peut-être couper la ligne à partir du fichier 1 pour obtenir votre mise en forme.

Mat
la source
2

Voici un moyen simple de le faire avec join(+ quelques autres outils) et de conserver l'espacement. Les deux fichiers semblent être triés par numéro de station, donc aucun tri supplémentaire n'est nécessaire:

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

La partie avant le tuyau est très similaire à ce que slm a utilisé dans sa réponse, donc je ne vais pas le revoir. La seule différence est que j'utilise -e " "- une chaîne de quatre espaces en remplacement des champs d'entrée manquants et -o 2.2pour sortir uniquement le 2ème champ du fichier2 Produit
donc join -j1 -a1 -o 2.2 -e " " file1 file2une colonne de quatre caractères (elle n'est pas visible ci-dessous mais il n'y a rien après EK ** et les lignes vides sont en fait quatre espaces):

EKVG







EKGF
EKTS

EKYT



EKSN

nous avons ensuite pastececi (en utilisant l'espace comme délimiteur) dans file1 dont nous avons cutles 5 premiers caractères. | paste -d' ' - <(cut -c6- file1)
Résultat 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
don_crissti
la source