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

O 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

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

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

ou algo parecido com isto.

Deve ser simples, mas pareço incapaz de fazê-lo funcionar.

Alguém pode fornecer um pipeline de trabalho para esta tarefa?

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 '|' juntará todos os hashes em uma expressão regular ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvinvocará grep com o padrão anterior como argumento, xargs será substituído {}pelo conteúdo doSTDIN

Se você não tiver, pastepoderá substituí-lo portr "\\n" "|" | sed 's/|$//'


3
+1, mas não é necessário cat, apenas cut -d / -f 4 file1.txt. Ou, se você preferir o visual seqüencial,<file1.txt cut -d / -f 4
Sparhawk

@Sparhawk thanks! Eu não sabia ;-) solução atualizada :-)
Gabriele Lana

11

awkSolução possível :

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

Primeiro, lemos file1.txtusando FS(separador de campo) "/" e criamos a matriz x com os valores das chaves do campo, $4que é o hash que você deseja. A seguir lemos segundo arquivo de file2.txtconfiguração FSpara ser ,e verificar se o valor de campo $1não existe como chave em ordem xe se não nós imprimi-lo.
O mesmo mais idiomático que proposto nos comentários pode ser:

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

Agradeço seu esforço, mas receio que isso voe bem acima da minha cabeça. Continuo esperando que uma solução baseada em alguma mistura sed / grep / cat seja possível.
Marco Faustinelli

1
Vou acrescentar uma explicação, é simples. E pode ser que alguém irá propor uma solução com as ferramentas que você deseja.
taliezin

Por que não apenas em !($1 in x)vez de{ if (!($1 in x)) print $0; }
iruvar

@ 1_CR é o meu mau hábito, sei que poderia ser mais idiomático, mas sempre acho que será mais simples a explicação do OP.
taliezin

@Muzietto ainda, acho que não há mal algum em começar a aprender outras ferramentas, como esta awksolução baseada ... a longo prazo, você aprenderá a buscar soluções que podem ser alcançadas usando tubos menores para simplificar ... :)
Hjk

5

Para GNU sed

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

onde first sed produz uma lista de hashes no formato sed-command-like /12ab09f46\|a77b3ff22\|..../de transfere-a para o próximo sed -script, que lê o comando acima da -f -opção input, portanto .
Mesmo com grep

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

ou sem expressões perl:

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

ou ainda melhor com o corte :

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

Isso me parece o que eu estava procurando. Você pode ilustrar um pouco? Não consigo ver como o segundo comando removerá as linhas do arquivo2.csv.
precisa

@Muzietto Ver atualizado
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

Observe que as picadas de pesquisa são /$key/e, ^$key,para reduzir os resultados, devem estar entre duas barras (arquivo 1) ou a primeira entrada de uma linha e seguidas por 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


2

Eu apenas tentei o seguinte liner 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 uma corrida a seco e, se estiver tudo bem, você pode executá-la com -ri


mmmh, redirecionei a saída do seu código para um arquivo temporário e ele contém cerca de 30k linhas, enquanto file2.csv possui inicialmente 240 e deve ser filtrado.
Marco Faustinelli

Bem, acho que é porque imprimo todos os hash no primeiro arquivo, quando faço a substituição (a parte "\ n" $ i do eco). De qualquer forma, se você executá-lo com -ri você não tem para redirecionar, porque ele faz a substituição no lugar
primero

Além disso, se você executar com -re e redirecionar, o arquivo2 será repetido para quantos hashes você tiver no primeiro arquivo. Basicamente, para cada hash no primeiro arquivo, ele o substitui no segundo arquivo e imprime o resultado, e é por isso que você tem tantas linhas.
primero

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 da entrada padrão.

comando manual de colar

Se '-' for especificado para um ou mais dos arquivos de entrada, a entrada padrão será usada; a entrada padrão é lida uma linha de cada vez, circularmente, para cada instância de '-'.

Então final precisa ser alterado como abaixo

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.