Łączenie dwóch plików z unikalnym identyfikatorem

9

Mam dwa pliki z odpowiednio około 12900 i 4400 wpisami, które chcę dołączyć. Pliki zawierają informacje o lokalizacji dla wszystkich lądowych stacji obserwacji pogody na całym świecie. Największy plik jest aktualizowany co dwa tygodnie, a mniejszy raz w roku. Oryginalne pliki można znaleźć tutaj ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm i http://weather.rap.ucar.edu/surface/ station.txt ). Pliki, które mam, są już przeze mnie manipulowane za pomocą mieszanego skryptu awk, sed i bash. Pliki używam do wizualizacji danych za pomocą pakietu GEMPAK, który jest swobodnie dostępny z Unidata. Największy plik będzie działał z GEMPAK, ale nie tylko z jego pełnymi możliwościami. W tym celu potrzebne jest połączenie.

Plik 1 zawiera informacje o lokalizacji dla stacji obserwujących pogodę, gdzie pierwsze 6 cyfr to unikalny identyfikator stacji. Różne parametry (numer stacji, nazwa stacji, kod kraju, długość i szerokość geograficzna oraz wysokość stacji) są określone tylko przez jego pozycję w linii, tzn. Brak zakładek.

         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

Plik 2 zawiera unikalny identyfikator w pliku 1 i drugi, 4-znakowy identyfikator (lokalizator 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

Chcę połączyć dwa pliki, aby plik wynikowy miał 4-znakowy identyfikator na pierwszych 4 pozycjach w wierszu, tj. Identyfikator powinien zastąpić 4 spacje.

         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

Czy można wykonać to zadanie za pomocą skryptu bash i / lub awk?

Staffan Scherloff
źródło
czy pliki są posortowane według pola ID?
miracle173

Odpowiedzi:

8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1
użytkownik46911
źródło
Eleganckie rozwiązanie, które rozumiem tylko z podstawowymi umiejętnościami. Dziękuję!
Staffan Scherloff,
Program odczytuje jeden plik do pamięci, zanim zacznie działać. Jeśli pliki się powiększą, może to znacznie obniżyć wydajność. W przypadku większego pliku nie można użyć tej metody.
miracle173
7

Kilku z nas chciało sprawdzić, czy możemy rozwiązać ten problem jointylko za pomocą . To jest moja próba zrobienia tego. Ponieważ częściowo działa @Terdon jest mi winien obiad 8-).

Komenda

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

Przykład

$ 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

Detale

Powyższe wykorzystuje prawie każdą dostępną opcję, joinktóra mówi mojemu przeczuciu, że używamy go źle, jak w pewnym sensie Frankensteina, ale wszyscy się tutaj uczymy, więc to jest OK ... Chyba.

Przełącznik -a1mówi dołączeniu, aby zawierał wszystkie wiersze, które nie mają odpowiadającego dopasowania z pliku2 w pliku1. Oto, co powoduje wyświetlanie tych linii:

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

-1 1I -2 1mówią, które kolumny do przyłączenia przewodów od 2 pliki na, głównie ich 1st kolumny. -o ...Mówi kolumny, które od 2 do wyświetlania plików iw jakiej kolejności.

-e "N/A"Mówi używać znaków „N / A” o wartości uchwytem miejsce drukować na obszarach, które są uznane przez pusty join.

Ostatnie 2 argumenty zasilają 2 pliki file1i file2są posortowane w poleceniu złączenia.

Prosimy o uprzejmość, ponieważ jest to praca w toku, a my staramy się zademonstrować, w jaki sposób można rozwiązać ten typ problemu za pomocą joinpolecenia, ponieważ wydaje się, że jest to rodzaj problemu, do którego został przeznaczony.

