Usuń linie z pliku w zależności od linii znalezionych w innym pliku

11

Plik file1.txt zawiera linie takie jak:

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

Na przykład:

/api/purchase/12ab09f46/index.html

Plik file2.csv zawiera linie takie jak:

<hash>,timestamp,ip_address

Na przykład:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Chcę filtrować plik2.csv usuwając wszystkie wiersze, w których wartość skrótu jest obecna, również w pliku1.txt. To znaczy:

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

lub coś w tym stylu.

To powinno być proste, ale wydaje mi się, że nie jestem w stanie sprawić, by działało.

Czy ktoś może podać działający potok dla tego zadania?

Marco Faustinelli
źródło

Odpowiedzi:

13

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

Wyjaśnienie:

cut -d / -f 4 file1.txt wybierze skróty z pierwszego pliku

paste -sd '|' połączy wszystkie skróty w wyrażenie regularne np. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvwywoła grep z poprzednim wzorem jako argument, xargs zastąpi {}treśćSTDIN

Jeśli nie masz, pastemożesz go zastąpićtr "\\n" "|" | sed 's/|$//'

Gabriele Lana
źródło
3
+1, ale nie ma takiej potrzeby cat, tylko cut -d / -f 4 file1.txt. Lub jeśli wolisz sekwencyjny wygląd,<file1.txt cut -d / -f 4
Sparhawk
@Sparhawk dzięki! Nie wiedziałem ;-) aktualizacja rozwiązania :-)
Gabriele Lana,
11

Możliwe awkrozwiązanie:

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

Najpierw czytamy file1.txtza pomocą FS(separator pól) „/” i tworzymy tablicę x z wartościami kluczy z pola, $4które jest skrótem, który chcesz. Następnie drugi plik odczytać file2.txtustawienie FSsię ,i sprawdzić, czy wartość pola $1nie istnieje jako klucza w tablicy x, a jeśli to nie my go wydrukować.
Taki sam bardziej idiomatyczny, jak zaproponowano w komentarzach, może być:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
taliezin
źródło
Doceniam twój wysiłek, ale obawiam się, że leci to ponad moją głową. Mam nadzieję, że możliwe będzie rozwiązanie oparte na mieszance sed / grep / cat.
Marco Faustinelli,
1
Dodam wyjaśnienie, to proste. Być może ktoś zaproponuje rozwiązanie, którego potrzebujesz.
taliezin
Dlaczego nie !($1 in x)zamiast{ if (!($1 in x)) print $0; }
iruvar,
@ 1_CR to mój zły nawyk, wiem, że może być bardziej idiomatyczny, ale zawsze myślę, że łatwiej będzie wyjaśnić OP.
taliezin
@Muzietto nadal, myślę, że nie ma nic złego w nauce innych narzędzi, takich jak to awkrozwiązanie oparte ... na dłuższą metę nauczysz się dążyć do rozwiązań, które można osiągnąć za pomocą mniejszych rur dla uproszczenia ... :)
hjk
5

Dla GNU sed

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

gdzie pierwszy sed tworzy listę skrótów w formacie sed-command /12ab09f46\|a77b3ff22\|..../di przenosi ją do następnego skryptu sed, który odczytuje powyższe polecenie z wejścia, dlatego też -f -opcja.
To samo z grep

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

lub bez perl-wyrażeń:

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

lub jeszcze lepiej z cięciem :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Costas
źródło
To wygląda na to, czego szukałem. Czy możesz to trochę zilustrować? Nie widzę, jak drugie polecenie usunie wiersze z pliku2.csv.
Marco Faustinelli,
@Muzietto Zobacz zaktualizowane
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

Zauważ, że żądaniami wyszukiwania są /$key/i, ^$key,aby zmniejszyć wyniki między dwoma ukośnikami (plik 1) lub być pierwszym wpisem linii, a po nim przecinek (plik 2). To powinno zapewnić bezpieczeństwo, jeśli wyglądają klucze

a,values
a1,values

w pliku 2 lub podobnym

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

w pliku 1

Fiximan
źródło
2

Właśnie wypróbowałem jedną linijkę i wydaje się, że wykonuje to zadanie:

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

Proszę wymienić pierwszy ri z -R , aby go przetestować. -re wykonuje próbę na sucho, a jeśli wszystko jest w porządku, możesz uruchomić ją za pomocą -ri

primero
źródło
mmmh, przekierowałem wyjście twojego kodu do pliku tymczasowego i zawiera on około 30 tys. linii, podczas gdy plik2.csv ma ​​początkowo 240 i powinien zostać przefiltrowany.
Marco Faustinelli,
Myślę, że dzieje się tak, ponieważ drukuję każdy skrót w pierwszym pliku, kiedy dokonuję podstawienia (część echa „\ n” $ i). W każdym razie, jeśli uruchomisz go z opcją -ri, nie musisz przekierowywać, ponieważ dokonuje podstawienia w miejscu
primero
Również jeśli uruchomisz z opcją -re i przekierowaniem, plik2 będzie powtarzany dla tylu skrótów, jakie masz w pierwszym pliku. Zasadniczo dla każdego skrótu w pierwszym pliku zastępuje go w drugim pliku i drukuje wynik, dlatego masz tak wiele wierszy.
primero
1

Oprócz odpowiedzi Gabriele Lany należy pamiętać, że polecenie wklejania BSD wymaga określenia myślnika, aby odczytać zawartość ze standardowego wejścia.

instrukcja polecenia wklej

Jeśli dla jednego lub więcej plików wejściowych podano „-”, używane jest standardowe wejście; standardowe wejście odczytywane jest po jednym wierszu, cyklicznie, dla każdego wystąpienia „-”.

Tak więc ostateczna potrzeba zmiany, jak poniżej

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
efesaid
źródło