
Ich habe eine Menge Dateien, aus denen ich bestimmte Zeilen herausnehmen und die herausgenommenen Daten dann in eine Tabelle einfügen muss. Ein Beispiel dafür wäre meine Datei, die Folgendes zeigt:
Name: w
Age: x
Height: y
Weight: z
Ich möchte nur das Alter, die Größe und das Gewicht, also habe ich zuerst Folgendes eingegeben:
grep -E 'Age|Height|Weight' [input file] > output.txt
Aufgrund der Anzahl der Dateien sieht meine Ausgabe jetzt so aus
Age 1
Height 1
Weight 1
Age 2
Height 2
Weight 2
etc...
Was ich jetzt möchte, ist, ein awk-Skript auszuführen, das meine neue Datei output.txt durchgeht und zuerst alle Zeilen mit dem Wort „Alter“ findet und diese dann ausdruckt. Wenn alle Zeilen mit „Alter“ durch sind, werden die Größe und dann das Gewicht ermittelt. Ich habe das Skript ausgeführt:
awk -F"\t" '/Age/ {print} /Height/ {print}' output.txt >output2.txt
Aber es druckt es einfach wie die ursprüngliche Ausgabedatei. Wie ändere ich es, sodass es, nachdem es alle Altersangaben gemacht hat, erst die Größenangaben findet?
BEARBEITEN:
Die gewünschte Ausgabe ist die Datei
Alter 1
Alter 2
Höhe 1
Höhe 2
Gewicht 1
Gewicht 2
usw..
Nur zur Klarstellung: Alter 1 ist die Zeile mit „Alter“ aus Datei 1 usw.
Antwort1
awk durchläuft die Datei standardmäßig nur einmal und führt alle Blöcke der Reihe nach aus. Deshalb erhalten Sie die Ausgabe, die Sie erhalten haben. Sie können das gewünschte Verhalten erreichen, indem Sieeine Anordnungum die Zeilen direkt zu speichern und die Datei trotzdem nur einmal zu verarbeiten:
BEGIN {
AgeIndex = 1
HeightIndex = 1
}
/Age/ {
ages[AgeIndex] = $0
AgeIndex+=1
}
/Height/ {
heights[HeightIndex] = $0
HeightIndex+=1
}
END {
for (x = 1; x < AgeIndex; x++)
print ages[x] "\n"
for (x = 1; x < HeightIndex; x++)
print heights[x] "\n"
}
Speichern Sie das beispielsweise in filter.awk
und führen Sie dann Folgendes aus:
awk -f filter.awk output.txt > output2.txt
um die gewünschte Ausgabe zu erhalten:
$ awk -f filter.awk < data
Age 1
Age 2
Height 1
Height 2
Wir erstellen zwei Arrays ages
und heights
speichern dabei jede passende Zeile darin. AgeIndex
gibt an, wie weit wir im Array gekommen sind. Am Ende drucken wir jede Zeile aus, die wir gespeichert haben (und eine zusätzliche neue Zeile, wie Sie möchten), zuerst alle Altersangaben, dann alle Körpergrößen.
Die Arrays halten am Ende die gesamte Datei im Speicher. Wenn Ihre Datei also besonders groß ist, müssen Sie diesen Speicherverbrauch gegen die Zeit eintauschen, die Sie für das mehrmalige Durchgehen der gesamten Datei benötigen. An diesem Punkt ist es im Wesentlichen dasselbe wie bei einem Programm in jeder anderen Sprache. Wenn Sie keinen besonderen Grund haben, awk zu verwenden, ziehen Sie möglicherweise eine andere Sprache vor. Ehrlich gesagt würde ich das empfehlen – awk bringt Ihnen hier nicht viel.
Antwort2
Mit gawk
:
$ awk -F"\t" '
{ a[$1]++ }
END {
n = asorti(a,b);
for (i = 1; i <= n; i++) {
print b[i];
if (i%2 == 0) {
printf "\n";
}
}
}
' output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
Antwort3
Ich gehe davon aus, dass die Leerzeilen nicht Teil Ihrer eigentlichen Datei sind oder dass sie Ihnen zumindest egal sind. Wenn das der Fall ist, brauchen Sie nur sort
:
$ cat output.txt
Age 1
Height 1
Weight 1
Age 2
Height 2
Weight 2
$ sort output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
Sofern Ihre Dateien jedoch nicht zu groß für den Speicher sind, ist es möglicherweise einfacher, die ganze Sache in einem einzigen Schritt zu erledigen:
grep -whE 'Age|Height|Weight' *txt | sort > outfile
Das Obige sucht nach Age
oder Height
oder in allen Dateien, deren Name im aktuellen Verzeichnis Weight
endet ( ). Das bedeutet „nur ganze Wörter abgleichen“ (das trifft also beispielsweise nicht zu), das ist notwendig, weil ohne es der Name der Datei zusammen mit der übereinstimmenden Zeile gedruckt wird, wenn mehr als eine Eingabedatei angegeben ist. Das ermöglicht erweiterte reguläre Ausdrücke, die uns für ODER geben.txt
*txt
-w
Age
Ageing
-h
-E
|
NOTIZ: Wenn Sie aus irgendeinem Grund tatsächlich die zusätzliche Leerzeile zwischen den einzelnen Einträgen wünschen (was Ihr grep
Befehl jedoch nicht bewirken würde), können Sie sie folgendermaßen hinzufügen:
grep -whE 'Age|Height|Weight' *txt | sort | sed 's/$/\n/'
Beispiel
$ for i in {1..3}; do echo -e "Name $i\nAge $i\nHeight $i\nWeight $i" > $i.txt; done
$ for f in *txt; do echo " -- $f --"; cat $f; done
-- 1.txt --
Name 1
Age 1
Height 1
Weight 1
-- 2.txt --
Name 2
Age 2
Height 2
Weight 2
-- 3.txt --
Name 3
Age 3
Height 3
Weight 3
$ grep -whE 'Age|Height|Weight' *txt | sort
Age 1
Age 2
Age 3
Height 1
Height 2
Height 3
Weight 1
Weight 2
Weight 3
Selbst wenn sort
es für Sie nicht das Richtige ist, würde ich Folgendes in Perl tun awk
(vorausgesetzt, Sie möchten die zusätzlichen Leerzeilen, was wahrscheinlich wiederum nicht der Fall ist):
$ perl -ane '$k{$F[0]}.=$_."\n" if /./;
END{print $k{$_},"\n" for sort keys (%k)}' output.txt
Age 1
Age 2
Height 1
Height 2
Weight 1
Weight 2
Sie können dies weitergeben, head -n -2
um die letzten beiden Leerzeilen zu entfernen, wenn Sie sie nicht möchten.
Antwort4
python
Lösung für dieses Problem:
from collections import defaultdict
f = open("output.txt", "r")
d = defaultdict(list)
for line in f:
line = line.strip()
if line != '':
arr = line.split(" ")
d[arr[0]].append(arr[1])
print d.items()
Ich habe die erste Spalte gehasht und in eine Liste eingefügt.