2013-03-02 8 views
10

Supponiamo che io ho due file, en.csv e sp.csv, ciascuno contenente esattamente due record separati da virgola:Come ottenere tutti i campi in outer join con Unix join?

en.csv:

1,dog,red,car 
3,cat,white,boat 

sp.csv:

2,conejo,gris,tren 
3,gato,blanco,bote 

Se eseguo

join -t, -a 1 -a 2 -e MISSING en.csv sp.csv 

l'uscita che ottengo è:

1,dog,red,car 
2,conejo,gris,tren 
3,cat,white,boat,gato,blanco,bote 

Si noti che tutti i campi mancanti sono state crollato. Per ottenere un join esterno completo "corretto", ho bisogno di specificare un formato; così

join -t, -a 1 -a 2 -e MISSING -o 0,1.2,1.3,1.4,2.2,2.3,2.4 en.csv sp.csv 

cede

1,dog,red,car,MISSING,MISSING,MISSING 
2,MISSING,MISSING,MISSING,conejo,gris,tren 
3,cat,white,boat,gato,blanco,bote 

Un inconveniente di questo modo di produrre un full outer join è che bisogna specificare esplicitamente il formato del tavolo finale, che non può essere facile da fare in programmatica applicazioni (dove l'identità delle tabelle unite è nota solo al runtime).

Le versioni recenti di GNU join eliminano questa mancanza supportando il formato speciale auto. Pertanto, con tale versione del join l'ultimo comando di cui sopra potrebbe essere sostituito dal gran lunga più generale

join -t, -a 1 -a 2 -e MISSING -o auto en.csv sp.csv 

Come posso ottenere lo stesso effetto con le versioni di join che non supportano l'opzione -o auto?


Background e dettagli

Ho una shell Unix (zsh) script che è stato progettato per processi diversi flatfiles CSV, e lo fa facendo vasta uso di GNU join 's' - o auto 'opzione. Devo modificare questo script in modo che possa funzionare in ambienti in cui il comando disponibile join non supporta l'opzione -o auto (come nel caso di BSD join e nelle versioni precedenti di GNU join).

Un utilizzo tipico di questa opzione nello script è qualcosa di simile:

_reccut() { 
    cols="1,$1" 
    shift 
    in=$1 
    shift 
    if (($# > 0)); then 
     join -t, -a 1 -a 2 -e 'MISSING' -o auto \ 
      <(cut -d, -f $cols $in | sort -t, -k1) \ 
      <(_reccut "[email protected]") 
    else 
     cut -d, -f $cols $in | sort -t, -k1 
    fi 
} 

mi mostra questo esempio per illustrare che sarebbe difficile da sostituire -o auto con un formato esplicito, dal momento che i campi da includere in questo il formato non è noto fino al runtime.

La funzione _reccut sopra in pratica estrae le colonne dai file e unisce le tabelle risultanti lungo la loro prima colonna.Per vedere come _reccut in azione, immaginare che, oltre ai file di cui sopra, abbiamo avuto anche il file

de.csv

2,Kaninchen,Grau,Zug 
1,Hund,Rot,Auto 

Quindi, per esempio, per visualizzare side-by-side colonna 3 en.csv, colonne 2 e 4 del sp.csv e colonna 3 de.csv si potrebbe correre:

% _reccut 3 en.csv 2,4 sp.csv 3 de.csv | cut -d, 2- 
red,MISSING,MISSING,Rot 
MISSING,conejo,tren,Grau 
white,gato,bote,MISSING 
+1

avendo dovuto fare esattamente ciò che si sta parlando di un progetto 1-off con Sun4, penso che tu sei bloccato con la tua codifica o fornisci un nuovo GNU join come parte della tua installazione. Scusa, ma buona fortuna. – shellter

+1

Immagino che dovrei aggiungere, dopo molti scherzi, ho finito con l'associare array in awk, con molto meno fastidio. In bocca al lupo. – shellter

risposta

1

Ecco una soluzione che potrebbe o non potrebbe funzionare per i vostri dati. Si avvicina al problema allineando i record all'interno di un file CSV per numero di riga, ovvero il record 2 termina sulla linea 2, registra 3123 sul numero di linea 3123 e così via. record mancante/linee sono imbottiti con MISSING campi, in modo che i file di input sarebbero alterati per assomigliare a questo:

en.csv:

1,dog,red,car 
2,MISSING,MISSING,MISSING 
3,cat,white,boat 

de.csv:

1,Hund,Rot,Auto 
2,Kaninchen,Grau,Zug 
3,MISSING,MISSING,MISSING 

sp.csv:

1,MISSING,MISSING,MISSING 
2,conejo,gris,tren 
3,gato,blanco,bote 

F da lì è facile ritagliare le colonne di interesse e stamparle una accanto all'altra usando paste.

Per raggiungere questo obiettivo, abbiamo ordinare i file di input prima e poi applichiamo qualche stupido awk magia:

  • Se appare un record sul loro numero di linea previsto, stamparlo
  • In caso contrario, la stampa come molti righe contenenti il ​​numero di aspettative (questo è basato sul numero di campi della prima riga del file, come quello join -o auto) MISSING campi fino a quando l'allineamento è corretto di nuovo
  • Non tutti i file di input stanno per lo stesso numero di record, quindi il massimo viene cercato prima di tutto questo. Quindi, più righe con i campi MISSING vengono stampate fino al raggiungimento del massimo.

Codice

reccut.sh:

#!/bin/bash 

get_max_recnum() 
{ 
    awk -F, '{ if ($1 > max) { max = $1 } } END { print max }' "[email protected]" 
} 

align_by_recnum() 
{ 
    sort -t, -k1 "$1" \ 
     | awk -F, -v MAXREC="$2" ' 
      NR==1 { for(x = 1; x < NF; x++) missing = missing ",MISSING" } 
      { 
       i = NR 
       if (NR < $1) 
       { 
        while (i < $1) 
        { 
         print i++ missing 
        } 
        NR+=i 
       } 
      }1 
      END { for(i++; i <= MAXREC; i++) { print i missing } } 
      ' 
} 

_reccut() 
{ 
    local infiles=() 
    local args=([email protected]) 
    for arg; do 
     infiles+=("$2") 
     shift 2 
    done 
    MAXREC="$(get_max_recnum "${infiles[@]}")" __reccut "${args[@]}" 
} 

__reccut() 
{ 
    local cols="$1" 
    local infile="$2" 
    shift 2 

    if (($# > 0)); then 
     paste -d, \ 
      <(align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}) \ 
      <(__reccut "[email protected]") 
    else 
     align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols} 
    fi 
} 

_reccut "[email protected]" 

Run

$ ./reccut.sh 3 en.csv 2,4 sp.csv 3 de.csv 
red,MISSING,MISSING,Rot 
MISSING,conejo,tren,Grau 
white,gato,bote,MISSING 
+0

Sarebbe bello solo per avere un feedback su questo, funziona per voi? –