Wählen Sie Zeilen aus einer Textdatei aus, deren IDs in einer anderen Datei aufgeführt sind


13

Ich verwende viel grep awk-Sortierung in meiner Unix-Shell, um mit mittelgroßen (ca. 10 bis 100 Millionen Zeilen) tabulatorgetrennten Spaltentextdateien zu arbeiten. In dieser Hinsicht ist die Unix-Shell meine Tabelle.

Aber ich habe ein großes Problem, nämlich die Auswahl von Datensätzen mit einer Liste von IDs.

Wenn Sie eine table.csvDatei mit Format id\tfoo\tbar...und eine ids.csvDatei mit einer Liste von IDs haben, wählen Sie nur Datensätze aus, in table.csvdenen eine ID vorhanden ist ids.csv.

Art von /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids, aber mit Shell, nicht Perl.

grep -Ferzeugt offensichtlich falsch positive Ergebnisse, wenn die IDs eine variable Breite haben. joinist ein Dienstprogramm, das ich nie herausfinden konnte. Zuallererst erfordert es eine alphabetische Sortierung (meine Dateien sind normalerweise numerisch sortiert), aber selbst dann kann ich es nicht zum Laufen bringen, ohne mich über falsche Reihenfolge zu beschweren und einige Datensätze zu überspringen. Also ich mag es nicht. grep -f gegen Datei mit ^id\t-s ist sehr langsam, wenn die Anzahl der IDs groß ist. awkist umständlich.

Gibt es dafür gute Lösungen? Gibt es spezielle Tools für durch Tabulatoren getrennte Dateien? Zusätzliche Funktionen sind ebenfalls sehr willkommen.

UPD: Korrigiert sort->join


Wenn grep -fes zu langsam ist, klingt die Beibehaltung dieser Strategie nach mehr Ärger als es wert ist - Variationen werden wahrscheinlich denselben O (N * M) -Leistungsproblemen zum Opfer fallen. Vielleicht ist es besser, wenn Sie lernen, wie man eine normalisierte SQL-
Datenbank verwendet

1
Warum nicht das Perl-Skript aus der von Ihnen verlinkten Frage verwenden? Alternativ sollte es möglich sein, ein ähnliches Skript zu schreiben awk.
cjm

Bash 4 verfügt über assoziative Arrays. Dies ist erforderlich, um die verschachtelten Schleifen nach dem Perl-Beispiel zu umgehen.
Goldlöckchen

1
sortkann alle Arten von Sortierungen, numerischen, alphabetischen und anderen durchführen. Siehe man sort.
Terdon

Ich habe hier eine Frage, wie machen wir dasselbe, wenn die Quelldatei, aus der wir die Daten extrahieren möchten, eine nicht begrenzte Datei ist

Antworten:


19

Ich denke du meintest es grep -fnicht, grep -Faber du brauchst tatsächlich eine Kombination aus beidem und -w:

grep -Fwf ids.csv table.csv

Der Grund, warum Sie falsch positive Ergebnisse erhalten haben, ist (ich denke, Sie haben es nicht erklärt), denn wenn eine ID in einer anderen enthalten sein kann, werden beide gedruckt. -wBehebt dieses Problem und -Fstellt sicher, dass Ihre Muster als Zeichenfolgen und nicht als reguläre Ausdrücke behandelt werden. Von man grep:

   -F, --fixed-strings
          Interpret PATTERN as a  list  of  fixed  strings,  separated  by
          newlines,  any  of  which is to be matched.  (-F is specified by
          POSIX.)
   -w, --word-regexp
          Select  only  those  lines  containing  matches  that form whole
          words.  The test is that the matching substring must  either  be
          at  the  beginning  of  the  line,  or  preceded  by  a non-word
          constituent character.  Similarly, it must be either at the  end
          of  the  line  or  followed by a non-word constituent character.
          Word-constituent  characters  are  letters,  digits,   and   the
          underscore.

   -f FILE, --file=FILE
          Obtain  patterns  from  FILE,  one  per  line.   The  empty file
          contains zero patterns, and therefore matches nothing.   (-f  is
          specified by POSIX.)

Wenn Ihre Fehlalarme darauf zurückzuführen sind, dass eine ID in einem Nicht-ID-Feld vorhanden sein kann, durchlaufen Sie stattdessen Ihre Datei:

