Bash Hinzufügen / Anhängen neuer Spalten aus anderen Dateien
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 awk
und 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 j1
für parallel
aufgrund 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 paste
es von Natur aus parallel ist.
Antworten
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
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 0
Element 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.
Ja, Sie können es tun, und ja, awk
ist 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.txt
dem a[]
Array lesen. Anschließend fno
können Sie die Dateinummer in der Variablen verfolgen x.txt
und 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 0
und 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.awk
und 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.
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.
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
.
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
Mit bash
wie 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"