Supprimer des lignes d'un fichier en fonction des lignes trouvées dans un autre fichier

11

Le fichier file1.txt contient des lignes comme:

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

Par exemple:

/api/purchase/12ab09f46/index.html

Le fichier file2.csv contient des lignes comme:

<hash>,timestamp,ip_address

Par exemple:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Je veux filtrer file2.csv en supprimant toutes les lignes où la valeur de hachage est également présente dans file1.txt. Ce est-à-dire:

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

ou quelque chose comme ça.

Cela devrait être simple, mais je semble incapable de le faire fonctionner.

Quelqu'un peut-il fournir un pipeline fonctionnel pour cette tâche?

Marco Faustinelli
la source

Réponses:

13

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

Explication:

cut -d / -f 4 file1.txt sélectionnera les hachages du premier fichier

paste -sd '|' joindra tous les hachages dans une expression régulière ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvinvoquera grep avec le modèle précédent comme argument, xargs remplacera {}par le contenu duSTDIN

Si vous n'en avez pas, pastevous pouvez le remplacer partr "\\n" "|" | sed 's/|$//'

Gabriele Lana
la source
3
+1 mais pas nécessaire cat, juste cut -d / -f 4 file1.txt. Ou si vous préférez le look séquentiel,<file1.txt cut -d / -f 4
Sparhawk
@Sparhawk merci! Je ne savais pas ;-) solution mise à jour :-)
Gabriele Lana
11

awkSolution possible :

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

D'abord, nous lisons en file1.txtutilisant FS(séparateur de champs) "/" et créons le tableau x avec les valeurs des clés du champ $4qui est le hachage que vous voulez. Ensuite , nous lisons deuxième fichier file2.txtparamètre FSpour être ,et vérifier si la valeur de champ $1n'existe pas comme clé dans le tableau xet si elle ne nous l' imprimer.
La même chose plus idiomatique que celle proposée dans les commentaires pourrait être:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
taliezin
la source
J'apprécie vos efforts, mais je crains que cela vole bien au-dessus de ma tête. J'espère toujours qu'une solution basée sur un mélange sed / grep / cat sera possible.
Marco Faustinelli
1
J'ajouterai une explication, c'est simple. Et peut-être que quelqu'un proposera une solution avec les outils que vous souhaitez.
taliezin
Pourquoi pas juste !($1 in x)au lieu de{ if (!($1 in x)) print $0; }
iruvar
@ 1_CR c'est ma mauvaise habitude, je sais que ça pourrait être plus idiomatique mais je pense toujours que ce sera plus simple à expliquer à OP.
taliezin
@Muzietto toujours, je pense qu'il n'y a pas de mal à commencer à apprendre d'autres outils tels que cette awksolution basée sur ... à long terme, vous apprendrez à graviter vers des solutions qui peuvent être obtenues en utilisant des tuyaux moindres pour plus de simplicité ... :)
hjk
5

Pour GNU sed

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

où la première sed liste des produits de hash en commandant format sed comme /12ab09f46\|a77b3ff22\|..../det le transférer à côté sed -script qui se lit ci - dessus commande d'entrée donc l' -f -option.
Idem avec grep

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

ou sans perl-expressions:

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

ou encore mieux avec coupe :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Costas
la source
Cela me semble ce que je cherchais. Pouvez-vous l'illustrer un peu? Je ne vois pas comment la deuxième commande supprimera les lignes de file2.csv.
Marco Faustinelli
@Muzietto Voir mise à jour
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

Notez que les piqûres de recherche sont /$key/et ^$key,pour que les résultats soient soit entre deux barres obliques (fichier 1) soit comme la première entrée d'une ligne et suivis d'une virgule (fichier 2). Cela devrait le rendre sûr si les clés ressemblent

a,values
a1,values

dans le fichier 2, ou comme

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

dans le dossier 1

Fiximan
la source
2

Je viens d'essayer la doublure suivante, et elle semble faire l'affaire:

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

Veuillez remplacer first -ri par -re pour le tester. -re fait un essai à sec, et si tout va bien, vous pouvez l'exécuter avec -ri

primero
la source
mmmh, j'ai redirigé la sortie de votre code vers un fichier temporaire et il contient environ 30k lignes, alors que file2.csv en a initialement 240 et il est censé être filtré.
Marco Faustinelli
Eh bien, je pense que c'est parce que j'imprime chaque hachage dans le premier fichier, quand je fais la substitution (la partie echo "\ n" $ i). Quoi qu'il en soit, si vous l'exécutez avec -ri, vous n'avez pas à rediriger, car il effectue la substitution en place
primero
De plus, si vous exécutez avec -re et redirigez, vous aurez répété file2 pour autant de hachages que vous avez dans le premier fichier. Fondamentalement, pour chaque hachage dans le premier fichier, il le remplace dans le deuxième fichier et imprime le résultat, c'est pourquoi vous avez autant de lignes.
primero
1

En plus de la réponse de Gabriele Lana, veuillez noter que la commande de collage BSD doit spécifier un tiret pour lire le contenu à partir de l'entrée standard.

manuel de la commande coller

Si «-» est spécifié pour un ou plusieurs des fichiers d'entrée, l'entrée standard est utilisée; l'entrée standard est lue une ligne à la fois, de façon circulaire, pour chaque occurrence de «-».

Donc, le dernier doit être changé comme ci-dessous

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