while read pat; do grep -w "^$pat" table.csv; done < ids.csv

oder schneller:

xargs -I {} grep "^{}" table.csv < ids.csv

Persönlich würde ich dies jedoch tun perl:

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv

1
+1 Aber: Was ist, wenn es potenzielle Fehlalarme gibt, die genau wortweise mit der ID übereinstimmen, nur nicht in der ID-Spalte? Wenn Sie nicht ^mit -F verwenden können, können Sie die erste Spalte nicht speziell ausrichten.
Goldlöckchen

@goldilocks Wenn sie genau übereinstimmen, sind sie keine Fehlalarme. Ich verstehe, was Sie meinen, aber in diesem Fall sollte das OP ihre Eingabedateien anzeigen.
Terdon

Das ^id\tvom OP implizierte Bit kann idin einer anderen Spalte auftreten. Wenn nicht, spielt das keine Rolle.
Goldlöckchen

@ Goldlöckchen fair Punkt, Antwort bearbeitet.
Terdon

Die Art und Weise, wie wir dies taten, bestand darin, temporäre Dateien (mit awk oder sed) zu erstellen, die ein eindeutiges Zeichen (z. B. Steuerelement-A) hinzufügten, das das gesuchte Feld abgrenzte, und dann grep -F -f temppatternfile intentargetfile | zu verwenden tr -d '\ 001'
Mark Plotnick

7

Das joinDienstprogramm ist das, was Sie wollen. Die Eingabedateien müssen lexikalisch sortiert sein.

Angenommen, Ihre Shell ist bash oder ksh:

join -t $'\t' <(sort ids.csv) <(sort table.csv)

Ohne sortieren zu müssen, ist die übliche awk-Lösung

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv

Wie ich versucht habe, aber letztendlich nicht zu vermitteln war, ist Join ein Kludge. Funktioniert bei mir nicht so gut.
Alamar

1
joinist kein Kludge: Ihre Worte waren, als Sie es nicht herausfinden konnten. Öffne deinen Geist und lerne. Welche Ausgabe haben Sie erhalten und wie unterscheidet sich das von Ihren Erwartungen?
Glenn Jackman

+1, das ist ein Job für join.
don_crissti

Die awkLösung hier ist für meine Zwecke sehr schnell und effizient (ich extrahiere Teilmengen von einigen hundert aus Dateien mit ~ 100 Millionen Zeilen)
Luke

2

Die Antworten auf diese SO-Frage haben mir geholfen, die Probleme mit Join zu umgehen. Wenn Sie die Datei sortieren, um sie für den Beitritt zu senden, müssen Sie im Wesentlichen sicherstellen, dass Sie nach der Spalte sortieren, zu der Sie beitreten. Wenn dies also das erste ist, müssen Sie ihm mitteilen, was das Trennzeichen in der Datei ist und dass es nach dem ersten Feld (und nur nach dem ersten Feld) sortiert werden soll. Andernfalls können Ihre Trennzeichen und möglicherweise andere Felder die Sortierreihenfolge beeinflussen, wenn das erste Feld beispielsweise eine variable Breite hat.

Verwenden Sie also die Sortieroption -t, um Ihr Trennzeichen anzugeben, und die Option -k, um das Feld anzugeben (denken Sie daran, dass Sie ein Start- und ein Endfeld benötigen - auch wenn es dasselbe ist - oder es wird nach diesem Zeichen sortiert bis zum Ende der Zeile).

Für eine durch Tabulatoren getrennte Datei wie in dieser Frage sollte Folgendes funktionieren (dank der Antwort von glenn für die Struktur):

join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv

(Als Referenz bedeutet das Flag -d die Wörterbuchsortierung. Möglicherweise möchten Sie auch das Flag -b verwenden, um führende Leerzeichen zu ignorieren (siehe man sortund man join).

Angenommen, Sie verbinden zwei durch Kommas getrennte Dateien - input1.csvin der dritten und input2.csvin der vierten Spalte . Du könntest benutzen

join -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv

Hier geben die Optionen -1und an -2, welche Felder in der ersten bzw. zweiten Eingabedatei verknüpft werden sollen.


0

Sie können Ruby auch verwenden, um etwas Ähnliches zu tun:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.