Bash Hinzufügen / Anhängen neuer Spalten aus anderen Dateien

Nov 24 2020

Ich habe eine name.txt-Datei mit einer Spalte, z

A
B
C
D
E
F

Dann habe ich viele Dateien, egxtxt, y.txt und z.txt

x.txt hat

A 1
C 3
D 2

y.txt hat

A 1
B 4
E 3

z.txt hat

B 2
D 2
F 1

Die gewünschte Ausgabe ist (Ausfüllen von 0, wenn keine Zuordnung vorliegt)

A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

Ist es möglich, es mit Bash zu schaffen? (vielleicht awk?)
Vielen Dank !!!


erste Änderungen - meine vorläufigen Bemühungen
Da ich noch ziemlich neu im Bash bin, fällt es mir wirklich schwer, mit awk eine mögliche Lösung zu finden. Ich bin eher mit R vertraut, in dem dies durch erreicht werden kann

namematrix[namematrix[,1]==xmatrix[,1],]

Alles in allem schätze ich die freundliche Hilfe, die mir hilft, mehr über awkund zu erfahren join!


Zweitbearbeitungen - ein super effizienter Ansatz gefunden!

Glücklicherweise habe ich mich unten von einigen wirklich brillanten Antworten inspirieren lassen und einen sehr rechnerisch effizienten Weg wie unten beschrieben. Dies kann für andere Personen hilfreich sein, die auf ähnliche Fragen stoßen, insbesondere wenn sie mit einer sehr großen Anzahl von Dateien mit sehr großer Größe arbeiten.

Berühren Sie zuerst eine join_awk.bash

#!/bin/bash
join -oauto -e0 -a1 $1 $2 | awk '{print $2}'

Führen Sie beispielsweise dieses Bash-Skript für name.txt und x.txt aus

join_awk.bash name.txt x.txt

würde erzeugen

1
0
3
2
0
0

Beachten Sie, dass ich hier nur die zweite Spalte behalte, um Speicherplatz zu sparen, da die ersten Spalten in meinem Datensatz sehr lange Namen sind, die enormen Speicherplatz beanspruchen würden.

Dann einfach umsetzen

parallel join_awk.bash name.txt {} \> outdir/output.{} ::: {a,b,c}.txt

Dies ist inspiriert von der brillanten Antwort unten, bei der GNU parallel und join verwendet wird. Der Unterschied besteht darin , dass die Antwort unten angeben muss j1für parallelaufgrund seiner seriellen Anfügen Logik, die es nicht wirklich „parallel“ macht. Außerdem wird die Geschwindigkeit immer langsamer, wenn das serielle Anhängen fortgesetzt wird. Im Gegensatz dazu bearbeiten wir hier jede Datei einzeln parallel. Es kann extrem schnell sein, wenn wir mit einer großen Anzahl großer Dateien mit mehreren CPUs arbeiten.

Zum Schluss führen Sie einfach alle einspaltigen Ausgabedateien von zusammen

cd outdir
paste output* > merged.txt

Dies wird auch sehr schnell sein, da pastees von Natur aus parallel ist.

Antworten

12 anubhava Nov 24 2020 at 13:42

Sie können dies verwenden awk:

awk 'NF == 2 {
   map[FILENAME,$1] = $2
   next
}
{
   printf "%s", $1 for (f=1; f<ARGC-1; ++f) printf "%s", OFS map[ARGV[f],$1]+0
   print ""
}' {x,y,z}.txt name.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
9 RavinderSingh13 Nov 24 2020 at 14:15

Eine weitere Möglichkeit hinzufügen. Könnten Sie bitte versuchen, mit den gezeigten Beispielen zu folgen, zu schreiben und zu testen. IMHO sollte in jedem funktionieren awk, obwohl ich nur die 3.1-Version von GNU habe awk. Dies ist eine sehr einfache und übliche Methode. Erstellen Sie ein Array beim Lesen der ersten (Haupt-) Eingabedatei und fügen Sie später in jeder Datei das 0Element dieses Arrays hinzu, das NICHT in dieser bestimmten Eingabedatei enthalten ist und nur mit kleinen Stichproben getestet wurde.

awk '
function checkArray(array){
  for(i in array){
    if(!(i in found)){ array[i]=array[i] OFS "0" }
  }
}
FNR==NR{
  arr[$0] next } foundCheck && FNR==1{ checkArray(arr) delete found foundCheck="" } { if($1 in arr){
    arr[$1]=(arr[$1] OFS $2) found[$1]
    foundCheck=1
    next
  }
}
END{
  checkArray(arr)
  for(key in arr){
    print key,arr[key]
  }
}
' name.txt x.txt y.txt  z.txt

Erläuterung: Hinzufügen einer detaillierten Erklärung für oben.

awk '                               ##Starting awk program from here.
function checkArray(array){         ##Creating a function named checkArray from here.
  for(i in array){                  ##CTraversing through array here.
    if(!(i in found)){ array[i]=array[i] OFS "0" }   ##Checking condition if key is NOT in found then append a 0 in that specific value.
  }
}
FNR==NR{                            ##Checking condition if FNR==NR which will be TRUE when names.txt is being read.
  arr[$0] ##Creating array with name arr with index of current line. next ##next will skip all further statements from here. } foundCheck && FNR==1{ ##Checking condition if foundCheck is SET and this is first line of Input_file. checkArray(arr) ##Calling function checkArray by passing arr array name in it. delete found ##Deleting found array to get rid of previous values. foundCheck="" ##Nullifying foundCheck here. } { if($1 in arr){                    ##Checking condition if 1st field is present in arr.
    arr[$1]=(arr[$1] OFS $2) ##Appening 2nd field value to arr with index of $1.
    found[$1]                       ##Adding 1st field to found as an index here.
    foundCheck=1                    ##Setting foundCheck here.
    next                            ##next will skip all further statements from here.
  }
}
END{                                ##Starting END block of this program from here.
  checkArray(arr)                   ##Calling function checkArray by passing arr array name in it.
  for(key in arr){                  ##Traversing thorugh arr here.
    print key,arr[key]              ##Printing index and its value here.
  }
}
' name.txt x.txt y.txt z.txt        ##Mentioning Input_file names here.
6 DavidC.Rankin Nov 24 2020 at 13:35

