1

I need to find the max and min of each entry in my file:

#subset of my file
NEUTRON   20.900103
PION-      0.215176
PION-     22.716532
NEUTRON    8.043279
PION+      1.374297
PION-      0.313350
PION+      0.167848

How could I loop through the file and find the min and max for each Name when there are multiple entry names. I have used awk already to count each entry and have no repeats, but each repeat of each name contains a number with it and that number is what im trying to filter out the max and min of each entry. ex output from whole file:

Name         Count     Minimum        Maximum        
--------     -----     ---------      ---------      
KAON-            1      5.489958       5.489958      
NEUTRON          2      8.043279      20.900103      
PHOTON          10      0.034664       1.897264       
PION-            5      0.192247      22.716532       
PION+            7      0.167848       7.631051       
PROTON           1      1.160216       1.160216       
6
  • 1
    Well I recommended SQL in your last Q. Unless you vote me up there, I will not tell you what the aggregate functions for min and max are called ;)
    – user373503
    Oct 11, 2019 at 17:23
  • Welcome to unix.stackexchange.com ! Do you only have value >= 0 in column 2 ? Or can they be negative too ?
    – Cbhihe
    Oct 11, 2019 at 17:30
  • @Cbhihe Every value is greater than 0
    – fgdark
    Oct 11, 2019 at 17:44
  • 1
    bash is just your shell; it is not (good for) a text processor
    – Jeff Schaller
    Oct 11, 2019 at 18:01
  • 1
    You can call any command from bash (or zsh or tcsh or any shell); the shell itself does not matter here.
    – Jeff Schaller
    Oct 11, 2019 at 18:10

2 Answers 2

5
awk '{
  count[$1]++
  min[$1]=(!($1 in min) || $2<min[$1]) ? $2 : min[$1]
  max[$1]=(!($1 in max) || $2>max[$1]) ? $2 : max[$1]
}
END {
  print "Name","Count","Minimum","Maximum"
  print "----","-----","-------","-------"
  for(i in count) print i,count[i],min[i],max[i]
}' file | column -t

The logic of the minimum array value assignment is:
If the name of the first field doesn't exist in the array (!($1 in min)) or (||) the second field is smaller than the current array value ($2<min[$1]), then (?) assign the new value $2, else (:) assign the old value min[$1].

The | column -t is used to pretty-print the result as table. You can remove it if you don't need it.

Output:

Name     Count  Minimum   Maximum
----     -----  -------   -------
PION+    2      0.167848  1.374297
PION-    3      0.215176  22.716532
NEUTRON  2      8.043279  20.900103
1
  • 1
    Always try to write software using positive rather than negative logic since positives are always easier to understand than negatives and cannot lead to obscure double negatives. min[$1]=(($1 in min) && (min[$1]<$2) ? min[$1] : $2) avoids the negative !. Similarly for max of course.
    – Ed Morton
    Oct 12, 2019 at 20:13
3

As pointed out by Jeff Schaller, bash is not a text processor, even though what you ask for is not particularly difficult to achieve. So here is a way to do it,... for whatever it's worth.

$ awk '!/^#.*/ {
      ((++cnt[$1])); 
      if (cnt[$1]==1) 
          {min[$1]=max[$1]=$2} 
      else if ($2 < min[$1]) 
          { min[$1]=$2} 
      else if ($2 > max[$1]) 
          {max[$1]=$2}
      } 
      END {
          printf "%-10s%7s%10s%12s\n","Name","Count","Minimum", "Maximum";
          for (i in cnt) 
              printf "%-10s%7d%10.6g%12.8g\n", i,cnt[i],min[i],max[i];
          }' testdata 

Output:

Name        Count   Minimum     Maximum
PION+           2  0.167848    1.374297
PION-           3  0.215176   22.716532
NEUTRON         2   8.04328   20.900103

Explanation:

  • skip any line (record) that start with a #
  • increment cnt[$1] by 1, for each record, starting from 0 at onset of awk program's execution
  • if first field value "$1" of current record was never seen before, initialize min and max value for that value to the value "$2".
  • in other cases (when value "$1" was seen at least once in a previous record), update min and max values
  • finally print with formating to respect the aspect of your example output in OP.
2
  • I am getting %7d 0 1 %7d 0 1 %7d 0 7 %7d 0 5 %7d 0 2 %7d 0 10 as an output from this
    – fgdark
    Oct 11, 2019 at 19:52
  • @fgdark. Strange ! One character dropped out when I copied-pasted the one-liner from my terminal. It was an "s" in the formatting string. I must have done something by mistake. Corrected.
    – Cbhihe
    Oct 11, 2019 at 20:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .