Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write this sed/bash command in awk or perl (or python, or ...)?

I need to replace instances of Progress (n,m) and Progress label="some text title" (n,m) in a scripting language with new values (N,M) where

N= integer ((n/m) * normal)
M= integer ( normal )

The progress statement can be anywhere on the script line (and worse, though not with current scripts, split across lines).

The value normal is a specified number between 1 and 255, and n and m are floating point numbers

So far, my sed implementation is below. It only works on Progress (n,m) formats and not Progress label="Title" (n,m) formats, but its just plain nuts:

#!/bin/bash
normal=$1; 
file=$2
for n in $(sed -rn '/Progress/s/Progress[ \t]+\(([0-9\. \t]+),([0-9\. \t]+)\).+/\1/p' "$file" )
do 
    m=$(sed -rn "/Progress/s/Progress[ \t]+\(${n},([0-9\. \t]+).+/\1/p" "$file")
    N=$(echo "($normal * $n)/$m" | bc)
    M=$normal
    sed -ri "/Progress/s/Progress[ \t]+\($n,$m\)/Progress ($N,$M)/" "$file"
done

Simply put: This works, but, is there a better way?

My toolbox has sed and bash scripting in it, and not so much perl, awk and the like which I think this problem is more suited to.

Edit Sample input.

Progress label="qt-xx-95" (0, 50) thermal label "qt-xx-95" ramp(slew=.75,sp=95,closed) Progress (20, 50) Pause  5 Progress (25, 50) Pause  5 Progress (30, 50) Pause  5 Progress (35, 50) Pause  5 Progress (40, 50) Pause  5 Progress (45, 50) Pause  5 Progress (50, 50)
Progress label="qt-95-70" (0, 40) thermal label "qt-95-70" hold(sp=70)        Progress (10, 40) Pause  5 Progress (15, 40) Pause  5 Progress (20, 40) Pause  5 Progress (25, 40) Pause  5 
like image 206
Jamie Avatar asked Dec 02 '25 02:12

Jamie


2 Answers

awk has good splitting capabilities, so it might be a good choice for this problem.

Here is a solution that works for the supplied input, let's call it update_m_n_n.awk. Run it like this in bash: awk -f update_m_n_n.awk -v normal=$NORMAL input_file.

#!/usr/bin/awk

BEGIN {
  ORS = RS = "Progress"
  FS = "[)(]"
  if(normal == 0) normal = 10
}

NR == 1 { print }

length > 1 { 
  split($2, A, /, */)
  N = int( normal * A[1] / A[2] )
  M = int( normal )
  sub($2, N ", " M)
  print $0
}

Explanation

  • ORS = RS = "Progress": Split sections at Progress and include Progress in the output.
  • FS = "[)(]": Separate fields at parenthesis.
  • NR == 1 { print }: Insert ORS before the first section.
  • split($2, A, /, */): Assuming there is only on parenthesized item between occurrences of Progress, this splits m and n into the A array.
  • sub($2, N ", " M): Substitute the new values the into current record.
like image 138
Thor Avatar answered Dec 03 '25 16:12

Thor


This is somewhat brittle but it seems to do the trick? It could be changed to a one-line with perl -pe but I think this is clearer:


use 5.16.0;
my $normal = $ARGV[0];
while(<STDIN>){
        s/Progress +(label=\".+?\")? *( *([0-9. ]+) *, *([0-9. ]+) *)/sprintf("Progress $1 (%d,%d)", int(($2/$3)*$normal),int($normal))/eg;
        print $_;
}

The basic idea is to optionally capture the label clause in $1, and to capture n and m into $2 and $3. We use perl's ability to replace the matched string with an evaluated piece of code by providing the "e" modifier. It's going to fail dramatically if the label clause has any escaped quotes or contains the string that matches something that looks like a Progress toekn, so its not ideal. I agree that you need an honest to goodness parser here, though you could modify this regex to correct some of the obvious deficiencies like the weak number matching for n and m.

like image 32
frankc Avatar answered Dec 03 '25 15:12

frankc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!