views:

72

answers:

5

I have a file that has two columns of floating point values. I also have a C program that takes a floating point value as input and returns another floating point value as output.

What I'd like to do is the following: for each row in the original, execute the C program with the value in the first column as input, and then print out the first column (unchanged) followed by the second column minus the result of the C program.

As an example, suppose c_program returns the square of the input and behaves like this:

$ c_program 4
16
$

and suppose data_file looks like this:

1 10
2 11
3 12
4 13

What I'd like to return as output, in this case, is

1 9
2 7
3 3
4 -3

To write this in really sketchy pseudocode, I want to do something like this:

awk '{print $1, $2 - `c_program $1`}' data_file

But of course, I can't just pass $1, the awk variable, into a call to c_program. What's the right way to do this, and preferably, how could I do it while still maintaining the "awk one-liner"? (I don't want to pull out a sledgehammer and write a full-fledged C program to do this.)

A: 

This shows how to execute a command in awk:

ls | awk '/^a/ {system("ls -ld " $1)}'

You could use a bash script instead:

while read line
do 
    FIRST=`echo $line | cut -d' ' -f1`
    SECOND=`echo $line | cut -d' ' -f2`
    OUT=`expr $SECOND \* 4`
    echo $FIRST $OUT `expr $OUT - $SECOND`
done
Sjoerd
expr doesn't support floating values, use bc , dc or awk
ghostdog74
A: 

The shell is a better tool for this using a little used feature. There is a shell variable IFS which is the Input Field Separator that sh uses to split command lines when parsing; it defaults to <Space><Tab><Newline> which is why ls foo is interpreted as two words.

When set is given arguments not beginning with - it sets the positional parameters of the shell to the contents of the arguments as split via IFS, thus:

#!/bin/sh
while read line ; do
    set $line
    subtrahend=`c_program $1`     
    echo $1 `expr $2 - $subtrahend`
done < data_file
msw
`expr` doesn't support floating values, use `bc` , `dc` or `awk`
ghostdog74
+2  A: 

you just do everything in awk

awk '{cmd="c_program "$1; cmd|getline l;print $1,$2-l}' file
ghostdog74
Very elegant; this is exactly what I was looking for. Thanks!
Elliott
+1  A: 

Pure Bash, without using any external executables other than your program:

#!/bin/bash
while read num1 num2
do
    (( result = $(c_program num2) - num1 ))
    echo "$num1 $result"
done
Dennis Williamson
Ah, I didn't know about this nice bash feature. Thanks!
Elliott
there's also the issue of floating pt calculations using bash, although OP has not stated that he might have floats(decimals)
ghostdog74
A: 

As others have pointed out: awk is not not well equipped for this job. Here is a suggestion in bash:

#!/bin/sh

data_file=$1

while read column_1 column_2 the_rest
do

    ((result=$(c_program $column_1)-$column_2))
    echo $column_1 $result "$the_rest"

done < $data_file

Save this to a file, say myscript.sh, then invoke it as:

sh myscript.sh data_file

The read command reads each line from the data file (which was redirected to the standard input) and assign the first 2 columns to $column_1 and $column_2 variables. The rest of the line, if there is any, is stored in $the_rest.

Next, I calculate the result based on your requirements and prints out the line based on your requirements. Note that I surround $the_rest with quotes to reserve spacing. Failure to do so will result in multiple spaces in the input file to be squeezed into one.

Hai Vu
@Hai, "awk is not not well equipped for this job.".That's nonsense. You should also take note of floating point calculations. Bash fails on that but awk doesn't.
ghostdog74
@ghostdog74: I stand corrected.
Hai Vu