Verbinden von zwei Dateien mit eindeutiger Kennung

9

Ich habe zwei Dateien mit ungefähr 12900 bzw. 4400 Einträgen, denen ich beitreten möchte. Die Dateien enthalten Standortinformationen für alle landgestützten Wetterbeobachtungsstationen auf der ganzen Welt. Die größte Datei wird alle zwei Wochen aktualisiert, die kleinere etwa einmal im Jahr. Die Originaldateien finden Sie hier ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm und http://weather.rap.ucar.edu/surface/). Stationen.txt ). Die Dateien, die ich habe, werden bereits von mir mit einem gemischten awk-, sed- und bash-Skript bearbeitet. Ich verwende die Dateien, um Daten mit dem GEMPAK-Paket zu visualisieren, das bei Unidata frei verfügbar ist. Die größte Datei funktioniert mit GEMPAK, jedoch nur nicht mit allen Funktionen. Hierzu wird ein Join benötigt.

Datei 1 enthält Standortinformationen für Wetterbeobachtungsstationen, wobei die ersten 6 Ziffern die eindeutige Stationskennung sind. Die verschiedenen Parameter (Stationsnummer, Stationsname, Ländercode, Längengrad und Stationshöhe) werden nur durch ihre Position in der Linie definiert, dh ohne Registerkarten.

         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

Datei 2 enthält die eindeutige Kennung in Datei 1 und eine zweite Kennung mit 4 Zeichen (ICAO-Locator).

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

Ich möchte die beiden Dateien verbinden, damit die resultierende Datei den 4-stelligen Bezeichner an den ersten 4 Stellen in der Zeile hat, dh der Bezeichner sollte die 4 Leerzeichen ersetzen.

         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

Ist es möglich, diese Aufgabe mit einem Bash- und / oder Awk-Skript auszuführen?

Staffan Scherloff
quelle
Sind die Dateien nach dem ID-Feld sortiert?
Wunder173

Antworten:

8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1
user46911
quelle
Eine elegante Lösung, die ich mit nur einigen Grundkenntnissen verstehe. Danke!
Staffan Scherloff
Das Programm liest eine Datei in den Speicher, bevor es funktioniert. Wenn die Dateien größer werden, kann dies die Leistung erheblich beeinträchtigen. Für größere Dateien kann diese Methode nicht verwendet werden.
Wunder173
7

Ein paar von uns wollten sehen, ob wir dieses Problem joinnur mit lösen können . Dies ist mein Versuch, das zu tun. Da es teilweise funktioniert, schuldet mir @Terdon ein Abendessen 8-).

Der Befehl

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

Beispiel

$ 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

Einzelheiten

Das Obige nutzt so ziemlich jede verfügbare Option, joindie mir sagt, dass wir sie falsch verwenden, wie auf eine Art Frankenstein-Art, aber wir lernen alle hier, also ist das in Ordnung ... Ich denke.

Der Schalter -a1weist join an, alle Zeilen, die nicht mit Datei2 übereinstimmen, in Datei1 aufzunehmen. Das ist es also, was diese Zeilen antreibt, um angezeigt zu werden:

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

Die -1 1und -2 1sagen, welche Spalten die Zeilen aus den 2 Dateien verbinden sollen, hauptsächlich ihre 1. Spalten. Das -o ...sagt, welche Spalten aus den 2 Dateien in welcher Reihenfolge angezeigt werden sollen.

Das -e "N/A"sagt, dass die Zeichenfolge "N / A" als Platzhalterwert zum Drucken für Felder verwendet werden soll, die von als leer gelten join.

Die letzten beiden Argumente speisen die beiden Dateien file1& file2wie im Join-Befehl sortiert.

Bitte seien Sie freundlich, da dies eine laufende Arbeit ist und wir versuchen zu demonstrieren, wie man diese Art von Problem mit dem joinBefehl lösen würde , da dies die Art von Problem zu sein scheint, für die es gedacht war.

Offene Fragen

  1. 3. Spalte

    Das wichtigste ist, wie man mit der 3. Spalte umgeht, da es sich um eine Mischung aus 1 Wort- und 2 Wortwerten handelt. Dies scheint ein großer Stolperstein zu sein joinund ich kann keinen Weg finden, um es zu umgehen. Jede Anleitung wäre dankbar.

  2. Abstand

    Der gesamte ursprüngliche Abstand geht verloren joinund ich sehe auch keine Möglichkeit, ihn beizubehalten. So ist joinvielleicht nicht der richtige Weg , um mit dieser Art von Problemen nach allem zu beschäftigen.

  3. Scheint aber zu funktionieren?

    Nach langem Biegen mit der Befehlszeile ist die allgemeine Lösung vorhanden, sodass dies zumindest teilweise funktionieren kann. Dies könnte also im Kern einer Lösung verwendet werden und dann andere Tools wie awkund sedzum Bereinigen verwenden . Dies wirft jedoch die Frage auf: "Wenn Sie es mit awk& auf sedirgendeine Weise aufräumen, können Sie sie genauso gut direkt verwenden?".

