Extraindo certos valores do texto

4

Eu tenho um arquivo de texto:

[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

Eu preciso extrair certos valores: data, nome da cidade, idioma, x, y, z nessa ordem. Observe que em algumas linhas há uma ordem diferente e, no futuro, a ordem dos arquivos também pode ser diferente daquela.

A saída deve se parecer com:

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

ou ainda melhor se a vírgula puder ser editada, já que uma saída padrão csv seria assim:

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
    
por vayacondios2015 24.08.2016 / 18:21

3 respostas

7

Como eles parecem ser essencialmente estruturados como consultas de URL, talvez você queira examinar um analisador de consulta dedicado, como o do módulo urlparse do python. Por exemplo

#!/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)

Então

$ ./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

NOTA: o método parse_qs retorna um dict de listas, isto é, permite múltiplos valores para cada chave de consulta: o '-'.join(q[key]) vira notionalmente cada lista de valores em uma string separada por hífen, no entanto, neste caso, esperamos apenas um valor único para cada chave.

    
por steeldriver 24.08.2016 / 21:25
7

Como o pedido pode mudar, isso exigirá um pouco de script. Aqui está uma versão Perl:

#!/usr/bin/perl -nl

my $time =  if /\[(.+?)\]/; 
my $city =  if /city=(.*?)(&|$)/;
my $lang =  if /language=(.*?)(&|$)/;
my $x =  if /\bx=(.*?)(&|$)/; 
my $y =  if /\by=(.*?)(&|$)/; 
my $z =  if /\bz=(.*?)(&|$)/;
print join ",", ($time, $city, $lang, $x, $y, $z)

Salve isso como foo.pl , torne-o executável ( chmod +x foo.pl ) e execute-o assim:

./foo.pl file.txt

Você também pode inserir isso em um "one-liner":

perl -lne '$t=if/\[(.+?)\]/;$c=if/city=(.*?)(&|$)/;$l=if/language=(.*?)(&|$)/;$x=if/\bx=(.*?)(&|$)/;$y=if/\by=(.*?)(&|$)/;$z=if/\bz=(.*?)(&|$)/;print join",",($t,$c,$l,$x,$y,$z)' file

Explicação

O -n significa "ler o arquivo de entrada linha a linha e aplicar o script a cada linha. O -l adiciona uma nova linha a cada print e tira novas linhas de cada linha de entrada.

Em cada caso, usamos uma expressão regular para encontrar a string de destino e atribuí-la a uma variável se uma correspondência foi encontrada. A primeira regex, \[(.+?)\] , corresponde a qualquer coisa entre um [ e o primeiro ] . Os parênteses em torno do .+ estão capturando grupos e vamos nos referir ao que foi capturado como . Então, $time será o que estiver dentro do [ ] .

As outras expressões regulares seguem a mesma ideia. O \b significa um "caractere sem palavra" e garante que y= não corresponderá a city etc. O (&|$) significa um & ou o fim da linha ( $ ) e é necessário para capturar padrões no final da linha.

Finalmente, join com vírgulas e imprimimos.

    
por terdon 24.08.2016 / 19:16
0

Como o pedido pode mudar, isso é um pouco mais difícil, mas sed pode lidar com isso:

s/\[(.*)\](\/segment\?)(.*)/,/ #Match text between [], append to end of line and remove /segmennt?
s/city=([^&,]*)[&,](.*)/,/     #Match city= followed by any character
s/language=([^&,]*)[&,](.*)/,/ #except & and , which are the separators and append to end of line
s/x=([^&,]*)[&,](.*)/,/
s/\by=([^&,]*)[&,](.*)/,/      #Avoid matching city again by making sure y is at a word boundary 
s/z=([^&,]*)[&,](.*)/,/

Executar como:     sed -rnf arquivo de entrada do arquivo de script

    
por someonewithpc 25.08.2016 / 11:33