Do some calculation in a text file in shell

Kay :

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"
tshiono :

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).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=336637&siteId=1