Grep for the most negative number

Hello, I am trying to write a bash script which will give me the most negative number. Here is an example input:

 Ce         3.7729752124       -4.9505731588       -4.1061257680
 Ce        -6.9156611391       -0.5991784762        7.3051893138
 Ce         7.6489739875        0.3513020731       -4.1428887409
  O        -2.0367674927        6.4059113030        4.2342869120
  O         1.8496529300        6.8212696160        0.4577675608

I used

grep -- "-*"

which returns all lines with negative numbers, what I really need is for it to look and find (in this instance) -6.9156611391. Then add +6.9156611391 to all the numbers in the file ex.

Ce         3.7729752124       -4.9505731588       -4.1061257680

to

Ce        10.6886363515       1.9650879...          2.80953537...

All lines do not necessarily contain number nor do they start with Ce or O,
however all numbers that need changed have the 3 set format (x,y,z coordinates)

Thanks in advance

Hi sdl27789,

One way using perl:

$ cat infile
 Ce         3.7729752124       -4.9505731588       -4.1061257680
 Ce        -6.9156611391       -0.5991784762        7.3051893138
 Ce         7.6489739875        0.3513020731       -4.1428887409
  O        -2.0367674927        6.4059113030        4.2342869120
  O         1.8496529300        6.8212696160        0.4577675608
$ cat script.pl
use warnings;
use strict;
use List::Util qw[ min ];

die qq[Usage: perl $0 <input-file>\n] unless @ARGV == 1;

push @ARGV, $ARGV[0];

my ($negative_num, $positive_num);

while ( <> ) {
        chomp;
        my @f = split;
        shift @f;
        my $min = min @f;
        $negative_num = !defined $negative_num || $min < $negative_num ? $min : $negative_num;
}
continue {
        last if eof;
}

$positive_num = abs $negative_num;

while ( <> ) {
        chomp;
        my @f = split;
        for my $pos ( 1 .. $#f ) {
                $f[ $pos ] += $positive_num;
        }

        printf qq[%s\n], join qq[\t], @f;
}
$ perl script.pl infile
Ce      10.6886363515   1.9650879803    2.8095353711
Ce      0       6.3164826629    14.2208504529
Ce      14.5646351266   7.2669632122    2.7727723982
O       4.8788936464    13.3215724421   11.1499480511
O       8.7653140691    13.7369307551   7.3734286999

And another -

$
$ cat f66
Ce 3.7729752124 -4.9505731588 -4.1061257680
Ce -6.9156611391 -0.5991784762 7.3051893138
Ce 7.6489739875 0.3513020731 -4.1428887409
O -2.0367674927 6.4059113030 4.2342869120
O 1.8496529300 6.8212696160 0.4577675608
$
$
$ perl -ane 'push @x, [@F];
            map {$min=$x[$.-1]->[$_] if $x[$.-1]->[$_] < $min} (1..$#F);
            END {
              foreach $i (@x) {print join(" ", map{$_==0 ? $i->[$_] : $i->[$_]-$min} (0..$#{$i})),"\n"}
            }' f66
Ce 10.6886363515 1.9650879803 2.8095353711
Ce 0 6.3164826629 14.2208504529
Ce 14.5646351266 7.2669632122 2.7727723982
O 4.8788936464 13.3215724421 11.1499480511
O 8.7653140691 13.7369307551 7.3734286999
$
$

tyler_durden

grep "-"

was sufficient. The * didn't do what you think it did -- it would've matched any number of dashes in a row.

To get the most negative number:

awk '{ for(N=2; N<=NF; N++) if((!MIN)||($N < MIN)) MIN=$N } END { print MIN }' filename

Expanding on that, this finds the minimum then subtracts from all lines:

awk -v OFS="\t" '
# Run this for all lines in the first given file
NR==FNR { for(N=2; N<=NF; N++) if((!MIN)||($N < MIN)) MIN=$N; next }
# Run this for all lines in any following files
{ for(N=2; N<=NF; N++) $N -= MIN; } 1' inputfile inputfile > outputfile

Yes, the same file is listed twice intentionally. The first time it reads it, it finds the minimum; the second time, it subtracts the minimum and prints.

It gives me this output:

Ce      10.6886 1.96509 2.80954
Ce      0       6.31648 14.2209
Ce      14.5646 7.26696 2.77277
O       4.87889 13.3216 11.1499
O       8.76531 13.7369 7.37343

Keep in mind that this can't write to the same file it's reading from.

Thank you for the replies, it almost works...
In the first reply,
0 needs to comeback as 0.0000000

in the second reply I received two strange parts in the file, one, the line
O 11.4730666365 0.0845493182000006 7.1215466626 with several extra decimal points
Two, a line O 12.69964795 0 12.7680007771 with no z coordinate.

In the third reply the decimal points end at 5 places.

I should add the text file is several thousand lines long so the demo values I gave are not all of it nor the lowest value

This will give you 10 decimal places:

awk -v OFS="\t" '
# Run this for all lines in the first given file
NR==FNR { for(N=2; N<=NF; N++) if((!MIN)||($N < MIN)) MIN=$N; next }
# Run this for all lines in any following files
{ for(N=2; N<=NF; N++) $N =sprintf("%.10f", $N-MIN); } 1' inputfile inputfile > outputfile

But awk math is floating point, not infinite precision.

1 Like

Fixed my script (modifications in red):

 $ cat script.pl
use warnings;
use strict;
use List::Util qw[ min ];

die qq[Usage: perl $0 <input-file>\n] unless @ARGV == 1;

push @ARGV, $ARGV[0];

my ($negative_num, $positive_num);

while ( <> ) { 
        chomp;
        my @f = split;
        shift @f; 
        my $min = min @f; 
        $negative_num = !defined $negative_num || $min < $negative_num ? $min : $negative_num;
}
continue {
        last if eof;
}

$positive_num = abs $negative_num;

while ( <> ) { 
        chomp;
        my @f = split;
        for my $pos ( 1 .. $#f ) { 
                $f[ $pos ] += $positive_num;
        }   

         #printf qq[%s\n], join qq[\t], @f;        
         printf qq[%s\t%.10f\t%.10f\t%.10f\n], @f; 
}
$ perl script.pl infile
Ce      10.6886363515   1.9650879803    2.8095353711
Ce      0.0000000000    6.3164826629    14.2208504529
Ce      14.5646351266   7.2669632122    2.7727723982
O       4.8788936464    13.3215724421   11.1499480511
O       8.7653140691    13.7369307551   7.3734286999
1 Like