Удалить строки из файла в зависимости от строк, найденных в другом файле

11

Файл file1.txt содержит такие строки:

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

Например:

/api/purchase/12ab09f46/index.html

Файл file2.csv содержит такие строки:

<hash>,timestamp,ip_address

Например:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Я хочу отфильтровать file2.csv, удалив все строки, где значение hash присутствует также в file1.txt. Это сказать:

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

или что-то вроде этого.

Это должно быть просто, но я не могу заставить его работать.

Кто-нибудь может предоставить рабочий конвейер для этой задачи?

Марко Фаустинелли
источник

Ответы:

13

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

Объяснение:

cut -d / -f 4 file1.txt выберет хэши из первого файла

paste -sd '|' объединит все хэши в регулярное выражение ex. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvвызовет grep с предыдущим шаблоном в качестве аргумента, xargs заменит {}его содержимымSTDIN

Если у вас нет, pasteвы можете заменить его наtr "\\n" "|" | sed 's/|$//'

Габриэле Лана
источник
3
+1 но не нужно cat, просто cut -d / -f 4 file1.txt. Или, если вы предпочитаете последовательный взгляд,<file1.txt cut -d / -f 4
Sparhawk
@ Sparhawk спасибо! Я не знал ;-) решение обновлено :-)
Габриэле Лана
11

Возможное awkрешение:

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

Сначала мы читаем file1.txtс помощью FS(разделитель полей) "/" и создаем массив x со значениями ключей из поля, $4которое является желаемым хешем. Далее мы читаем второй файл file2.txtнастройку , FSчтобы быть ,и проверить , если значение поля $1не существует в качестве ключа в массиве , xи если он не выводит его.
Та же более идиоматическая, как предложено в комментариях:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
taliezin
источник
Я ценю ваши усилия, но боюсь, что это пролетает над моей головой. Я продолжаю надеяться, что решение, основанное на смеси sed / grep / cat, будет возможным.
Марко Фаустинелли
1
Я добавлю объяснение, это просто. И, может быть, кто-то предложит решение с помощью инструментов, которые вы хотите.
Тализин
Почему бы просто !($1 in x)вместо{ if (!($1 in x)) print $0; }
iruvar
@ 1_CR это моя плохая привычка, я знаю, что это может быть более идиоматичным, но я всегда думаю, что это будет проще для объяснения ОП.
Тализин
@Muzietto все же, я думаю, что нет никакого вреда awk, если вы начнете изучать другие инструменты, такие как это решение на основе ... в конечном счете, вы научитесь стремиться к решениям, которые могут быть достигнуты с использованием меньших каналов для простоты ... :)
HJK
5

Для GNU SED

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

где first sed создает список хэшей в формате sed-command-format /12ab09f46\|a77b3ff22\|..../dи передает его следующему сценарию sed, который читает указанную выше команду из ввода, поэтому -f -опция.
То же самое с grep

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

или без perl-выражений:

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

или еще лучше с разрезом :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Костас
источник
Это выглядит для меня то, что я искал. Не могли бы вы проиллюстрировать это немного? Я не вижу, как вторая команда удалит строки из file2.csv.
Марко Фаустинелли
@Muzietto Смотрите обновленную
Костас
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

Обратите внимание, что строки поиска /$key/и ^$key,для уменьшения результатов должны быть либо между двумя косыми чертами (файл 1), либо быть первой записью строки, за которой следует запятая (файл 2). Это должно обеспечить безопасность, если ключи выглядят как

a,values
a1,values

в файле 2 или как

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

в файле 1

Fiximan
источник
2

Я только что попробовал следующий лайнер, и он, кажется, делает эту работу:

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

Пожалуйста, замените сначала -ri на -re, чтобы проверить это. -re делает пробный прогон, и если все в порядке, вы можете запустить его с -ri

Primero
источник
mmmh, я перенаправил вывод вашего кода во временный файл, и он содержит около 30 тыс. строк, тогда как file2.csv изначально имеет 240, и он должен фильтроваться.
Марко Фаустинелли
Ну, я думаю, это потому, что я печатаю каждый хеш в первом файле, когда я делаю замену (часть echo "\ n" $ i). В любом случае, если вы запускаете его с -ri, вам не нужно перенаправлять, потому что он выполняет замену на месте
primero
Также, если вы запустите с -re и перенаправлением, file2 будет повторяться столько же хешей, сколько у вас в первом файле. В основном для каждого хэша в первом файле он заменяет его во втором файле и печатает результат, поэтому у вас так много строк.
прим
1

В дополнение к ответу Габриэле Ланы, обратите внимание, что для вставки команды BSD необходимо указать дефис для чтения содержимого из стандартного ввода.

руководство команды вставки

Если '-' указано для одного или нескольких входных файлов, используется стандартный ввод; стандартный ввод читается по одной строке за раз, по кругу, для каждого экземпляра '-'.

Таким образом, окончательное должно быть изменено, как показано ниже

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
efesaid
источник