Search code examples
perlawksed

Match a float and multiply by 100


I would like to extract a float from a coverage report and multiply it with 100 to report it as percentage.

I am able to match the float using sed or grep and then multiply it using awk, but I was wondering if there is a more elegant single tool solution, maybe with perl or awk only?

The line looks like this:

<coverage line-rate="0.34869999999999995" branch-rate="0.2777" >

My current solution:

sed -n 's/^<coverage line-rate="\([0-9\.]*\)".*$/\1/p' a.txt | awk '{print (100 * $1)}'
34.87

Solution

  • A way to capture a number and process it, if matched, with Perl

    perl -wnE'say 100 * $1 if /^<coverage line-rate="([0-9]*\.[0-9]*)"/' file
    

    Another way to extract captures and process (multiply and print) them

    perl -wnE'say 100 * $_ for /^<coverage line-rate="([0-9]*\.[0-9]*)"/' file
    

    This uses the property of the match operator (/.../) to return the matches when in list context, provided by the for loop, in which we then process matches (only one in this case), multiply and print. When there is no match the loop just won't have items to iterate over.

    Both these process a file and merely print the result from lines that match, like in the question. In both cases if a particular precision is wanted the say can be replaced by printf ".2f\n", $x; (two decimal places, etc).


    I am not sure whether printing a number out of a one-liner is indeed the ultimate need or merely a simplified presentation for this purpose, but there are of course yet other ways.

    One can also index into the list with matches in order to extract a captured pattern

    my $num = ( /^<coverage line-rate="([0-9]*\.[0-9]*)/ )[0];
    

    and then process after a check (whether there was a match), $num *= 100 if defined $num;

    In case the input string need be changed, as is done in the question, perhaps for further work, to a particular precision

    # Keep four decimal places
    s/^<coverage line-rate="([0-9]*\.[0-9]*).*/sprintf("%.4f", 100*$1)/e; 
    

    or if the precision doesn't matter and can be left to the interpreter

    s/^<coverage line-rate="([0-9]*\.[0-9]*).*/100*$1/e; 
    

    In both cases the /e modifier makes the replacement part be evaluated as code, so we can do some processing there. In the first case I use sprintf to format the replacement string while in the other case it's just multiplied.


    There could be a potential complication here, about how many digits need be kept. If it should be the same number as in the original then we may need to first detect how many that was

    use warnings;
    use strict;
    use feature 'say';
    
    # Number is shortened for demonstration
    my $str = shift // q(<coverage line-rate="0.348691" branch-rate="0.2777" >);
    
    sub process_num {
        my ($num, $frac) = @_;
    
        $num *= 100;
        my $frac_len = length($frac) - 2;  # keeps same digits
    
        return sprintf "%.${frac_len}f", $num;
    }
    
    $str =~ s/^<coverage line-rate="([0-9]*\.([0-9]+)).*/process_num($1, $2)/e;
    
    say $str;
    

    Running this without arguments prints 34.8691 instead of the original line with 0.348691

    Thus assumes the number to have digit(s) for the fractional part, at least.