GNU/Linux >> Linux Esercitazione >  >> Linux

Unire più campi in file di testo su Unix

Probabilmente è più semplice combinare i primi tre campi con awk:

awk '{print $1 "_" $2 "_" $3 " " $4}' filename

Allora puoi usare join normalmente sul "campo 1"


puoi provare questo

awk '{
 o1=$1;o2=$2;o3=$3
 $1=$2=$3="";gsub(" +","")
 _[o1 FS o2 FS o3]=_[o1 FS o2 FS o3] FS $0
}
END{ for(i in _) print i,_[i] }' file1 file2

uscita

$ ./shell.sh
foo 1 scaf  3 4.5
bar 2 scaf  3.3 1.00
foo 1 boo  2.3

Se vuoi omettere righe non comuni

awk 'FNR==NR{
 s=""
 for(i=4;i<=NF;i++){ s=s FS $i }
 _[$1$2$3] = s
 next
}
{
  printf $1 FS $2 FS $3 FS
  for(o=4;o<NF;o++){
   printf $i" "
  }
  printf $NF FS _[$1$2$3]"\n"
 } ' file2 file1

uscita

$ ./shell.sh
foo 1 scaf 3  4.5
bar 2 scaf 3.3  1.00

Ecco la corretta risposta (in termini di utilizzo dello standard GNU coreutils tools e non scrivere script personalizzati in perl/awk lo chiami tu).

$ join -j1 -o1.2,1.3,1.4,1.5,2.5 <(<file1 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1) <(<file2 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1)
bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5

OK, come funziona:

  1. Prima di tutto useremo un ottimo strumento join che può unire due righe. join ha due requisiti:

    • Possiamo unirci solo da un singolo campo.
    • Entrambi i file devono essere ordinati per colonna chiave!
  2. Dobbiamo generare chiavi nei file di input e per questo usiamo un semplice awk script:

    $ cat file1
    foo 1 scaf 3
    bar 2 scaf 3.3    
    
    $ <file1 awk '{print $1"-"$2"-"$3" "$0}'
    foo-1-scaf foo 1 scaf 3
    bar-2-scaf bar 2 scaf 3.3
    

    Vedi, abbiamo aggiunto la prima colonna con una chiave come "foo-1-scaf ".Facciamo lo stesso con file2 .A proposito. <file awk , è solo un modo stravagante di scrivere awk file o cat file | awk .

    Dovremmo anche ordinare i nostri file per chiave, nel nostro caso questa è la colonna 1, quindi aggiungiamo alla fine del comando il | sort -k1,1 (ordina per testo dalla colonna 1 alla colonna 1)

  3. A questo punto potremmo semplicemente generare i file file1.with.key e file2.con.chiave e unisciti a loro, ma supponiamo che quei file siano enormi, non vogliamo copiarli sul filesystem. Invece possiamo usare qualcosa chiamato bash sostituzione del processo per generare l'output in named pipe (questo eviterà la creazione di file intermedi non necessari). Per maggiori informazioni, leggi il link fornito.

    La nostra sintassi di destinazione è:join <( some command ) <(some other command)

  4. L'ultima cosa è spiegare gli argomenti di join fantasiosi:-j1 -o1.2,1.3,1.4,1.5,2.5

    • -j1 - join per chiave nella prima colonna (in entrambi i file)
    • -o - restituisce solo quei campi 1.2 (1° campo file2), 1.3 (1a colonna 3 del file), ecc.

      In questo modo abbiamo unito le righe, ma join restituisce solo le colonne necessarie.

Le lezioni apprese da questo post dovrebbero essere:

  • dovresti padroneggiare i coreutils pacchetto, questi strumenti sono molto potenti se combinati e quasi non ne hai mai bisogno scrivere un programma personalizzato per gestire questi casi,
  • Gli strumenti di core utils sono anche velocissimi e ampiamente testati, quindi sono sempre la scelta migliore.

Il comando join è difficile da usare e si unisce solo su una colonna

Un'ampia sperimentazione e un attento esame delle pagine di manuale indicano che non è possibile unire direttamente più colonne e tutti i miei esempi funzionanti di unione, stranamente, utilizzano solo una colonna di unione.

Di conseguenza, qualsiasi soluzione richiederà che le colonne da unire siano concatenate in una colonna, in qualche modo. Il comando join standard richiede anche che i suoi input siano nell'ordine corretto - c'è un'osservazione nel join GNU (info coreutils join) che non richiede sempre dati ordinati:

Tuttavia, come estensione GNU, se l'input non ha righe non abbinabili, l'ordinamento può essere qualsiasi ordine che consideri due campi uguali se e solo se il confronto dell'ordinamento sopra descritto li considera uguali.

Un modo possibile per farlo con i file forniti è:

awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file1 |
sort > sort1
awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file2 |
sort > sort2
join -1 1 -2 1 -o 1.2,1.3,1.4,1.5,2.5 sort1 sort2

Questo crea un campo di ordinamento composito all'inizio, utilizzando ':' per separare i sottocampi, quindi ordina il file - per ciascuno dei due file. Il comando join quindi unisce i due campi compositi, ma stampa solo i campi non compositi (non join).

L'output è:

bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5

Tentativi falliti di fare in modo che join faccia ciò che non farà

unisciti -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -o 1.1,1.2,1.3,1.4,2.4 file1 file2

Su MacOS X 10.6.3, questo dà:

$ cat file1
foo 1 scaf 3 
bar 2 scaf 3.3
$ cat file2
foo 1 scaf 4.5
foo 1 boo 2.3
bar 2 scaf 1.00
$ join -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -o 1.1,1.2,1.3,1.4,2.4 file1 file2
foo 1 scaf 3 4.5 
bar 2 scaf 3.3 4.5 
$

Questo è entrare nel campo 3 (solo) - che non è ciò che si desidera.

Devi assicurarti che i file di input siano nell'ordine corretto.


Linux
  1. Come rinominare più file in un singolo comando o script in Unix??

  2. Come rinominare più file usando Trova?

  3. Dd:file di input multipli?

  4. Come rinominare più file da un'estensione all'altra in Linux / Unix?

  5. Come dividere un file di testo in più file *.txt?

Grep Command in Linux (Trova testo nei file)

Rinomina comando in Linux (rinomina più file)

Come unire/unire più file audio in uno in Linux

Trova testo nei file su Linux usando grep

comando ls in Linux/UNIX

Ordina i file di testo con più righe come una riga