Remover linhas de um arquivo, dependendo das linhas encontradas em outro arquivo

11

O arquivo file1.txt contém linhas como:

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

Por exemplo:

/api/purchase/12ab09f46/index.html

Arquivo file2.csv contém linhas como:

<hash>,timestamp,ip_address

Por exemplo:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Eu quero filtrar file2.csv removendo todas as linhas onde o valor de hash está presente também em file1.txt. Isso é para dizer:

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

ou algo assim.

Deve ser simples, mas parece que não consigo fazer funcionar.

Alguém pode fornecer um canal de trabalho para essa tarefa?

    
por Marco Faustinelli 17.08.2015 / 13:58

6 respostas

13

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

Explicação:

cut -d / -f 4 file1.txt selecionará os hashes do primeiro arquivo

paste -sd '|' unirá todos os hashes em uma expressão regular ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csv chamará o grep com o padrão anterior como argumento, xargs substituirá {} pelo conteúdo do STDIN

Se você não tiver paste , poderá substituí-lo por tr "\n" "|" | sed 's/|$//'

    
por 17.08.2015 / 15:50
11

Possível awk solution:

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

Primeiro, lemos file1.txt usando FS (separador de campo) "/" e criamos matriz x com valores de chaves do campo $4 , que é o hash desejado. Em seguida, lemos o segundo arquivo file2.txt configuração FS para ser , e verificamos se o valor do campo $1 não existe como chave na matriz x e, se não o imprimimos.
O mesmo mais idiomático como proposto nos comentários poderia ser:

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

Para o GNU sed

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

em que primeiro sed produz uma lista de hashes em sed-command-format como /12ab09f46\|a77b3ff22\|..../d e transfere para próximo sed -script que lê o comando acima da entrada, portanto, -f - option.
Mesmo com o grep

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

ou sem expresões de perl:

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

ou ainda melhor com corte :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
    
por 17.08.2015 / 14:57
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

Observe que as picadas de pesquisa são /$key/ e ^$key, para reduzir os resultados entre duas barras (arquivo 1) ou a primeira entrada de uma linha e, em seguida, uma vírgula (arquivo 2). Isso deve torná-lo seguro se as chaves parecerem

a,values
a1,values

no arquivo 2 ou como

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

no arquivo 1

    
por 17.08.2015 / 14:11
2

Eu apenas tentei o seguinte, e parece fazer o trabalho:

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

Substitua primeiro -ri por -re para testá-lo. -re faz um teste a seco, e se tudo estiver ok você pode executá-lo com -ri

    
por 17.08.2015 / 14:20
1

Além da resposta de Gabriele Lana , observe que o comando colar do BSD precisa que o traço seja especificado para ler o conteúdo de entrada padrão.

manual do comando paste

If '-' is specified for one or more of the input files, the standard input is used; standard input is read one line at a time, circularly, for each instance of '-'.

Então, a final precisa ser mudada como abaixo

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