Joe Joe - 6 months ago 7
Perl Question

Edit a file without change in formatting

I have a text file containing a strings and numbers. I would like to create a short script that searches for a given string, and if the string is found, it multiplies the nth space-separated argument of that line by a scaling factor and saves everything into a new file.

We can assume there will be only one such string in the file, and also that the nth argument on that line will be a floating point number. Ideally the program would preserve the formatting of the original file as well.

For example to search for the string "ab ce" and multiply the fourth argument of that line by 2.0:

./prog "ab ce" 4 2.0 inputfile.txt output.txt


inputfile.txt:

ab cd 1.0 2.0 3.0
ab ce 1.0 2.0 3.0
ac ce 1.0 2.0 3.0
ac ce de ef 1.0 2.0 3.0


output.txt

ab cd 1.0 2.0 3.0
ab ce 1.0 4.0 3.0
ac ce 1.0 2.0 3.0
ac ce de ef 1.0 2.0 3.0


I have attempted to do this with awk (below), however this does not preserve the formatting and I suspect there is a more elegant solution. Can someone suggest another approach?

#!/bin/sh

search_string=$1
n=$2
scaling=$3
inputfilename=$4
outputname=$5

awk -vORS= -v scal="$scaling" -v ch="$n" -v inp="$search_string" 'BEGIN {} {if($0 ~ inp){ s = ""; for (i = 1; i < ch; i++) s = s $i " "; print s; print $ch*scal," "; s = ""; for (i = ch+1; i <= NF; i++) s = s $i " "; print s,"\n" } else print $0,"\n" } END{}' $inputfilename>$outputname

Answer

A solution using GNU awk:

awk -v pattern="ab ce" -v scale=2.0 -v col=4 -f input.awk inputfile.txt

The input.awk:

pattern {
    count = split($0, fields, / +/, seps);
    printf fields[1];
    for (i = 2; i <= count; i++) {
        if (i == col) {
            len = length(seps[col-1] fields[col]);
            printf("%" len ".1f", fields[4] * scale);
        } else {
            printf(seps[i-1] fields[i]);
        }
    }
    print "";
    next;
}
{ print }