2011-05-29 17 views
6

estoy tratando de capturar la salida de temperatura de los sensores, por el que tengo las siguientes líneas relevantes:Perl: ¿Es buena o mala expresión regular y cómo mejorarla?

temp1:  +39.5 C (crit = +105.0 C) 
Core 0:  +40.0 C (high = +100.0 C, crit = +100.0 C) 
Core 1:  +40.0 C (high = +100.0 C, crit = +100.0 C) 

Sólo necesito la primera temperatura de cada línea (39.5, 40.0, 40.0). La cuestión, por supuesto, es que realmente no puedo en el número de palabra ya que hay un espacio extra en "Núcleo 0"/"Núcleo 1".

He llegado con la siguiente expresión regular que funciona, sin embargo, me han dicho que el uso de .* es un enfoque algo vago y sucio para regex.

$core_data =~ s/^.*\+(.*)C\ .*$/$1/g; 

Me preguntaba, ¿hay una manera más estricta o mejor para lograr esto o estoy haciendo bien?

+0

¿Qué quiere decir por bueno o malo? la eficiencia parece no importar a menos que tengas que unir miles de patrones. No parece ser el caso. – VGE

+0

OTOH, la legibilidad ** es ** importante. Asegúrate de tener un comentario cerca. – pavium

+0

Eficiencia del pozo en nombre del aprendizaje :). También me doy cuenta de que mi expresión regular está produciendo un resultado con un personaje principal en el espacio. No creo que esto importe para mis propósitos, pero aún me gustaría saber cómo deshacerse de él. – DanH

Respuesta

6

A más concisa regex

/\+(\d+\.?\d*) C/ 

esto coincidirá con la primera temperatura con un valor decimal opcional.

#!/usr/bin/perl 
use strict; 
use warnings; 

my $re = qr{\+(\d+\.?\d*) C}; 
while (my $line = <DATA>){ 
    $line =~$re and print $1,"\n"; 
} 
__DATA__ 
temp1:  +39.5 C (crit = +105.0 C) 
Core 0:  +40.0 C (high = +100.0 C, crit = +100.0 C) 
Core 1:  +40.0 C (high = +100.0 C, crit = +100.0 C) 

de salida:

39.5 
40.0 
40.0 
2

una expresión regular más precisa

$core_data =~ s/^.*\+([\d.]+)C\ .*$/$1/g; 

Pero, probablemente, el siguiente es suficiente porque sólo el valor numeical parece ser interesante.

$cpu_head = $1 if m/:\s*\+([\d.]+) C/; 

Nota: \ s significa cualquier espacio y \ d para cualquier dígito.

2

En mi humilde opinión,. * Está perfectamente bien cuando tiene sentido, aunque cuando se puede reducir a algo más específico, entonces todo es mejor.

En su caso, se podría decir

S/^[^+]+\+([0-9.]) C.*$/$1/g 

En esta expresión regular, me centro en lo que estoy buscando y caracterizar la temperatura como una secuencia de dígitos con un punto en alguna parte mientras que el resto es simplemente no es relevante a mi. Como tiene dos temperaturas en cada línea y solo quiere la primera, utilicé [^ +] al principio, que coincide con todo lo que no es un +, por lo que se detendrá justo donde comienza la primera temperatura. una vez que tengo la temperatura, me trago todo usando. * hasta el final de la línea.

Esto es solo un ejemplo de razonamiento, no pretende ser la mejor expresión regular que puede encontrar para resolver su problema.

3

No entiendo por qué está buscando y reemplazando con su expresión regular (s///g), si solo está tratando de capturar la primera temperatura. Su expresión regular parece depender de .* siendo codicioso. Suponiendo que usted puede confiar en el formato name: temp C (..., esta expresión regular funcionará sin tener que coincidir con toda la cadena:

$core_data =~ m/^(?:\w*\b)*:\s*(\+?\d+\.\d+)/; 

... o para capturar sin el signo + delante:

$core_data =~ m/^(?:\w*\b)*:\s*\+?(\d+\.\d+)/; 
2

esto parece más adecuado para un split de una expresión regular. split borrará automáticamente todos los espacios en blanco innecesarios, y no es necesario planificar anticipadamente los cambios en los datos.

my $tag; 
($tag, $core_data) = split (/:/, $core_data); 
my @fields = split (/\s/, $core_data); 
my $temp = $fields[0]; 

que almacenará las cuerdas "+39.5" y "+40.0" en las diferentes líneas de ejemplo, que se pueden convertir en una serie automágicamente, creo.

Además, tendrá fácil acceso a la etiqueta de la línea en $tag.

Si lo desea, puede cortar la información añadida dentro de los paréntesis con una expresión regular:

if ($core_data =~ s/\(([^\)]*)\)//) { 
    my $tmp = $1; 
    $tmp =~ s/[\s\+C]//g; # clear away junk 
    %data = split (/=/, (split (/,/, $tmp))); 
} 
for my $key (keys %data) { 
    printf "%-7s = %s\n", $key, $data{$key}; 
} 
+0

Desafortunadamente, debido al espacio en 'Core 0' y 'Core 1', pero no en 'temp1', dividir en el espacio no funcionará para mí. – DanH

+0

@DanH Solucionado ahora ... no puedo verificar si '$ {1}' funcionará (porque no tengo Perl instalado en esta computadora). Supongo que '_' intentará unirse al escalar de lo contrario. – TLP

1

me gustaría escribir una función general que analiza la entrada y le devuelve un hash. En general, usaría esta expresión regular:

m/\A ([^:]+) : \s+ ([+-][0-9.]+) /xms 

Esto coincide con una línea. En $ 1 es lo que coincide (es decir: "Core 0") y en $ 2 la temperatura. También me gustaría hacer una conversión de cadena en un número que iba a terminar con algo como esto:

my $temp_string = q{ 
temp1:  +39.5 C (crit = +105.0 C) 
Core 0:  +40.0 C (high = +100.0 C, crit = +100.0 C) 
Core 1:  +40.0 C (high = +100.0 C, crit = +100.0 C) 
Core 2:  -40.0 C (high = +100.0 C, crit = +100.0 C) 
}; 

my $temps = parse_temps($temp_string); 

print "temp1: ", $temps->{temp1}, "\n"; 
print "Core 0: ", $temps->{core0}, "\n"; 
print "Core 1: ", $temps->{core1}, "\n"; 
print "Core 2: ", $temps->{core2}, "\n"; 


sub parse_temps { 
    my ($str) = @_; 
    my %temp; 
    for my $line (split /\n/, $str) { 
     if ($line =~ m/\A ([^:]+) : \s+ ([+-][0-9.]+) /xms) { 
      my $key = $1; 
      my $value = $2; 

      $key =~ s/\s+//g; 
      $temp{ lc $key } = 0+$value; 
     } 
    } 
    return wantarray ? %temp : \%temp; 
} 

La salida del programa:

temp1: 39.5 
Core 0: 40 
Core 1: 40 
Core 2: -40 
Cuestiones relacionadas