Hapus baris dari file tergantung pada baris yang ditemukan di file lain

11

File file1.txt berisi baris seperti:

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

Sebagai contoh:

/api/purchase/12ab09f46/index.html

File file2.csv berisi baris seperti:

<hash>,timestamp,ip_address

Sebagai contoh:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Saya ingin memfilter file2.csv menghapus semua baris di mana nilai hash hadir juga di file1.txt. Itu untuk mengatakan:

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

atau sesuatu seperti ini.

Itu harus langsung, tetapi saya tampaknya tidak dapat membuatnya bekerja.

Adakah yang bisa memberikan pipeline yang berfungsi untuk tugas ini?

Marco Faustinelli
sumber

Jawaban:

13

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

Penjelasan:

cut -d / -f 4 file1.txt akan memilih hash dari file pertama

paste -sd '|' akan menggabungkan semua hash ke dalam ekspresi reguler ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvakan memanggil grep dengan pola sebelumnya sebagai argumen, xargs akan diganti {}dengan kontenSTDIN

Jika tidak punya, pasteAnda bisa menggantinya dengantr "\\n" "|" | sed 's/|$//'

Gabriele Lana
sumber
3
+1 tetapi tidak perlu cat, cukup cut -d / -f 4 file1.txt. Atau jika Anda lebih suka tampilan sekuensial,<file1.txt cut -d / -f 4
Sparhawk
@Sparhawk terima kasih! Saya tidak tahu ;-) solusi diperbarui :-)
Gabriele Lana
11

awkSolusi yang mungkin :

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

Pertama kita membaca file1.txtmenggunakan FS(pemisah bidang) "/" dan membuat array x dengan nilai kunci dari bidang $4yang merupakan hash yang Anda inginkan. Selanjutnya kita membaca file2.txtpengaturan file kedua FSmenjadi ,dan memeriksa apakah nilai field $1tidak ada sebagai kunci dalam array xdan jika tidak kita cetak.
Hal yang lebih idiomatis seperti yang diusulkan dalam komentar dapat berupa:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
taliezin
sumber
Saya menghargai upaya Anda, tetapi saya takut ini terbang jauh di atas kepala saya. Saya terus berharap solusi berdasarkan beberapa campuran sed / grep / cat akan mungkin.
Marco Faustinelli
1
Saya akan menambahkan penjelasan, itu sederhana. Dan mungkin seseorang akan mengusulkan solusi dengan alat yang Anda inginkan.
taliezin
Kenapa tidak !($1 in x)malah{ if (!($1 in x)) print $0; }
iruvar
@ 1_CR itu kebiasaan buruk saya, saya tahu itu bisa lebih idiomatis tapi saya selalu berpikir itu akan lebih mudah untuk penjelasan kepada OP.
taliezin
@Muzietto masih, saya pikir tidak ada salahnya mulai mempelajari alat-alat lain seperti ini awksolusi berbasis ... dalam jangka panjang, Anda akan belajar untuk tertarik pada solusi yang dapat dicapai dengan menggunakan pipa yang lebih rendah untuk kesederhanaan ... :)
hjk
5

Untuk GNU sed

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

di mana sed pertama menghasilkan daftar hash dalam format sed-command-like dan mentransfernya ke sed- script berikutnya yang membaca perintah di atas dari input karena itu opsi. Sama dengan grep/12ab09f46\|a77b3ff22\|..../d -f -

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

atau tanpa perl-expresions:

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

atau bahkan lebih baik dengan potongan :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Costas
sumber
Ini terlihat oleh saya apa yang saya cari. Bisakah Anda menggambarkannya sedikit? Saya tidak bisa melihat bagaimana perintah kedua akan menghapus baris dari file2.csv.
Marco Faustinelli
@Muzietto Lihat diperbarui
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

Perhatikan bahwa sengatan pencarian adalah /$key/dan ^$key,untuk mengurangi hasil menjadi antara dua garis miring (file 1) atau menjadi entri pertama dari sebuah baris dan diikuti oleh koma (file 2). Ini akan membuatnya aman jika kunci terlihat seperti

a,values
a1,values

dalam file 2, atau suka

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

dalam file 1

Fiximan
sumber
2

Saya baru saja mencoba satu liner berikut, dan sepertinya berhasil:

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

Silahkan ganti pertama -Ri dengan -re untuk menguji itu. -re melakukan lari kering, dan jika semuanya baik-baik saja Anda dapat menjalankannya dengan -ri

primero
sumber
mmmh, saya sudah mengarahkan output kode Anda ke file sementara dan berisi sekitar 30k baris, sedangkan file2.csv awalnya 240 dan seharusnya disaring.
Marco Faustinelli
Yah, saya pikir itu karena saya mencetak setiap hash di file pertama, ketika saya melakukan substitusi (gema "\ n" $ i bagian). Bagaimanapun jika Anda menjalankannya dengan -jadi Anda tidak perlu mengarahkan ulang, karena ia melakukan substitusi di tempat
primero
Juga jika Anda menjalankan dengan -re dan redirect Anda akan memiliki file2 diulang sebanyak hash yang Anda miliki di file pertama. Pada dasarnya untuk setiap hash di file pertama itu menggantikannya di file kedua dan mencetak hasilnya, jadi itu sebabnya Anda memiliki begitu banyak baris.
primero
1

Selain jawaban Gabriele Lana, harap dicatat bahwa perintah tempel BSD perlu tanda hubung ditentukan untuk membaca konten dari input standar.

manual perintah tempel

Jika '-' ditentukan untuk satu atau lebih dari file input, input standar digunakan; input standar dibaca satu baris pada satu waktu, secara melingkar, untuk setiap instance '-'.

Jadi final perlu diubah seperti di bawah ini

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