0

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       
  • 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 '19 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 '19 at 17:30
  • @Cbhihe Every value is greater than 0 – fgdark Oct 11 '19 at 17:44
  • 1
    bash is just your shell; it is not (good for) a text processor – Jeff Schaller Oct 11 '19 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 '19 at 18:10
4
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
    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 '19 at 20:13
2

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.
  • 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 '19 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 '19 at 20:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.