slm
quelle
@Terdon - siehe diese Antwort, lass mich wissen, was du denkst.
slm
+1 Ich denke, das ist das Unix-Tool, das programmiert wurde, um eine solche Aufgabe zu lösen
miracle173
warum nicht -e "" anstelle von -e "N / A". funktioniert das nicht (ich kann es nicht versuchen)?
Wunder173
@ miracle173 - auf jeden Fall ja die Verwendung eines Leerzeichens als Argument. funktioniert auch. Ich entschied mich für N / A, damit klar war, was es tat.
slm
2
@terdon - ja, es war trotzdem ein lustiges Problem, ich habe es genossen, zusammen daran zu arbeiten, hoffentlich können wir auch gemeinsam an einigen zukünftigen Problemen arbeiten. Ich denke immer noch, dass diese Antwort einen nützlichen Zweck auf der Website erfüllen wird, da ich nicht viele extreme Beispiele dafür finden joinkonnte. Jetzt hat das Internet diese. 8-)
slm
4

Dies sollte möglich sein, joinaber ich kann nicht herausfinden, wie Leerzeichen und leere Felder korrekt gedruckt werden. Wie auch immer, dieses kleine Perl-Skript wird den Trick machen:

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

Speichern Sie dies als foo.plund führen Sie es wie folgt aus:

$ 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
quelle
Das funktioniert hervorragend. Vielen Dank - ich freue mich sehr über Ihre Erklärung des Textes im Code.
Staffan Scherloff
@StaffanScherloff Gern geschehen. Wenn dies Ihre Frage beantwortet, markieren Sie sie bitte als akzeptiert, damit die Frage als beantwortet markiert werden kann.
Terdon
@StaffanScherloff beim zweiten Gedanken, akzeptiere den awk, es ist viel besser. - Terdon vor 5 Minuten
Terdon
@terdon - hast du dich nicht mehr mit Join beschäftigt? Schien der richtige Weg zu sein, habe seine -oFunktion noch nie benutzt und nicht so funktioniert, wie ich es erwartet hätte.
slm
@slm nein, ich dachte an eine Kombination von -ound -ekonnte es aber nicht dazu bringen, Zeilen zu drucken, die keinen Eintrag in Datei2 hatten. Viel Glück, ich würde gerne wissen, ob es möglich ist.
Terdon
3

Bash wird es tun.

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

In dieser SO-Antwort finden Sie Einzelheiten dazu, was mit dem "Hash" passiert. Bash 4 unterstützt nativ assoziative Arrays, dies sollte jedoch in 3 + 4 funktionieren (möglicherweise 2?).

Möglicherweise müssen Sie die Zeile von Datei1 nach links kürzen, um die Formatierung zu erhalten.

Matt
quelle
2

Hier ist eine einfache Möglichkeit, dies mit join(+ ein paar weiteren Werkzeugen) zu tun und den Abstand beizubehalten. Beide Dateien scheinen nach Stationsnummer sortiert zu sein, sodass keine zusätzliche Sortierung erforderlich ist:

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

Der Teil vor der Pipe ist dem in seiner Antwort verwendeten Slm sehr ähnlich, daher werde ich nicht noch einmal darauf eingehen . Der einzige Unterschied ist , dass ich mit - einer vierRäume Zeichenfolge als Ersatz für fehlende Eingabefelder und der Ausgabe nur das zweite Feld von file2 So erzeugt eine Vier-Zeichen-breite Spalte (es ist nicht sichtbar unter , aber es gibt nichts nach EK ** und Leerzeilen sind eigentlich vier Leerzeichen):-e " "-o 2.2
join -j1 -a1 -o 2.2 -e " " file1 file2

EKVG







EKGF
EKTS

EKYT



EKSN

wir dann pastedies (mit Leerzeichen als Trennzeichen) zu Datei1, aus der wir cutdie ersten 5 Zeichen | paste -d' ' - <(cut -c6- file1)
Endergebnis:

         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
quelle