Entfernen Sie Zeilen aus einer Datei, abhängig von den Zeilen in einer anderen Datei

11

Die Datei file1.txt enthält Zeilen wie:

/api/purchase/<hash>/index.html

Beispielsweise:

/api/purchase/12ab09f46/index.html

Die Datei file2.csv enthält Zeilen wie:

<hash>,timestamp,ip_address

Beispielsweise:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Ich möchte file2.csv filtern und alle Zeilen entfernen, in denen der Wert von Hash auch in file1.txt vorhanden ist. Das heißt:

cat file1.txt | extract <hash> | sed '/<hash>/d' file2.csv

oder sowas.

Es sollte einfach sein, aber ich scheine nicht in der Lage zu sein, es zum Laufen zu bringen.

Kann jemand bitte eine funktionierende Pipeline für diese Aufgabe bereitstellen?

Marco Faustinelli
quelle

Antworten:

13

cut -d / -f 4 file1.txt | paste -sd '|' | xargs -I{} grep -v -E {} file2.csv

Erläuterung:

cut -d / -f 4 file1.txt wählt die Hashes aus der ersten Datei aus

paste -sd '|' verbindet alle Hashes zu einem regulären Ausdruck ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvruft grep mit dem vorherigen Muster als Argument auf, xargs ersetzt {}durch den Inhalt vonSTDIN

Wenn Sie nicht haben, können pasteSie es durch ersetzentr "\\n" "|" | sed 's/|$//'

Gabriele Lana
quelle
3
+1, aber keine Notwendigkeit cat, nur cut -d / -f 4 file1.txt. Oder wenn Sie den sequentiellen Look bevorzugen,<file1.txt cut -d / -f 4
Sparhawk
@ Sparhawk danke! Ich wusste es nicht ;-) Lösung aktualisiert :-)
Gabriele Lana
11

Mögliche awkLösung:

awk 'NR == FNR { x[$4] = 1; next; } { if (!($1 in x)) print $0; }' FS="/" file1.txt FS="," file2.txt

Zuerst lesen wir file1.txtmit FS(Feldtrennzeichen) "/" und erstellen das Array x mit Schlüsselwerten aus dem Feld, $4das der gewünschte Hash ist. Als nächstes werden wir zweite Datei lesen file2.txtEinstellung FSsein ,und zu prüfen , ob Wert des Feldes $1nicht als Schlüssel in einem Array existiert xund wenn es uns es nicht gedruckt wird .
Das gleiche idiomatischere wie in den Kommentaren vorgeschlagen könnte sein:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
Taliezin
quelle
Ich schätze Ihre Bemühungen, aber ich fürchte, das fliegt weit über meinen Kopf. Ich hoffe weiterhin, dass eine Lösung auf der Basis einer Mischung aus Sed / Grep / Katze möglich sein wird.
Marco Faustinelli
1
Ich werde eine Erklärung hinzufügen, es ist einfach. Und vielleicht schlägt jemand eine Lösung mit den von Ihnen gewünschten Tools vor.
Taliezin
Warum nicht einfach !($1 in x)statt{ if (!($1 in x)) print $0; }
iruvar
@ 1_CR es ist meine schlechte Angewohnheit, ich weiß, dass es idiomatischer sein könnte, aber ich denke immer, dass es einfacher sein wird, OP zu erklären.
Taliezin
@Muzietto noch, ich denke, es schadet nicht, wenn man anfängt, andere Tools wie diese awkbasierte Lösung zu lernen ... auf lange Sicht werden Sie lernen, sich für Lösungen zu interessieren, die der Einfachheit halber mit weniger Rohren erreicht werden können ... :)
hjk
5

Für GNU sed

sed -z 's%.*/\([^/]*\)/index.html\n%\1\\|%g;s%^%/%;s%\\|$%/d%' file1.csv |
sed -f - file2.csv

wo erste sed produzieren Liste von Hashes in sed-Befehl-Format wie /12ab09f46\|a77b3ff22\|..../des übertragen nächsten sed -script die daher von der Eingabe über Befehl liest -f -Option.
Gleiches gilt für grep

grep -oP '[^/]*(?=/index.html$)' file1.csv | grep -Fvf - file2.csv

oder ohne Perl-Ausdrücke:

grep -o '[^/]*/index.html$' file1.csv | 
grep -o '^[^/]*' | 
grep -Fvf - file2.csv

oder noch besser mit Schnitt :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Costas
quelle
Das sieht für mich so aus, wie ich es gesucht habe. Können Sie es bitte etwas veranschaulichen? Ich kann nicht sehen, wie der zweite Befehl Zeilen aus file2.csv entfernt.
Marco Faustinelli
@ Muzietto Siehe aktualisiert
Costas
2
#!/bin/bash
cut -d, -f1 file2 | while read key ; do 
   #check for appearance in file1 with successful grep:
   #exit status is 0 if pattern is found, only search for at least 1
   #appearance -> to speed it up
   if [[ $(grep -m 1 "/$key/" file1) ]] ; then
      sed "/^$key,/d" -i file2
      #note that we are gradually overwriting file2 (-i option),
      #so make a backup!
   fi
done

Beachten Sie, dass die Suchstiche /$key/und ^$key,um die Ergebnisse zu reduzieren, entweder zwischen zwei Schrägstrichen (Datei 1) oder als erster Eintrag einer Zeile und gefolgt von einem Komma (Datei 2) liegen. Dies sollte es sicher machen, wenn Schlüssel aussehen

a,values
a1,values

in Datei 2 oder ähnlich

/api/../a1/../
/api/../a/../

in Datei 1

Fiximan
quelle
2

Ich habe gerade den folgenden Liner ausprobiert und er scheint den Job zu machen:

 for i in `cat file1.txt  | awk -F"/" '{print $4}'`; do echo "\n $i" ; sed -ri "/^$i,/d" file2.csv ; done

Bitte ersetzen Sie zuerst -ri durch -re , um es zu testen. -re macht einen Trockenlauf, und wenn alles in Ordnung ist, kannst du ihn mit -ri laufen lassen

Primero
quelle
mmmh, ich habe die Ausgabe Ihres Codes in eine temporäre Datei umgeleitet und sie enthält ungefähr 30.000 Zeilen, während file2.csv anfangs 240 hat und gefiltert werden soll.
Marco Faustinelli
Nun, ich denke, das liegt daran, dass ich jeden Hash in der ersten Datei drucke, wenn ich die Ersetzung durchführe (das Echo "\ n" $ i Teil). Wie auch immer, wenn Sie es mit -ri ausführen, müssen Sie nicht umleiten, da es die Ersetzung an Ort und Stelle
Primero
Wenn Sie mit -re ausführen und umleiten, wird Datei2 für so viele Hashes wiederholt, wie Sie in der ersten Datei haben. Grundsätzlich ersetzt jeder Hash in der ersten Datei ihn in der zweiten Datei und druckt das Ergebnis aus. Deshalb haben Sie so viele Zeilen.
Primero
1

Zusätzlich zur Antwort von Gabriele Lana beachten Sie bitte, dass für den BSD-Einfügebefehl ein Bindestrich angegeben werden muss, um Inhalte von der Standardeingabe lesen zu können.

Handbuch des Einfügebefehls

Wenn für eine oder mehrere der Eingabedateien '-' angegeben ist, wird die Standardeingabe verwendet. Die Standardeingabe wird zeilenweise zirkulär für jede Instanz von '-' gelesen.

Das letzte muss also wie unten geändert werden

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
efesaid
quelle