pdfid, по-видимому, плохо справляется с подсчетом пар obj/endobj --в вашем конкретном примере нечетный «obj» является частью потока FlateDecode:
$ cat pdf.pl
use Compress::Zlib qw(inflateInit Z_STREAM_END);
use strict;
my ($o);
while(<>){
$o -= s/\bendobj\b//g;
$o += s/\b\d+\s+\d+\s+obj\b//g;
if(/\bstream\s*$/){
local $/ = "endstream"; my $s = <>; $s =~ s/\s*endstream$//;
if($s =~ /(\w*obj)/){
my ($d, $err) = inflateInit->inflate($s);
if(length($s) == 0 && $err == Z_STREAM_END){
warn "innocuous '$1' in well formed stream\n";
}else{
warn "WARNING: inflateInit: $err\n";
}
}
}
if(/(\w*obj)\b/){ warn "WARNING: possible stray $1\n" }
}
warn "WARNING: unbalanced obj/endobj: $o\n" if $o;
$ perl pdf.pl using-freedos-24.pdf
innocuous 'obj' in well formed stream
Обратите внимание :, что мало что может проиллюстрировать рассматриваемую проблему; не используйте его для проверки безопасности PDF-файла;-)
Формат pdf довольно неприятный и сложный; вам действительно нужен -полноценный синтаксический анализатор, чтобы понять его структуру. И программа, которая способна это делать (для правильной идентификации вредоносных PDF-файлов ), сама становится вектором атаки --нет причин полагать, что специальный -синтаксический анализатор более безопасен, чем libpoppler или либмупдф.
Использование awk
и GNUdatamash
:
awk 'BEGIN{ OFS=FS="\t" }
NR>2{ # skip first two records
split($3, a, "/" ) # split $3 into array a on /
domain=a[3] # 3rd element is the domain name
sub(/^www\./, "", domain) # remove www. prefix
print domain, $4 # print domain and email
}
' file | datamash -g 1 unique 2
Часть awk
печатает домен и адрес электронной почты для всех записей, пропуская первые две строки. Это будет
a.com a@b.com
a.com a@b.com
b.fr c@hl.com
b.fr a@hl.com
Затем выходные данные направляются в datamash
для группировки ввода по первому полю и вывода разделенного запятыми -списка уникальных значений второго поля.
Выход:
a.com a@b.com
b.fr a@hl.com,c@hl.com
Строка заголовка оставлена в качестве упражнения.
попробуй:
cat inputfile |
awk '{
match($3, "^https?://.*\\.([^./]+\\.[^./]+)", matches);
print(matches[1], $4)
}' |
sort |
uniq |
awk '{
domains[$1] = domains[$1] " " $2;
}
END {
for (d in domains) {
printf("%-15s ", d);
$0 = domains[d];
for (i = 1; i < NF; i++) {
printf("%s, ", $i);
};
print($NF);
};
}'
Первый awk соответствует завершающему компоненту домена foo.bar, за которым следует электронная почта. Фильтрация по sort | uniq
устраняет повторяющиеся комбинации домен/электронная почта и гарантирует, что результаты находятся в порядке домена -, но нам все еще нужно объединить поле домена и объединить совпадающие электронные письма с запятыми, поэтому второй awk просто продолжает добавлять электронные письма в словарь с ключом домена (с использованием конкатенации с пробелом в -между ). Затем, в конце, мы перебираем все электронные письма и печатаем их с запятой в конце, если только это не последний домен, где запятая не нужна, поэтому мы просто делаем второй -до -последним итерации -. ] остановить и в заключение вывести последнее поле ($NF
).
Я пытался использовать один awk-скрипт с двухмерным -мерным array[domain][email]
, но не продвинулся очень далеко, потому что я думаю, что они только симулируются в awk, т.е. с awk for (d in domain) { for (e in d[email]) }
это невозможно. Поэтому я прибегнул к шелловому фильтру.
Мне также не нравятся хакерские циклы конкатенации строк, которые становятся нелепыми при большом количестве электронных писем в домене. Но это должно сработать, если у вас всего тысячи;для миллионов следует использовать лучший метод и т. д.
Я произвольно выбрал 15-символьное поле для домена. Вы можете удалить длину поля, если вам не нужно, чтобы они были выровнены. Вам понадобится печать BEGIN {}
, если вы хотите распечатать строку заголовка...
вывод выглядит так, учитывая ваши ответы на вопрос:
a.com a@b.com
b.fr a@hl.com, c@hl.com
(с добавлением фиктивных данных для полей 1 и 2, которые вы оставили пустыми; эти поля должны содержать фактические данные без пробелов, чтобы скрипт работал, в противном случае вам понадобится разделитель полей, например табуляция или запятая, во входном файле)
Использование Миллера(с небольшой помощью sed):
$ mlr --prepipe 'sed "/^$/d"' --tsv put -q -S '
$domain = joinv(mapexcept(splitnvx(joinv(mapselect(splitnvx($URL,"/"),3),""),"."),1),".");
@e[$domain] = mapsum(@e[$domain],{$email:1});
end {
for(k,v in @e){@{email(s)}[k] = joink(v,",")};
emit @{email(s)}, "domain"
}' File.tsv
domain email(s)
a.com a@b.com
b.fr c@hl.com,a@hl.com
Команда sed --prepipe
просто удаляет лишнюю пустую строку, чтобы ввод можно было проанализировать как TSV. Переменная $domain
получается путем разделения поля URL
дважды, сначала на/
(выбирая 3-й элемент ), затем на.
(выбирая все, кроме 1-го элемента, например.www
). Затем исходящая -из -карта потоков @e
строится как карта полей email
-. Это шаг, на котором de -дублирует электронные письма для одного и того же домена.. В end
преобразуйте карты электронной почты в строки с разделителями-запятыми -и отправьте их.
С помощью GNU awk для массивов массивов иgensub()
:
$ cat tst.awk
BEGIN { FS=OFS="\t" }
NR>1 { d2e[gensub(/[^.]+\.([^.]+\.[^./]+).*/,"\\1",1,$3)][$4] }
END {
print "domain", "emails(s)"
for (domain in d2e) {
cnt = 0
for (email in d2e[domain]) {
row = (cnt++ ? row ", " : domain OFS) email
}
print row
}
}
$ awk -f tst.awk file
domain emails(s)
a.com a@b.com
b.fr c@hl.com, a@hl.com