Kay
Kay

Reputation: 2067

Do some calculation in a text file in shell

I have a text file:

$cat ifile.txt
this is a text file
assign x to 9 and y to 10.0702
define f(x)=x+y

I would like to disable the original line and divide the x-value by 2 and multiply the y-value by 2

My desired output is

$cat ofile.txt
this is a text file
#assign x to 9 and y to 10.0702    
assign x to 5 and y to 20.1404
define f(x)=x+y

Here 5 is calculate from 9/2 and rounded to the next integer and 20.14 is calculated from 10.07x2 and not rounded

I am thinking of the following way, but can't write a script.

if [ line contains "assign x to" ]; then new_x_value=[next word]/2
if [ line contains "and y to" ]; then new_y_value=[next word]x2

if [ line contains "assign x to" ]; 
   then disable it and add a line "assign x to new_x_value and y to new_y_value"

Upvotes: 0

Views: 260

Answers (3)

tshiono
tshiono

Reputation: 22012

Would you please try the following:

#!/bin/bash

pat="(assign x to )([[:digit:]]+)( and y to )([[:digit:].]+)"
while IFS= read -r line; do
    if [[ $line =~ $pat ]]; then
        echo "#$line"
        x2=$(echo "(${BASH_REMATCH[2]} + 1) / 2" | bc)
        y2=$(echo "${BASH_REMATCH[4]} * 2" | bc)
        echo "${BASH_REMATCH[1]}$x2${BASH_REMATCH[3]}$y2"
    else
        echo "$line"
    fi
done < ifile.txt > ofile.txt

Output:

this is a text file
#assign x to 9 and y to 10.0702
assign x to 5 and y to 20.1404
define f(x)=x+y
  • The regex (assign x to )([[:digit:]]+)( and y to )([[:digit:].]+) matches a literal string, followed by digits, followed by a literal string, and followed by digits including decimal point.
  • The bc command (${BASH_REMATCH[2]} + 1) / 2 caclulates the ceiling value of the input divided by 2.
  • The next bc command ${BASH_REMATCH[4]} * 2 multiplies the input by 2.

The reason I have picked bash is just because it supports back reference in regex and is easier to parse and reuse the input parameters than awk. As often pointed out, bash is not suitable for processing large files due to the performance reason. If you plan to large / multiple files, it will be recommended to use other languages like perl.

With perl you can say:

perl -pe 's|(assign x to )([0-9]+)( and y to )([0-9.]+)|
    "#$&\n" . $1 . int(($2 + 1) / 2) . $3 . $4 * 2|ge' ifile.txt > ofile.txt

[EDIT]

If your ifile.txt looks like:

this is a text file
assign x to   9 and y to   10.0702   45
define f(x)=x+y
  • There are more than one space before the numbers.
  • One more value exists at the end (after whitespaces).

Then please try the following instead:

pat="(assign x to +)([[:digit:]]+)( and y to +)([[:digit:].]+)( +)([[:digit:].]+)"
while IFS= read -r line; do
    if [[ $line =~ $pat ]]; then
        echo "#$line"
        x2=$(echo "(${BASH_REMATCH[2]} + 1) / 2" | bc)
        y2=$(echo "${BASH_REMATCH[4]} * 2" | bc)
        y3=$(echo "${BASH_REMATCH[6]} * 2" | bc)
        echo "${BASH_REMATCH[1]}$x2${BASH_REMATCH[3]}$y2${BASH_REMATCH[5]}$y3"
    else
        echo "$line"
    fi
done < ifile.txt > ofile.txt

Result:

this is a text file
#assign x to   9 and y to   10.0702   45
assign x to   5 and y to   20.1404   90
define f(x)=x+y

The plus sign after a whitespace is a regex quantifier and defines the number of repetition. In this case it matches one or more whitespace(s).

Upvotes: 1

Digvijay S
Digvijay S

Reputation: 2705

awk '{if(match($0,/^assign/)){b=$0;split($0,a," ");a[8]=a[8]/2;a[4]=a[4]/2; for (x in a) {c = a[x] " " c; $0 = "#" b "\n" c } } { print }}'

Demo :

:>awk  ' { if(match ($0, /^assign/)) {b=$0;split($0,a," ");a[8]=a[8]/2; a[4]=a[4]/2; for (x in a) {c = a[x] " " c; $0 = "#" b "\n" c } } { print }}' <ifile
this is a text file
#assign x to 9 and y to 10.0702
to x assign 5.0351 to y and 4.5
define f(x)=x+y
:>

Explanation:

awk  ' { 
if(match ($0, /^assign/))  <--- $0 is whole input record. ^ is start of line. 
                                 We are checking if record is starting with "assign"

    {b=$0; <-- Assign input value to variable b 
     split($0,a," "); <-- Create a array by splitting input record with space as separator 
     a[8]=a[8]/2; a[4]=a[4]/2; <--  Divide value stored in 8 and 4 index
     for (x in a)  <-- Loop for getting all values of array 
          {c = a[x] " " c; <-- Create a variable by concatenating values of a   
     $0 = "#" b "\n" c <-- Update value of current record. "\n" new line operator 
    } } 
{ print }}' 

Upvotes: 1

James Brown
James Brown

Reputation: 37404

One in awk:

awk '
/assign/ {                                          # when assign met in record
    for(i=1;i<=NF-1;i++)                            # iterate from the beginning
        if($i=="to" && $(i-1)=="x")                 # if to x
            $(i+1)=((v=$(i+1)/2)>(u=int(v))?u+1:u)  # ceil of division
        else if($i=="to" && $(i-1)=="y")            # if to y
            $(i+1)*=2                               # multiply by 2
}1' file                                            # output

Output:

this is a text file
assign x to 5 and y to 20.1404
define f(x)=x+y

Sanity checking of the ceiling calculation left as homework...

Upvotes: 1

Related Questions