Unindo dois arquivos com identificador exclusivo

9

Eu tenho dois arquivos com aproximadamente 12900 e 4400 entradas respectivamente, que desejo ingressar. Os arquivos contêm informações de localização para todas as estações terrestres de observação do tempo em todo o mundo. O arquivo maior é atualizado quinzenalmente e o menor uma vez por ano. Os arquivos originais podem ser encontrados aqui ( http://www.wmo.int/pages/prog/www/ois/volume-a/vola-home.htm e http://weather.rap.ucar.edu/surface/ stations.txt ). Os arquivos que eu já tenho são manipulados por mim com alguns scripts awk, sed e bash misturados. Eu uso os arquivos para visualizar dados usando o pacote GEMPAK, disponível gratuitamente na Unidata. O arquivo maior funcionará com o GEMPAK, mas não apenas com toda a sua capacidade. Para isso, é necessária uma junção.

O arquivo 1 contém informações de localização das estações de observação meteorológica, onde os 6 primeiros dígitos são o identificador exclusivo da estação. Os diferentes parâmetros (número da estação, nome da estação, código do país, longitude da latitude e elevação da estação) são definidos apenas pela sua posição na linha, ou seja, sem tabulações.

         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

O arquivo 2 contém o identificador exclusivo no arquivo 1 e um segundo identificador de 4 caracteres (localizador 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

Quero unir os dois arquivos, para que o arquivo resultante tenha o identificador de 4 caracteres nas 4 primeiras posições da linha, ou seja, o identificador deve substituir os 4 espaços.

         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

É possível realizar esta tarefa com algum script bash e / ou awk?

Staffan Scherloff
fonte
os arquivos são classificados pelo campo ID?
miracle173

Respostas:

8
awk 'BEGIN { while(getline < "file2" ) { codes[$1] = $2 } }
     { printf "%4s%s\n", codes[$1], substr($0, 5) }' file1
user46911
fonte
Uma solução elegante, que eu compreendo apenas com algumas habilidades básicas.
Staffan Scherloff
O programa lê um arquivo na memória antes de começar a trabalhar. Se os arquivos aumentarem, isso pode diminuir significativamente o desempenho. Para arquivos maiores, esse método não pode ser usado.
precisa saber é o seguinte
7

Alguns de nós queriam ver se poderíamos resolver esse problema usando joinapenas. Esta é a minha tentativa de fazer isso. Como funciona parcialmente, o @Terdon me deve um jantar 8-).

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

Exemplo

$ 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

Detalhes

A descrição acima está fazendo uso de praticamente todas as opções disponíveis, o joinque diz ao meu intestino que estamos usando errado, como em algum tipo de maneira de Frankenstein, mas todos estamos aprendendo aqui, então tudo bem ... eu acho.

A opção -a1informa ao join para incluir quaisquer linhas que não tenham uma correspondência correspondente do arquivo2 no arquivo1. Então é isso que está direcionando essas linhas para serem exibidas:

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

O -1 1e -2 1está dizendo em quais colunas juntar as linhas dos 2 arquivos, principalmente suas primeiras colunas. O -o ...está dizendo que colunas das 2 arquivos para mostrar e em que ordem.

O -e "N/A"diz para usar a string "N / A" como um valor de espaço reservado para imprimir para campos que são considerados vazia join.

Os últimos 2 argumentos estão alimentando os 2 arquivos file1e file2classificados no comando join.

Por favor, seja gentil, pois esse é um trabalho em andamento e estamos tentando demonstrar como alguém resolveria esse tipo de problema usando o joincomando, pois esse parece ser o tipo de problema para o qual foi criado.

Questões pendentes

  1. 3ª coluna

    O principal é como lidar com a terceira coluna, pois é uma mistura de 1 palavra e 2 valores de palavra. Isso parece ser uma grande pedra de tropeço joine eu não consigo descobrir uma maneira de contornar isso. Qualquer orientação seria apreciada.

  2. Espaçamento

    Todo o espaçamento original é perdido joine também não vejo uma maneira de mantê-lo por perto. Portanto, jointalvez não seja o caminho certo para lidar com esses tipos de problemas.

  3. Parece funcionar?

    Depois de muita flexão com a linha de comando, a solução geral está lá, então parece que pode funcionar pelo menos parcialmente; portanto, isso pode ser usado no centro de uma solução e, em seguida, fazer uso de outras ferramentas como awke sedpara limpá-la. . Isso levanta a questão, porém: "Se você está limpando-o com awk& sedqualquer forma, então você pode muito bem usá-los diretamente?".

slm
fonte
@ Terdon - veja esta resposta, deixe-me saber o que você pensa.
slm
+1 Eu acho que é a ferramenta unix que foi programado para resolver tal tarefa tarefa
miracle173
por que não -e "" em vez de -e "N / A". isso não funciona (não posso tentar)?
miracle173
@ miracle173 - por todos os meios, sim, o uso de um espaço como argumento. também funciona. Optei por N / A para que fosse óbvio o que estava fazendo.
slm
2
@terdon - sim, foi um problema divertido, gostamos de trabalhar juntos, espero que possamos trabalhar juntos em alguns problemas futuros. Eu ainda acho que essa resposta servirá a um propósito útil no site, já que não consegui encontrar muitos exemplos extremos de joinagora a internet tem esse. 8-)
slm
4

Isso deve ser possível usando, joinmas não consigo descobrir como fazê-lo imprimir espaços e campos vazios corretamente. De qualquer forma, este pequeno script Perl fará o truque:

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

Salve isso foo.ple execute da seguinte maneira:

$ 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
fonte
Isso funciona excelente. Muito obrigado - eu realmente aprecio o seu texto explicativo no código.
Staffan Scherloff
@StaffanScherloff de nada. Se isso responder à sua pergunta, marque-a como aceita para que a pergunta possa ser marcada como respondida.
terdon
@StaffanScherloff pensando melhor, aceite o awk, é muito melhor. - terdon há 5 minutos
terdon
@terdon - você mexeu mais com esse aqui usando join? Parecia o caminho a percorrer, nunca usou seu -orecurso antes, não funcionando como eu esperaria.
slm
@slm não, eu estava pensando em alguma combinação de -oe -e, mas não poderia obtê-lo para imprimir linhas que não tiveram nenhuma entrada no file2. Boa sorte, eu estaria interessado em saber se é possível.
terdon
3

Bash 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

Veja esta resposta SO para obter detalhes do que está acontecendo com o "hash" O Bash 4 suporta nativamente o array associativo, mas isso deve funcionar em 3 + 4 (talvez 2?)

Pode ser necessário aparar a linha esquerda do arquivo1 para obter sua formatação.

Matt
fonte
2

Aqui está uma maneira simples de fazer isso join(+ mais algumas ferramentas) e preservar o espaçamento. Ambos os arquivos parecem ser classificados pelo número da estação, portanto, nenhuma classificação adicional é necessária:

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

A parte anterior ao tubo é muito parecida com a que slm usou em sua resposta, para que eu não volte a examiná-lo. A única diferença é que estou usando -e " "- uma string de quatro espaços como substituto para campos de entrada ausentes e -o 2.2para gerar apenas o segundo campo do arquivo2.
Portanto, join -j1 -a1 -o 2.2 -e " " file1 file2produz uma coluna com quatro caracteres (não é visível abaixo, mas não há nada depois do EK ** e linhas vazias são na verdade quatro espaços):

EKVG







EKGF
EKTS

EKYT



EKSN

nós então paste(usando o espaço como delimitador) no arquivo1 a partir do qual obtemos cutos 5 primeiros 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
don_crissti
fonte