Verbinden Sie zwei Dateien, die in einer Spalte übereinstimmen, mit Wiederholungen

7

Wie kann ich zwei Dateien A und B erhalten und ein Ergebnis wie das folgende ausgeben:

Datei A:

001 Apple, CA
020 Banana, CN
023 Apple, LA
045 Orange, TT
101 Orange, OS
200 Kiwi, AA

Datei B:

01-Dec-2013 01.664  001     AAA CAC 1083
01-Dec-2013 01.664  020     AAA CAC 0513
01-Dec-2013 01.668  023     AAA CAC 1091
01-Dec-2013 01.668  101     AAA CAC 0183
01-Dec-2013 01.674  200     AAA CAC 0918
01-Dec-2013 01.674  045     AAA CAC 0918
01-Dec-2013 01.664  001     AAA CAC 2573
01-Dec-2013 01.668  101     AAA CAC 1091
01-Dec-2013 01.668  020     AAA CAC 6571
01-Dec-2013 01.668  023     AAA CAC 2148
01-Dec-2013 01.674  200     AAA CAC 0918
01-Dec-2013 01.668  045     AAA CAC 5135

Ergebnis:

01-Dec-2013 01.664  001     AAA CAC 1083    Apple, CA
01-Dec-2013 01.664  020     AAA CAC 0513    Banana, CN
01-Dec-2013 01.668  023     AAA CAC 1091    Apple, LA
01-Dec-2013 01.668  101     AAA CAC 0183    Orange, OS
01-Dec-2013 01.674  200     AAA CAC 0918    Kiwi, AA
01-Dec-2013 01.674  045     AAA CAC 0918    Orange, TT
01-Dec-2013 01.664  001     AAA CAC 2573    Apple, CA
01-Dec-2013 01.668  101     AAA CAC 1091    Orange, OS
01-Dec-2013 01.668  020     AAA CAC 6571    Banana, CN
01-Dec-2013 01.668  023     AAA CAC 2148    Apple, LA
01-Dec-2013 01.674  200     AAA CAC 0918    Kiwi, AA
01-Dec-2013 01.668  045     AAA CAC 5135    Orange, TT

(Datei A: Die Nummer sollte mit der mittleren Nummer aus Datei B übereinstimmen.)

Gibt es eine Möglichkeit, dies zu tun?

JOSS
quelle

Antworten:

5

Eine einfache Lösung mit awk:

awk -v FILE_A="file-A" -v OFS="\t" 'BEGIN { while ( ( getline < FILE_A ) > 0 ) { VAL = $0 ; sub( /^[^ ]+ /, "", VAL ) ; DICT[ $1 ] = VAL } } { print $0, DICT[ $3 ] }' file-B

Hier ist eine kommentierte Version:

awk -v FILE_A="file-A" -v OFS="\t" '
BEGIN {

  # Loop on the content of file-A
  # to put the values in a table

  while ( ( getline < FILE_A ) > 0 ){

     # Remove the index from the value
     VAL = $0
     sub( /^[^ ]+ /, "", VAL )

     # Fill the table
     DICT[ $1 ] = VAL
  }
}
{

  # Print the line followed by the
  # corresponding value
  print $0, DICT[ $3 ]

}' file-B

quelle
@ Jean, Danke für deine Antwort. :) Ich habe das beste Ergebnis von Ihrer Hilfe.
JOSS
@JOSS, Wenn Sie eine awkAntwort akzeptieren , sollten Sie das Bash-Script-Tag entfernen.
Ricky Beam
3

Hier ist ein Bash-Skript, das genau das tut, wonach Sie suchen. Das Skript heißt mergeAB.bash.

#!/bin/bash

readarray A < fileA.txt 

