Ich habe eine riesige tabulatorgetrennte Datei, die so formatiert ist
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
Ich möchte es auf effiziente Weise nur mit Bash-Befehlen transponieren (ich könnte dazu ein Perl-Skript mit etwa zehn Zeilen schreiben, aber es sollte langsamer ausgeführt werden als die nativen Bash-Funktionen). Die Ausgabe sollte also so aussehen
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
Ich dachte an eine solche Lösung
cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done
Aber es ist langsam und scheint nicht die effizienteste Lösung zu sein. Ich habe in diesem Beitrag eine Lösung für vi gesehen , aber sie ist immer noch zu langsam. Irgendwelche Gedanken / Vorschläge / geniale Ideen? :-)
Antworten:
Ausgabe
Leistung gegen Perl-Lösung von Jonathan in einer 10000-Zeilen-Datei
EDIT von Ed Morton (@ ghostdog74 kann jederzeit gelöscht werden, wenn Sie dies ablehnen).
Vielleicht hilft diese Version mit einigen expliziteren Variablennamen dabei, einige der folgenden Fragen zu beantworten und allgemein zu klären, was das Skript tut. Es werden auch Tabulatoren als Trennzeichen verwendet, nach denen das OP ursprünglich gefragt hatte, damit leere Felder verarbeitet werden können, und es verschönert zufällig die Ausgabe für diesen speziellen Fall ein wenig.
Die oben genannten Lösungen funktionieren in jedem awk (außer natürlich alten, kaputten awk - dort YMMV).
Die oben genannten Lösungen lesen jedoch die gesamte Datei in den Speicher. Wenn die Eingabedateien dafür zu groß sind, können Sie dies tun:
Dies verwendet fast keinen Speicher, liest jedoch die Eingabedatei einmal pro Anzahl von Feldern in einer Zeile, sodass sie viel langsamer ist als die Version, die die gesamte Datei in den Speicher liest. Es geht auch davon die Anzahl der Felder in jeder Zeile das gleiche ist und verwendet GNU awk für
ENDFILE
undARGIND
aber jede awk kann auf das gleiche mit Tests durchführenFNR==1
undEND
.quelle
Eine andere Option ist
rs
:-c
Ändert das Trennzeichen für Eingabespalten,-C
ändert das Trennzeichen für Ausgabespalten und-T
transponiert Zeilen und Spalten. Verwenden Sie nicht-t
anstelle von-T
, da eine automatisch berechnete Anzahl von Zeilen und Spalten verwendet wird, die normalerweise nicht korrekt ist.rs
, das nach der Umformungsfunktion in APL benannt ist, wird mit BSDs und OS X geliefert, sollte jedoch von Paketmanagern auf anderen Plattformen verfügbar sein.Eine zweite Option ist die Verwendung von Ruby:
Eine dritte Option ist
jq
:jq -R .
druckt jede Eingabezeile als JSON-Zeichenfolgenliteral,-s
(--slurp
) erstellt ein Array für die Eingabezeilen, nachdem jede Zeile als JSON analysiert wurde, und-r
(--raw-output
) gibt den Inhalt von Zeichenfolgen anstelle von JSON-Zeichenfolgenliteralen aus. Der/
Operator ist überladen, um Zeichenfolgen zu teilen.quelle
rs
- danke für den Zeiger! (Der Link ist zu Debian; der Upstream scheint mirbsd.org/MirOS/dist/mir/rs zu sein )rs
, die mit OS X geliefert wird , wird-c
allein das Trennzeichen für die Eingabespalte auf eine Registerkarte gesetzt.$'\t'
TTC TTA TTC TTC TTT
,rs -c' ' -C' ' -T < rows.seq > cols.seq
gibt das Ausführenrs: no memory: Cannot allocate memory
. Dies ist ein System, auf dem FreeBSD 11.0-RELEASE mit 32 GB RAM ausgeführt wird. Ich vermute also, dassrs
alles im RAM gespeichert wird, was gut für die Geschwindigkeit ist, aber nicht für große Datenmengen.Eine Python-Lösung:
Das Obige basiert auf Folgendem:
Dieser Code setzt voraus, dass jede Zeile die gleiche Anzahl von Spalten hat (es wird kein Auffüllen durchgeführt).
quelle
l.split()
durchl.strip().split()
(Python 2.7), sonst ist die letzte Zeile der Ausgabe verkrüppelt. Funktioniert für beliebige Spaltentrennzeichen. Verwenden Siel.strip().split(sep)
und,sep.join(c)
wenn Ihr Trennzeichen in einer Variablen gespeichert istsep
.Das Transponierungsprojekt auf SourceForge ist genau dafür ein Coreutil-ähnliches C-Programm.
quelle
-b
und-f
Argumente.Pure BASH, kein zusätzlicher Prozess. Eine schöne Übung:
quelle
printf "%s\t" "${array[$COUNTER]}"
Schauen Sie sich GNU Datamash an, das wie verwendet werden kann
datamash transpose
. Eine zukünftige Version wird auch Kreuztabellen (Pivot-Tabellen) unterstützen.quelle
Hier ist ein mäßig solides Perl-Skript, um die Arbeit zu erledigen. Es gibt viele strukturelle Analogien zur
awk
Lösung von @ ghostdog74 .Bei der Größe der Probendaten war der Leistungsunterschied zwischen Perl und Awk vernachlässigbar (1 Millisekunde von insgesamt 7). Mit einem größeren Datensatz (100x100-Matrix, Einträge mit jeweils 6-8 Zeichen) übertraf Perl awk - 0,026s gegenüber 0,042s leicht. Beides dürfte kein Problem sein.
Repräsentative Timings für Perl 5.10.1 (32-Bit) vs awk (Version 20040207 bei Angabe von '-V') vs gawk 3.1.7 (32-Bit) unter MacOS X 10.5.8 in einer Datei mit 10.000 Zeilen mit 5 Spalten pro Linie:
Beachten Sie, dass Gawk auf diesem Computer viel schneller als Awk ist, aber immer noch langsamer als Perl. Ihr Kilometerstand wird natürlich variieren.
quelle
Wenn Sie
sc
installiert haben, können Sie Folgendes tun:quelle
sc
die Spalten als ein oder eine Kombination aus zwei Zeichen benannt werden. Die Grenze ist26 + 26^2 = 702
.Hierfür gibt es ein spezielles Dienstprogramm:
GNU Datamash-Dienstprogramm
Entnommen von dieser Website, https://www.gnu.org/software/datamash/ und http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods
quelle
Angenommen, alle Ihre Zeilen haben die gleiche Anzahl von Feldern, löst dieses awk-Programm das Problem:
In Worten, wenn Sie die Zeilen durchlaufen,
f
wächst für jedes Feld eine durch ':' getrennte Zeichenfolge,col[f]
die die Elemente dieses Felds enthält. Nachdem Sie mit allen Zeilen fertig sind, drucken Sie jede dieser Zeichenfolgen in einer separaten Zeile. Sie können dann das gewünschte Trennzeichen (z. B. ein Leerzeichen) durch ':' ersetzen, indem Sie die Ausgabe durchleitentr ':' ' '
.Beispiel:
quelle
GNU Datamash ist mit nur einer Codezeile und möglicherweise beliebig großer Dateigröße perfekt für dieses Problem geeignet!
quelle
Eine hackige Perl-Lösung kann so sein. Es ist schön, weil es nicht die gesamte Datei in den Speicher lädt, temporäre Zwischendateien druckt und dann die wundervolle Paste verwendet
quelle
Die einzige Verbesserung, die ich an Ihrem eigenen Beispiel sehen kann, ist die Verwendung von awk, wodurch die Anzahl der ausgeführten Prozesse und die Datenmenge, die zwischen ihnen geleitet wird, verringert werden:
quelle
Normalerweise verwende ich dieses kleine
awk
Snippet für diese Anforderung:Dadurch werden nur alle Daten in ein zweidimensionales Array geladen
a[line,column]
und anschließend als gedruckta[column,line]
, sodass die angegebene Eingabe transponiert wird.Dies muss die
max
Anzahl der Spalten in der ursprünglichen Datei verfolgen , damit sie als Anzahl der Zeilen verwendet werden kann, die zurückgedruckt werden sollen.quelle
Ich habe die Lösung von fgm verwendet (danke fgm!), Musste aber die Tabulatorzeichen am Ende jeder Zeile entfernen, also habe ich das Skript folgendermaßen geändert:
quelle
Ich war nur auf der Suche nach einer ähnlichen Bash-Übertragung, aber mit Unterstützung für die Polsterung. Hier ist das Skript, das ich basierend auf der Lösung von fgm geschrieben habe und das zu funktionieren scheint. Wenn es hilfreich sein kann ...
quelle
Ich suchte nach einer Lösung, um jede Art von Matrix (nxn oder mxn) mit jeder Art von Daten (Zahlen oder Daten) zu transponieren und bekam die folgende Lösung:
quelle
Wenn Sie nur eine einzelne (durch Kommas getrennte) Zeile $ N aus einer Datei nehmen und in eine Spalte umwandeln möchten:
quelle
Nicht sehr elegant, aber dieser "einzeilige" Befehl löst das Problem schnell:
Hier ist cols die Anzahl der Spalten, in denen Sie 4 durch ersetzen können
head -n 1 input | wc -w
.quelle
Eine andere
awk
Lösung und begrenzte Eingabe mit der Größe des Speichers, den Sie haben.Dies verbindet jede Position der gleichen abgelegten Nummer miteinander und
END
druckt das Ergebnis aus, das die erste Zeile in der ersten Spalte, die zweite Zeile in der zweiten Spalte usw. wäre. Wird ausgegeben:quelle
Einige * nix- Standards verwenden Einzeiler, es werden keine temporären Dateien benötigt. NB: Das OP wollte eine effiziente Lösung (dh schneller), und die Top-Antworten sind normalerweise schneller als diese Antwort. Diese Einzeiler sind für diejenigen gedacht, die * nix- Softwaretools aus welchen Gründen auch immer mögen . In seltenen Fällen ( z. B. knappe E / A und Speicher) können diese Snippets tatsächlich schneller sein als einige der wichtigsten Antworten.
Rufen Sie die Eingabedatei foo auf .
Wenn wir wissen, dass foo vier Spalten hat:
Wenn wir nicht wissen, wie viele Spalten foo hat:
xargs
hat eine Größenbeschränkung und würde daher unvollständige Arbeit mit einer langen Datei machen. Welche Größenbeschränkung ist systemabhängig, z.tr
&echo
:... oder wenn die Anzahl der Spalten unbekannt ist:
Unter Verwendung
set
, die wiexargs
, hat ähnliche Kommandozeile Größe basierend Einschränkungen:quelle
awk
.cut
,head
,echo
Etc. sind nicht mehr POSIX kompatible Shell - Code als einawk
Skript ist - sie alle auf jeder UNIX - Installation Standard sind. Es gibt einfach keinen Grund, eine Reihe von Tools zu verwenden, bei denen Sie in Kombination vorsichtig mit dem Inhalt Ihrer Eingabedatei und dem Verzeichnis sein müssen, aus dem Sie das Skript ausführen, wenn Sie nur awk verwenden können und das Endergebnis schneller und robuster ist .for f in cut head xargs seq awk ; do wc -c $(which $f) ; done
Wenn der Speicher zu langsam oder die E / A zu niedrig ist, verschlimmern größere Dolmetscher die Situation, egal wie gut sie unter idealeren Umständen sind. Grund Nr. 2: awk (oder fast jede Sprache) leidet auch unter einer steileren Lernkurve als ein kleines Dienstprogramm, das entwickelt wurde, um eine Sache gut zu machen. Wenn die Laufzeit billiger ist als die Arbeitsstunden des Programmierers, spart das einfache Codieren mit "Software-Tools" Geld.eine andere Version mit
set
eval
quelle
Eine weitere Bash-Variante
Skript
Ausgabe
quelle
Hier ist eine Haskell-Lösung. Wenn es mit -O2 kompiliert wird, läuft es etwas schneller als Ghostdogs Awk und etwas langsamer als Stephans
dünn umwickelte C-Python auf meinem Computer für wiederholte "Hallo Welt" -Eingabezeilen. Leider gibt es, soweit ich das beurteilen kann, keine Unterstützung für die Übergabe von Befehlszeilencode durch GHC. Sie müssen diese daher selbst in eine Datei schreiben. Die Zeilen werden auf die Länge der kürzesten Zeile abgeschnitten.quelle
Eine awk-Lösung, die das gesamte Array im Speicher speichert
Wir können die Datei jedoch so oft "durchlaufen", wie Ausgabezeilen benötigt werden:
Welche (für eine geringe Anzahl von Ausgabezeilen ist schneller als der vorherige Code).
quelle
Hier ist ein Bash-Einzeiler, der darauf basiert, jede Zeile einfach in eine Spalte umzuwandeln und
paste
zusammenzufügen:m.txt:
Erstellt eine
tmp1
Datei, damit sie nicht leer ist.liest jede Zeile und wandelt sie mit in eine Spalte um
tr
Fügt die neue Spalte in die
tmp1
Datei einKopien ergeben sich wieder in
tmp1
.PS: Ich wollte unbedingt Io-Deskriptoren verwenden, konnte sie aber nicht zum Laufen bringen.
quelle
Ein Oneliner mit R ...
quelle
Ich habe unten zwei Skripte verwendet, um ähnliche Vorgänge auszuführen. Der erste ist in awk, was viel schneller ist als der zweite, der in "pure" bash ist. Möglicherweise können Sie es an Ihre eigene Anwendung anpassen.
quelle