Rimuovere le righe da un file in base alle righe trovate in un altro file


11

Il file file1.txt contiene righe come:

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

Per esempio:

/api/purchase/12ab09f46/index.html

Il file file2.csv contiene righe come:

<hash>,timestamp,ip_address

Per esempio:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Voglio filtrare file2.csv rimuovendo tutte le righe in cui il valore di hash è presente anche in file1.txt. Questo è da dire:

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

o qualcosa del genere.

Dovrebbe essere semplice, ma mi sembra incapace di farlo funzionare.

Qualcuno può fornire una pipeline funzionante per questa attività?

Risposte:


13

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

Spiegazione:

cut -d / -f 4 file1.txt selezionerà gli hash dal primo file

paste -sd '|' unirà tutti gli hash in un'espressione regolare ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvinvocherà grep con il modello precedente come argomento, xargs sostituirà {}con il contenuto diSTDIN

Se non lo hai, pastepuoi sostituirlo contr "\\n" "|" | sed 's/|$//'


3
+1 ma non è necessario cat, solo cut -d / -f 4 file1.txt. O se preferisci l'aspetto sequenziale,<file1.txt cut -d / -f 4
Sparhawk,

@Sparhawk grazie! Non lo sapevo ;-) soluzione aggiornata :-)
Gabriele Lana,

11

Possibile awksoluzione:

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

Per prima cosa leggiamo file1.txtusando FS(separatore di campo) "/" e creiamo la matrice x con i valori delle chiavi dal campo $4che è l'hash che desideri. Successivo leggiamo secondo file file2.txtimpostazione FSdi essere ,e verificare se il valore di campo $1non esiste come chiave in ordine xe se non ci stamparlo.
Lo stesso più idiomatico proposto nei commenti potrebbe essere:

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

Apprezzo il tuo sforzo, ma temo che questo vola molto sopra la mia testa. Continuo a sperare che sia possibile una soluzione basata su una miscela sed / grep / cat.
Marco Faustinelli,

1
Aggiungerò una spiegazione, è semplice. E potrebbe essere qualcuno proporrà una soluzione con gli strumenti che desideri.
Taliezin,

Perché non solo !($1 in x)invece di{ if (!($1 in x)) print $0; }
iruvar il

@ 1_CR è la mia cattiva abitudine, so che potrebbe essere più idiomatica, ma penso sempre che sarà più semplice per una spiegazione all'OP.
Taliezin,

@Muzietto ancora, penso che non ci sia nulla di male a cominciare ad apprendere altri strumenti come questa awksoluzione basata su ... a lungo termine, imparerai a gravitare verso soluzioni che possono essere ottenute usando semplici tubi per semplicità ... :)
HJK

5

Per GNU sed

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

dove prima sed produce un elenco di hash in formato sed-command-like /12ab09f46\|a77b3ff22\|..../de lo trasferisce al successivo sed -script che legge sopra il comando dall'input quindi l' -f -opzione.
Lo stesso con grep

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

o senza perl-expresions:

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

o meglio ancora con il taglio :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv

Questo mi sembra quello che stavo cercando. Puoi per favore illustrarlo un po '? Non riesco a vedere come il secondo comando rimuoverà le righe da file2.csv.
Marco Faustinelli,

@Muzietto Vedi aggiornato
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

Si noti che le punture di ricerca sono /$key/e ^$key,per ridurre i risultati tra due barre (file 1) o come prima voce di una riga e seguita da una virgola (file 2). Questo dovrebbe renderlo sicuro se i tasti sembrano

a,values
a1,values

nel file 2 o simili

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

nel file 1


2

Ho appena provato il seguente liner e sembra fare il lavoro:

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

Sostituisci prima -ri con -re per testarlo. -re esegue una corsa a secco, e se tutto è ok puoi eseguirlo con -ri


mmmh, ho reindirizzato l'output del tuo codice su un file temporaneo e contiene circa 30k righe, mentre file2.csv ha inizialmente 240 e si suppone che venga filtrato.
Marco Faustinelli,

Bene, penso che sia perché stampo ogni hash nel primo file, quando faccio la sostituzione (la parte echo "\ n" $ i). Comunque se lo esegui con -ri non devi reindirizzare, perché esegue la sostituzione in atto
primero

Inoltre, se esegui -re e reindirizza, avrai ripetuto file2 per tutti gli hash che hai nel primo file. Fondamentalmente per ogni hash nel primo file lo sostituisce nel secondo file e stampa il risultato, ecco perché hai così tante linee.
primero,

1

Oltre alla risposta di Gabriele Lana, tieni presente che è necessario specificare il comando Incolla BSD per leggere il contenuto dall'input standard.

manuale del comando incolla

Se '-' è specificato per uno o più file di input, viene utilizzato l'input standard; l'input standard viene letto una riga alla volta, in modo circolare, per ogni istanza di '-'.

Quindi il finale deve essere cambiato come di seguito

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.