Ja, Sie können es tun, und ja, awkist das Werkzeug. Mithilfe von Arrays und Ihrer normalen Dateizeilennummer ( FNR Dateinummer der Datensätze ) und Gesamtzeilen ( NR Datensätze ) können Sie alle Buchstaben aus names.txtdem a[]Array lesen. Anschließend fnokönnen Sie die Dateinummer in der Variablen verfolgen x.txtund alle Hinzufügungen von und dann hinzufügen Bevor Sie die erste Zeile der nächsten Datei ( y.txt) verarbeiten, durchlaufen Sie alle Buchstaben in der letzten Datei. Platzieren Sie für die nicht gesehenen Buchstaben a 0und setzen Sie die Verarbeitung wie gewohnt fort. Wiederholen Sie dies für jede weitere Datei.

Weitere zeilenweise Erklärungen finden Sie in den Kommentaren:

awk '
    FNR==NR {                           # first file
        a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a {                           # if line begins with letter in array
        a[$1] = a[$1]" "$2 # append second field seen[$1]++                      # add letter to seen array
    }
END {
    for (i in a)                        # place zeros for last column
        if (!(i in seen))
            a[i] = a[i]" "0
    for (i in a)                        # print results
        print i a[i]
}' name.txt x.txt y.txt z.txt

Beispiel Verwendung / Ausgabe

Kopieren Sie einfach das Obige und fügen Sie es mit der mittleren Maus in ein xterm mit dem aktuellen Verzeichnis ein, das Ihre Dateien enthält. Sie erhalten:

A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

Erstellen eines in sich geschlossenen Skripts

Wenn Sie ein Skript zum Ausführen erstellen möchten, anstatt es in die Befehlszeile einzufügen, fügen Sie einfach den Inhalt ein (ohne in einfache Anführungszeichen gesetzt) ​​und machen die Datei dann ausführbar. Beispielsweise fügen Sie den Interpreter als erste Zeile und den Inhalt wie folgt ein:

#!/usr/bin/awk -f

FNR==NR {                           # first file
    a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a {                           # if line begins with letter in array
    a[$1] = a[$1]" "$2 # append second field seen[$1]++                      # add letter to seen array
}
END {
    for (i in a)                    # place zeros for last column
        if (!(i in seen))
            a[i] = a[i]" "0
    for (i in a)                    # print results
        print i a[i]
}

awk verarbeitet die als Argumente angegebenen Dateinamen in der angegebenen Reihenfolge.

Beispiel Verwendung / Ausgabe

Mit der Skriptdatei (ich habe sie eingefügt names.awkund dann verwendet chmod +x names.awk, um sie ausführbar zu machen) würden Sie dann Folgendes tun:

$ ./names.awk name.txt x.txt y.txt z.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

Lassen Sie mich wissen, wenn Sie weitere Fragen haben.

4 Sundeep Nov 24 2020 at 14:40

Ein anderer Ansatz mit GNU awk

$ cat script.awk NF == 1 { name[$1] = $1 for (i = 1; i < ARGC - 1; i++) { name[$1] = name[$1] " 0" } next } { name[$1] = gensub(/ ./, " " $2, ARGIND - 1, name[$1])
}

END {
    for (k in name) {
        print name[k]
    }
}

Aufruf des Skripts:

$ awk -f script.awk name.txt {x,y,z}.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

Die Ausgabe zeigt die gleiche Reihenfolge wie name.txt, aber ich denke nicht, dass dies für alle Arten von Eingaben gilt.

3 potong Nov 24 2020 at 19:47

Dies könnte für Sie funktionieren (GNU parallel und Join):

cp name.txt out && t=$(mktemp) && parallel -j1 join -oauto -e0 -a1 out {} \> $t \&\& mv $t out ::: {x,y,z}.txt

Die Ausgabe erfolgt in einer Datei out.

2 DiegoTorresMilano Nov 24 2020 at 15:12

Sie können verwenden join

join -a1 -e0 -o '0,2.2' name.txt x.txt | join -a1 -e0 -o '0,1.2,2.2' - y.txt | join -a1 -e0 -o '0,1.2,1.3,2.2' - z.txt
1 tshiono Nov 24 2020 at 13:48

Mit bashwie wäre es mit:

#!/bin/bash

declare -A hash                                 # use an associative array
for f in "x.txt" "y.txt" "z.txt"; do            # loop over these files
    while read -r key val; do                   # read key and val pairs
        hash[$f,$key]=$val # assign the hash to val done < "$f"
done

while read -r key; do
    echo -n "$key" # print the 1st column for f in "x.txt" "y.txt" "z.txt"; do # loop over the filenames echo -n " ${hash[$f,$key]:-0}"          # print the associated value or "0" if undefined
    done
    echo                                        # put a newline
done < "name.txt"