Nierozstrzygnięte problemy

  1. Trzecia kolumna

    Najważniejsze to, jak walczyć z trzecią kolumną, ponieważ jest to połączenie 1 słowa i 2 wartości słów. To wydaje się być poważną przeszkodą joini nie mogę znaleźć rozwiązania. Wszelkie wskazówki będą mile widziane.

  2. Rozstaw

    Całe pierwotne odstępy zostały utracone joini nie widzę też sposobu na ich obejście. W joinkońcu może to nie być właściwy sposób radzenia sobie z tego rodzaju problemami.

  3. Wydaje się jednak działać?

    Po wielu zgięciach za pomocą wiersza poleceń istnieje ogólne rozwiązanie, więc wydaje się, że może ono działać przynajmniej częściowo, więc można go użyć w centrum rozwiązania, a następnie skorzystać z innych narzędzi, takich jak awki, sedaby go wyczyścić . Nasuwa się pytanie jednak: „Jeśli jesteś czyszczenia go z awk& sedjakikolwiek sposób, to równie dobrze można po prostu wykorzystać je bezpośrednio?”.

slm
źródło
@Terdon - zobacz tę odpowiedź, daj mi znać, co myślisz.
slm
+1 Myślę, że to narzędzie uniksowe, które zostało zaprogramowane do rozwiązania takiego zadania
miracle173 11.09.2013
dlaczego nie -e „” zamiast -e „N / A”. czy to nie działa (nie mogę tego spróbować)?
miracle173
@ miracle173 - zdecydowanie użycie spacji jako argumentu. też działa. Wybrałem N / A, aby było oczywiste, co robi.
slm
2
@terdon - tak, to był jednak fajny problem, podobała mi się wspólna praca, mam nadzieję, że możemy wspólnie pracować nad niektórymi przyszłymi problemami. Nadal uważam, że ta odpowiedź będzie użytecznym celem na stronie, ponieważ nie mogłem znaleźć wielu ekstremalnych przykładów, joinwięc teraz Internet ma taki. 8-)
slm
4

To powinno być możliwe przy użyciu join, ale nie mogę dowiedzieć się, jak zrobić to poprawnie wydrukować spacji i pustych pól. W każdym razie ten mały skrypt w Perlu załatwi sprawę:

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

Zapisz to jako foo.pli uruchom w następujący sposób:

$ 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
źródło
Działa to doskonale. Wielkie dzięki - naprawdę doceniam twój tekst wyjaśniający w kodzie.
Staffan Scherloff,
@StaffanScherloff nie ma za co. Jeśli to odpowiada na twoje pytanie, zaznacz je jako zaakceptowane, aby pytanie można było oznaczyć jako odpowiedź.
terdon
@StaffanScherloff po zastanowieniu, zaakceptuj awk, jest o wiele lepiej. - terdon 5 minut temu
terdon
@terdon - czy miałeś z tym kłopotów przy użyciu joysticka? Wydawało się, że to droga, nigdy wcześniej nie korzystałem z tej -ofunkcji, nie działałem tak, jakbym się spodziewał.
slm
@slm nie, myślałem o jakiejś kombinacji -oi -e, ale nie mógł go wydrukować linie, które nie miały wpisu plik2. Powodzenia, chciałbym wiedzieć, czy to możliwe.
terdon
3

Bash zrobi.

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

Zobacz tę odpowiedź SO, aby uzyskać szczegółowe informacje na temat tego, co dzieje się z „skrótem” Bash 4 obsługuje natywnie tablicę asocjacyjną, ale powinno to działać w 3 + 4 (może 2?)

Konieczne może być przycięcie w lewo linii z pliku 1, aby uzyskać formatowanie.

Matt
źródło
2

Oto prosty sposób na zrobienie tego join(+ kilka dodatkowych narzędzi) i zachowanie odstępów. Wygląda na to, że oba pliki są posortowane według numeru stacji, więc nie jest wymagane dodatkowe sortowanie:

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

Część przed potokiem jest bardzo podobna do tego, co użył SLM w swojej odpowiedzi, więc nie będę jej więcej omawiać. Jedyną różnicą jest to, że używam -e " "- ciąg czterech spacji jako zamiennik brakujących pól wejściowych i -o 2.2do wyprowadzenia tylko 2. pola pliku2
Tak więc join -j1 -a1 -o 2.2 -e " " file1 file2tworzy kolumnę o szerokości czterech znaków (nie jest widoczna poniżej, ale po EK nie ma nic ** i puste linie to tak naprawdę cztery spacje):

EKVG







EKGF
EKTS

EKYT



EKSN

my następnie pasteto (używając spacji jako separatora) do pliku 1, z którego otrzymujemy cutpierwsze 5 znaków | paste -d' ' - <(cut -c6- file1)
Wynik końcowy:

         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
źródło