Unire due file con identificativo univoco


9

Ho due file con rispettivamente circa 12900 e 4400 voci, che voglio unire. I file contengono informazioni sulla posizione di tutte le stazioni di osservazione meteorologica terrestre in tutto il mondo. Il file più grande viene aggiornato ogni due settimane e il più piccolo una volta all'anno. I file originali sono disponibili qui ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm e http://weather.rap.ucar.edu/surface/ stations.txt ). I file che ho sono già stati manipolati da me con alcuni script misti awk, sed e bash. Uso i file per visualizzare i dati utilizzando il pacchetto GEMPAK, che è disponibile gratuitamente da Unidata. Il file più grande funzionerà con GEMPAK, ma non solo con la sua piena capacità. Per questo è necessario un join.

Il file 1 contiene informazioni sulla posizione per le stazioni meteorologiche, dove le prime 6 cifre sono l'identificatore univoco della stazione. I diversi parametri (numero della stazione, nome della stazione, prefisso internazionale, longitudine della latitudine ed elevazione della stazione) sono definiti solo dalla sua posizione nella linea, cioè senza schede.

         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

Il file 2 contiene l'identificatore univoco nel file 1 e un secondo identificatore di 4 caratteri (localizzatore ICAO).

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

Voglio unire i due file, in modo che il file risultante abbia l'identificatore di 4 caratteri nelle prime 4 posizioni nella riga, cioè l'identificatore dovrebbe sostituire i 4 spazi.

         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

È possibile eseguire questo compito con alcuni script bash e / o awk?


i file sono ordinati in base al campo ID?
miracle173

Risposte:


8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1

Una soluzione elegante, che capisco solo con alcune competenze di base. Grazie!
Staffan Scherloff,

Il programma legge un file in memoria prima che inizi a funzionare. Se i file diventano più grandi ciò può ridurre significativamente le prestazioni. Per file di dimensioni maggiori questo metodo non può essere utilizzato.
miracolo173

7

Un paio di noi volevano vedere se potevamo risolvere questo problema usando joinsolo. Questo è il mio tentativo di farlo. Dal momento che funziona parzialmente @Terdon mi deve una cena Cool.

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

Esempio

$ 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

Dettagli

Quanto sopra fa uso praticamente di tutte le opzioni disponibili a joincui dice al mio istinto che lo stiamo usando in modo sbagliato, come in un qualche modo di Frankenstein, ma stiamo tutti imparando qui, quindi va bene ... Immagino.

L'opzione -a1dice join per includere tutte le righe che non hanno una corrispondenza corrispondente da file2 in file1. Quindi questo è ciò che guida queste linee per essere visualizzate:

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

I -1 1e -2 1stanno dicendo quali colonne unire le linee dai 2 file in poi, principalmente le loro prime 1 colonne. La -o ...sta dicendo che le colonne dai file 2 per visualizzare e in quale ordine.

La -e "N/A"dice di usare la stringa "N / A" come un valore segnaposto da stampare per i campi che sono ritenuti vuota join.

Gli ultimi 2 argomenti stanno alimentando i 2 file file1e file2come ordinati nel comando join.

Per favore sii gentile, dato che si tratta di un lavoro in corso e stiamo provando a dimostrare come si potrebbe risolvere questo tipo di problema usando il joincomando, dato che questo sembra essere il tipo di problema a cui era destinato.

Problemi eccezionali

  1. 3a colonna

    Il principale è come fare i conti con la terza colonna poiché è un mix di valori di 1 parola e 2 parole. Sembra un grosso ostacolo joine non riesco a capire come aggirarlo. Qualsiasi consiglio sarebbe apprezzato.

  2. Spaziatura

    Tutta la spaziatura originale è persa joine non vedo nemmeno un modo per tenerlo in giro. Dopotutto, joinpotrebbe non essere il modo giusto di affrontare questi tipi di problemi.

  3. Sembra funzionare però?

    Dopo molte flessioni con la riga di comando, la soluzione generale è lì, quindi sembra che possa funzionare almeno in parte, quindi potrebbe essere utilizzata al centro di una soluzione e quindi fare uso di altri strumenti come awke sedper ripulirla . Questo pone la domanda però: "Se lo stai pulendo con awke in sedqualche modo, allora potresti anche usarli direttamente?".


@Terdon - vedi questa risposta, fammi sapere cosa ne pensi.
slm

+1 Penso che sia lo strumento unix programmato per risolvere un simile compito
miracolo173

perché non -e "" invece di -e "N / A". questo non funziona (non posso provarlo)?
miracolo173

@ miracle173 - certamente, sì l'uso di uno spazio come arg. funziona anche. Ho optato per N / A in modo che fosse ovvio cosa stesse facendo.
slm

2
@terdon - sì, è stato comunque un problema divertente, ci è piaciuto lavorare insieme, speriamo di poter lavorare anche su alcuni problemi futuri insieme. Penso ancora che questa risposta servirà a uno scopo utile sul sito poiché non sono riuscito a trovare molti esempi estremi di joincosì ora Internet ha questo. Cool
slm

4

Questo dovrebbe essere possibile usando joinma non riesco a capire come stampare correttamente spazi e campi vuoti. Comunque, questo piccolo script Perl farà il trucco:

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

Salva come foo.pled esegui come segue:

$ 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

Funziona benissimo. Mille grazie - apprezzo molto il tuo testo esplicativo nel codice.
Staffan Scherloff,

@StaffanScherloff, prego. Se questo risponde alla tua domanda, contrassegnala come accettata in modo che la domanda possa essere contrassegnata come risposta.
terdon

@StaffanScherloff ripensandoci, accetta quello awk, è molto meglio. - Terdon 5 min fa
Terdon

@terdon - hai mai avuto problemi con questo usando join? Sembrava la strada da percorrere, non -oavevo mai usato la sua funzionalità prima, non funzionava come mi sarei aspettato.
slm

@slm no, stavo pensando a una combinazione di -oe -ema non sono riuscito a farlo stampare linee che non avevano voce in file2. Buona fortuna, sarei interessato a sapere se è possibile.
terdon

3

Bash lo farà.

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

Vedi questa risposta SO per i dettagli di ciò che sta succedendo con l '"hash" Bash 4 supporta nativamente l'array associativo, ma questo dovrebbe funzionare in 3 + 4 (forse 2?)

Potrebbe essere necessario tagliare a sinistra la linea da file1 per ottenere la formattazione.


2

Ecco un modo semplice per farlo con join(+ un paio di strumenti in più) e preservare la spaziatura. Entrambi i file sembrano essere ordinati per numero di stazione, quindi non è necessario un ordinamento aggiuntivo:

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

La parte prima della pipe è molto simile a quella usata da slm nella sua risposta, quindi non ci riproverò. L'unica differenza è che sto usando -e " ": una stringa di quattro spazi in sostituzione dei campi di input mancanti e -o 2.2per produrre solo il secondo campo di file2
Quindi join -j1 -a1 -o 2.2 -e " " file1 file2produce una colonna di quattro caratteri (non è visibile sotto ma non c'è nulla dopo EK ** e le righe vuote sono in realtà quattro spazi):

EKVG







EKGF
EKTS

EKYT



EKSN

abbiamo quindi pastequesto (usando lo spazio come delimitatore) in file1 da cui abbiamo cuti primi 5 caratteri. | paste -d' ' - <(cut -c6- file1)
Risultato finale:

         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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.