Извлечение определенных значений из текста
У меня есть текстовый файл:
[31/May/2016:11:58:29-0500]/segment?city=london&language=en&x=12345&y=6789&z=1
[31/May/2016:11:59:15-0500]/segment?language=en&city=madrid&x=4589.4583&y=4865.5465&z=3
[31/May/2016:12:05:13-0500]/segment?city=london&language=en&x=12345&y=6789&z=1
[31/May/2016:12:15:13-0500]/segment?city=london&language=en&x=12345&y=6789&z=1
[31/May/2016:12:26:53-0500]/segment?language=en&city=newyork&x=45724.75575&y=424424.77474&z=3
Мне нужно извлечь определенные значения: дата, название города, язык, х, у, г в этом порядке. Обратите внимание, что в некоторых строках есть другой порядок, и в будущем порядок файлов также может отличаться от этого.
Вывод должен выглядеть так:
31/May/2016:11:58:29-0500 london en 12345 6789 1
31/May/2016:11:59:15-0500 madrid en 589.4583 4865.5465 3
31/May/2016:12:05:13-0500 london en 12345 6789 1
31/May/2016:12:15:13-0500 london en 12345 6789 1
31/May/2016:12:26:53-0500 newyork en 45724.75575 424424.77474 3
или даже лучше, если запятая может быть отредактирована, поскольку определенный стандартный вывод csv будет выглядеть так:
31/May/2016:11:58:29-0500,london,en,12345,6789,1
31/May/2016:11:59:15-0500,madrid,en,589.4583,4865.5465,3
31/May/2016:12:05:13-0500,london,en,12345,6789,1
31/May/2016:12:15:13-0500,london,en,12345,6789,1
31/May/2016:12:26:53-0500,newyork,en,45724.75575,424424.77474,3
3 ответа
Поскольку они, по-видимому, в основном структурированы как URL-запросы, вы можете захотеть использовать специальный синтаксический анализатор запросов, например, из Python. urlparse
модуль. Например
#!/usr/bin/python2
import sys,re
from urlparse import urlparse,parse_qs
keys = ['city', 'language', 'x', 'y', 'z']
with open(sys.argv[1],'r') as f:
for line in f:
u = urlparse(line.strip('\n'))
q = parse_qs(u.query)
# extract the strings we want from the dict-of-lists
values = ','.join(['-'.join(q[key]) for key in keys])
# extract the timestamp portion of the path (between `[` and `]`)
m = re.search('(?<=\[).*?(?=\])', u.path)
ts = m.group(0)
# print as a comma-separated list
print '{},{}'.format(ts, values)
затем
$ ./queryparse.py queries.txt
31/May/2016:11:58:29-0500,london,en,12345,6789,1
31/May/2016:11:59:15-0500,madrid,en,4589.4583,4865.5465,3
31/May/2016:12:05:13-0500,london,en,12345,6789,1
31/May/2016:12:15:13-0500,london,en,12345,6789,1
31/May/2016:12:26:53-0500,newyork,en,45724.75575,424424.77474,3
Обратите внимание parse_qs
Метод возвращает набор списков, т.е. он допускает несколько значений для каждого ключа запроса: '-'.join(q[key])
условно превращает каждый список значений в строку, разделенную дефисом, однако в этом случае мы ожидаем только одно значение для каждого ключа.
Поскольку порядок может измениться, потребуется немного сценариев. Вот версия Perl:
#!/usr/bin/perl -nl
my $time = $1 if /\[(.+?)\]/;
my $city = $1 if /city=(.*?)(&|$)/;
my $lang = $1 if /language=(.*?)(&|$)/;
my $x = $1 if /\bx=(.*?)(&|$)/;
my $y = $1 if /\by=(.*?)(&|$)/;
my $z = $1 if /\bz=(.*?)(&|$)/;
print join ",", ($time, $city, $lang, $x, $y, $z)
Сохранить как foo.pl
сделать его исполняемым (chmod +x foo.pl
) и запустите его так:
./foo.pl file.txt
Вы также можете втиснуть это в "однострочник":
perl -lne '$t=$1if/\[(.+?)\]/;$c=$1if/city=(.*?)(&|$)/;$l=$1if/language=(.*?)(&|$)/;$x=$1if/\bx=(.*?)(&|$)/;$y=$1if/\by=(.*?)(&|$)/;$z=$1if/\bz=(.*?)(&|$)/;print join",",($t,$c,$l,$x,$y,$z)' file
объяснение
-n
означает "читать входной файл построчно и применять скрипт к каждой строке. -l
добавляет новую строку к каждому print
вызывайте и удаляйте символы новой строки из каждой строки ввода.
В каждом случае мы используем регулярное выражение, чтобы найти целевую строку и присвоить ее переменной, если совпадение было найдено. Первое регулярное выражение, \[(.+?)\]
соответствует что-нибудь между [
и первый ]
, Скобки вокруг .+
захватывают группы и давайте обратимся к тому, что было захвачено как $1
, Так, $time
будет то, что было внутри [ ]
,
Другие регулярные выражения следуют той же идее. \b
означает "несловесный символ" и гарантирует, что y=
не будет соответствовать city
и т. д. (&|$)
означает либо &
или конец строки ($
) и необходим для захвата узоров в самом конце строки.
Наконец, мы join
эти с запятыми и распечатать их.
Поскольку порядок может измениться, это немного сложнее, но sed
может справиться с этим:
s/\[(.*)\](\/segment\?)(.*)/\3,\1/ #Match text between [], append to end of line and remove /segmennt?
s/city=([^&,]*)[&,](.*)/\2,\1/ #Match city= followed by any character
s/language=([^&,]*)[&,](.*)/\2,\1/ #except & and , which are the separators and append to end of line
s/x=([^&,]*)[&,](.*)/\2,\1/
s/\by=([^&,]*)[&,](.*)/\2,\1/ #Avoid matching city again by making sure y is at a word boundary
s/z=([^&,]*)[&,](.*)/\2,\1/
Запустите как: sed -rnf scriptfile inputfile