i=0
while read -r B; do
  idx=$(( $i % ${#A[@]} ))

  printf "%s %s" "$B" "${A[$idx]}"
  #echo "i: $i | A#: ${#A[@]} | IDX: $idx"

  let i=i+1
done < fileB.txt

Wenn Sie es ausführen:

$ ./mergeAB.bash 
01-Dec-2013 01.664  001     AAA CAC 1083 001 Apple, CA
01-Dec-2013 01.664  020     AAA CAC 0513 020 Banana, CN
01-Dec-2013 01.668  023     AAA CAC 1091 023 Apple, LA
01-Dec-2013 01.668  101     AAA CAC 0183 045 Orange, TT
01-Dec-2013 01.674  200     AAA CAC 0918 101 Orange, OS
01-Dec-2013 01.674  045     AAA CAC 0918 200 Kiwi, AA
01-Dec-2013 01.664  001     AAA CAC 2573 001 Apple, CA
01-Dec-2013 01.668  101     AAA CAC 1091 020 Banana, CN
01-Dec-2013 01.668  020     AAA CAC 6571 023 Apple, LA
01-Dec-2013 01.668  023     AAA CAC 2148 045 Orange, TT
01-Dec-2013 01.674  200     AAA CAC 0918 101 Orange, OS
01-Dec-2013 01.668  045     AAA CAC 5135 200 Kiwi, AA

Einzelheiten

Das allererste, was wir tun, ist, den Befehl readarrayzu verwenden, um den Inhalt von fileA.txtin ein Array einzulesen . Dies ist eine neuere Funktion von Bash 4.x. Wenn Sie also eine ältere Version von Bash verwenden, können Sie stattdessen Folgendes verwenden:

$ IFS=$'\n' read -d '' -r -a A < fileA.txt

Der Rest dieses Skripts ist etwas komplex, aber ich habe echoin der Mitte eine ausführliche Beschreibung hinterlassen , die Sie entfernen können, um zu sehen, was los ist.

$ ./mergeAB.bash | grep i:
i: 0 | A#: 6 | IDX: 0
i: 1 | A#: 6 | IDX: 1
i: 2 | A#: 6 | IDX: 2
i: 3 | A#: 6 | IDX: 3
i: 4 | A#: 6 | IDX: 4
i: 5 | A#: 6 | IDX: 5
i: 6 | A#: 6 | IDX: 0
i: 7 | A#: 6 | IDX: 1
i: 8 | A#: 6 | IDX: 2
i: 9 | A#: 6 | IDX: 3
i: 10 | A#: 6 | IDX: 4
i: 11 | A#: 6 | IDX: 5

Was ist denn hier los? Es gibt einen Zähler, mit $idem wir jede Zeile zählen, fileB.txtwährend wir sie durchlaufen. Wir berechnen dann $idxdurch Berechnung der Modulo-Division des aktuellen Wertes von $iund der Anzahl der Zeilen in fileA.txt.

HINWEIS: Die Länge des Arrays A. Wenn $idxwir auf diese Weise berechnen, können wir es von 0 bis 5, dann von 0 bis 5 usw. "schleifen" lassen. In der obigen Debug-Ausgabe können Sie dies mit der IDX:Spalte sehen.

Der Rest des Skripts ist ziemlich normal und verwendet printfdas Drucken der verketteten Zeilen von fileB.txtmit der entsprechenden Zeile von fileA.txt.

slm
quelle
Vielen Dank!! SIM, das ist es, was ich brauche, um mehr darüber zu erfahren
JOSS
@JOSS - Gern geschehen, danke für das interessante Q!
slm
@ SIM, ich finde nur heraus, dass das Ergebnis nicht übereinstimmt .....
JOSS
1
Datei A: Die Nummer sollte mit der mittleren Nummer aus Datei B übereinstimmen. Wissen Sie, wie man das Problem behebt?
JOSS
2
$ cat b | während gelesen b; do key = $ (echo $ b | awk '{print $ 3}'); / bin / echo -n "$ b"; grep -w $ key a | cut -d \ -f2-; erledigt
01.12.2013 01.664 001 AAA CAC 1083 Apple, CA.
01.12.2013 01.664 020 AAA CAC 0513 Banana, CN
01.12.2013 01.668 023 AAA CAC 1091 Apple, LA
01-Dec-2013 01.668 101 AAA CAC 0183 Orange, OS
01.12.2013 01.674 200 AAA CAC 0918 Kiwi, AA
01.12.2013 01.674 045 AAA CAC 0918 Orange, TT
01.12.2013 01.664 001 AAA CAC 2573 Apple, CA.
01.12.2013 01.668 101 AAA CAC 1091 Orange, OS
01.12.2013 01.668 020 AAA CAC 6571 Banana, CN
01.12.2013 01.668 023 AAA CAC 2148 Apple, LA
01.12.2013 01.674 200 AAA CAC 0918 Kiwi, AA
01-Dec-2013 01.668 045 AAA CAC 5135 Orange, TT

Ich vermute, dass das awkKonstrukt eleganter gemacht werden kann, aber es scheint zu funktionieren.

ckujau
quelle
Danke, das funktioniert. aber ist es möglich, Array zu verwenden?
JOSS
2

Das joinDienstprogramm führt einen "Gleichheits-Join" für die angegebenen Dateien durch und schreibt das Ergebnis in die Standardausgabe. Das "Verknüpfungsfeld" ist das Feld in jeder Datei, mit dem die Dateien verglichen werden.

Mit anderen Worten, Sie haben zwei Dateien, die eine Spalte gemeinsam nutzen. Sie können die Zeilen der Dateien verbinden, in denen die Spalte gleich ist.

Lass es uns versuchen:

$ join -1 1 -2 3 a b
001 Apple, CA 01-Dec-2013 01.664 AAA CAC 1083
020 Banana, CN 01-Dec-2013 01.664 AAA CAC 0513
023 Apple, LA 01-Dec-2013 01.668 AAA CAC 1091
101 Orange, OS 01-Dec-2013 01.668 AAA CAC 0183
200 Kiwi, AA 01-Dec-2013 01.674 AAA CAC 0918

Ja, funktioniert. Aber nicht in dem von Ihnen angegebenen Format. Tauschen wir also die Dateien aus:

$ join -1 3 -2 1 b a
001 01-Dec-2013 01.664 AAA CAC 1083 Apple, CA
020 01-Dec-2013 01.664 AAA CAC 0513 Banana, CN
023 01-Dec-2013 01.668 AAA CAC 1091 Apple, LA
101 01-Dec-2013 01.668 AAA CAC 0183 Orange, OS
200 01-Dec-2013 01.674 AAA CAC 0918 Kiwi, AA

Viel besser. Immer noch nicht ganz richtig, da das verbundene Feld zuerst angezeigt wird. Awk kann das beheben:

$ join -1 3 -2 1 b a | awk '{print $2,$3,$1,$4,$5,$6,$7,$8}'
01-Dec-2013 01.664 001 AAA CAC 1083 Apple, CA
01-Dec-2013 01.664 020 AAA CAC 0513 Banana, CN
01-Dec-2013 01.668 023 AAA CAC 1091 Apple, LA
01-Dec-2013 01.668 101 AAA CAC 0183 Orange, OS
01-Dec-2013 01.674 200 AAA CAC 0918 Kiwi, AA

Hier bitteschön. Die Felder sind in derselben Reihenfolge. In können awkSie printfeinige Registerkarten verwenden oder einfügen, wenn Sie den genauen Abstand erhalten möchten, aber ich denke, Sie werden auf die Idee kommen.

Bahamat
quelle
1
Beachten Sie, dass Sie die Eingabedateien im Verknüpfungsfeld sortieren müssen, damit sie joinordnungsgemäß funktionieren.
Stéphane Chazelas
1
joinist auch nicht ganz richtig für die Frage. Es gibt mehr Zeilen von B als von A; join gibt nicht alle Zeilen aus. Und es zerstört die festen Feldbreiten (dh frisst Leerzeichen)
Ricky Beam
@ RickyBeam - Falsch. joinist definitiv das richtige Werkzeug für diesen Job : join -1 1 -2 3 -o 2.1 2.2 2.3 2.4 2.5 2.6 1.2 1.3 fileA <(sort -k3 fileB). Sie können sogar die Reihenfolge der Zeilen fileBund den Abstand beibehalten, wenn Sie dies wünschen. Durchsuchen Sie meine Beiträge unter dem joinTag, wenn Sie neugierig sind, wie.
don_crissti
Wichtig: FILE1 und FILE2 müssen in den Join-Feldern sortiert werden. Das tötet die Bestellung ziemlich solide. joinist ein schlechtes Werkzeug für die Aufgabe; Sie haben nichts anderes bewiesen.
Ricky Beam
@RickyBeam - im OP-Beispiel file1ist bereits sortiert, obwohl im Allgemeinen beide sortiert werden müssen, also stimme ich hier voll und ganz zu. Die Tatsache, dass sort"die Bestellung solide beendet", ist irrelevant, da Sie die Ausgabe auf die ursprüngliche Reihenfolge zurücksortieren können. Das heißt, wenn Sie klug genug sind. Ich habe nicht das Bedürfnis, Ihnen etwas zu beweisen, aber hier sind einige Beispiele, die Sie lesen können: 1 , 2 , 3 .
don_crissti
0

Mit einem Array, wie angefordert (vollständig in bash) ...

while read num loc; do A[0x$num]=$loc; done < A
while read B; do set -- $B; echo "${B} ${A[0x$3]}"; done < B

(funktioniert in bash v2)

Die erste Zeile lädt das Array "A" aus Datei A. Das 0x $ num- Bit dient dazu, alles in derselben Zahlenbasis zu halten, andernfalls werden sie durch die führenden Nullen oktal. Die zweite Zeile liest jede Zeile der Datei B (wobei Leerzeichen erhalten bleiben), legt die Positionsargumente aus dieser Zeile fest und druckt schließlich die Zeile plus den indizierten Eintrag von "A".

Ricky Beam
quelle