Is there a unix command that gives the minimum/maximum of two numbers?

95,059

Solution 1

You can compare just two numbers with dc like:

dc -e "[$1]sM $2d $1<Mp"

... where "$1" is your max value and "$2" is the number you would print if it is lesser than "$1". That also requires GNU dc - but you can do the same thing portably like:

dc <<MAX
    [$1]sM $2d $1<Mp
MAX

In both of the above cases you can set the precision to something other than 0 (the default) like ${desired_precision}k. For both it is also imperative that you verify that both values are definitely numbers because dc can make system() calls w/ the ! operator.

With the following little script (and the next) you should verify the input as well - like grep -v \!|dc or something to robustly handle arbitrary input. You should also know that dc interprets negative numbers with a _ prefix rather than a - prefix - because the latter is the subtraction operator.

Aside from that, with this script dc will read in as many sequential \newline separated numbers as you would care to provide it, and print for each either your $max value or the input, depending on which is the lesser of the wo:

dc -e "${max}sm
       [ z 0=? d lm<M p s0 lTx ]ST
       [ ? z 0!=T q ]S?
       [ s0 lm ]SM lTx"

So... each of those [ square bracketed ] expanses is a dc string object that is Saved each to its respective array - any one of T, ?, or M. Besides some few other things dc might do with a string, it can also execute one as a macro. If you arrange it right a fully functioning little dc script is assembled simply enough.

dc works on a stack. All input objects are stacked each upon the last - each new input object pushing the last top object and all objects below it down on the stack by one as it is added. Most references to an object are to the top stack value, and most references pop that top of stack (which pulls all objects below it up by one).

Besides the main stack, there are also (at least) 256 arrays and each array element comes with a stack all its own. I don't use much of that here. I just store the strings as mentioned so I can load them when wanted and execute them conditionally, and I store $max's value in the top of the m array.

Anyway, this little bit of dc does, largely, what your shell-script does. It does use the GNU-ism -e option - as dc generally takes its parameters from standard-in - but you could do the same like:

echo "$script" | cat - /dev/tty | dc

...if $script looked like the above bit.

It works like:

  • lTx - This loads and executes the macro stored in the top of T (for test, I guess - I usually pick those names arbitrarily).
  • z 0=? - Test then tests the stack depth w/ z and, if the stack is empty (read: holds 0 objects) it calls the ? macro.
  • ? z0!=T q - The ? macro is named for the ? dc builtin command which reads a line of input from stdin, but I also added another z stack depth test to it, so that it can quit the whole little program if it pulls in a blank line or hits EOF. But if it does !not and instead successfully populates the stack, it calls Test again.
  • d lm<M - Test will then duplicate the top of stack and compare it to $max (as stored in m). If m is the lesser value, dc calls the M macro.
  • s0 lm - M just pops the top of stack and dumps it to the dummy scalar 0 - just a cheap way of popping the stack. It also loads m again before returning to Test.
  • p - This means that if m is less than the current top of stack, then m replaces it (the duplicate of it, anyway) and is here printed, else it does not and whatever the input was is printed instead.
  • s0 - Afterward (because p doesn't pop the stack) we dump the top of stack into 0 again, and then...
  • lTx - recursively load Test once more then execute it again.

So you could run this little snippet and interactively type numbers at your terminal and dc would print back at you either the number you entered or the value of $max if the number you typed was larger. It would also accept any file (such as a pipe) as standard input. It will continue the read/compare/print loop until it encounters a blank line or EOF.

Some notes about this though - I wrote this just to emulate the behavior in your shell function, so it only robustly handles the one number per line. dc can, however, handle as many space separated numbers per line as you would care to throw at it. However, because of its stack the last number on a line winds up being the first it operates on, and so, as written, dc would print its output in reverse if you printed/typed more than one number per line at it.The proper way to handle that is to store up a line in an array, then to work it.

Like this:

dc -e "${max}sm
    [ d lm<M la 1+ d sa :a z0!=A ]SA
    [ la d ;ap s0 1- d sa 0!=P ]SP 
    [ ? z 0=q lAx lPx l?x ]S?
    [q]Sq [ s0 lm ]SM 0sa l?x"

But... I don't know if I want to explain that in quite as much depth. Suffice it to say that as dc reads in each value on the stack it stores either its value or $max's value in an indexed array, and, once it detects the stack is once again empty, it then prints each indexed object before attempting to read another line of input.

And so, while the first script does...

10 15 20 25 30    ##my input line
20
20
20
15
10                ##see what I mean?

The second does:

10 15 20 25 30    ##my input line
10                ##that's better
15
20
20                ##$max is 20 for both examples
20

You can handle floats of arbitrary precision if you first set it with the k command. And you can alter the input or output radices independently - which can sometimes be useful for reasons you might not expect. For example:

echo 100000o 10p|dc
 00010

...which first sets dc's output radix to 100000 then prints 10.

Solution 2

If you know you are dealing with two integers a and b, then these simple shell arithmetic expansions using the ternary operator are sufficient to give the numerical max:

$(( a > b ? a : b ))

and numerical min:

$(( a < b ? a : b ))

E.g.

$ a=10
$ b=20
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
20
$ echo $min
10
$ a=30
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
30
$ echo $min
20
$ 

Here is a shell script demonstrating this:

#!/usr/bin/env bash
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }
read number
echo Min: $(( $number  < $1 ? $number : $1 ))
echo Max: $(( $number  > $1 ? $number : $1 ))

Solution 3

sort and head can do this:

numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1       # => 21

Solution 4

You can define a library of predefined math functions for bc and then use them in the command line.

For example, include the following in a text file such as ~/MyExtensions.bc:

define max(a,b){
  if(a>b)
  { 
   return(a)
  }else{
   return(b)
  }
}

Now you can call bc by:

> echo 'max(60,54)' | bc ~/MyExtensions.bc
60

FYI, there are free math library functions such as this available online.

Using that file, you can easily calculate more complicated functions such as GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6

Solution 5

Too long for a comment:

While you can do these things e.g. with the sort | head or sort | tail combos, it seems rather suboptimal both resource- and error-handling-wise. As far as execution is concerned, the combo means spawning 2 processes just to check two lines. That seems to be a bit of an overkill.

The more serious problem is, that in most cases you need to know, that the input is sane, that is contains only numbers. @glennjackmann's solution cleverly solves this, since printf %d should barf on non-integers. It won't work with floats either (unless you change the format specifier to %f, where you will run into rounding problems).

test $1 -gt $2 will give you indication of whether the comparison failed or not (exit status of 2 means there was an error during the test. Since this usually is a shell built-in, there is no additional process spawned - we're talking of order of hundreds times faster execution. Works with integers only, though.

If you happen to need to compare a couple of floating point numbers, interesting option might be bc:

define x(a, b) {
    if (a > b) {
       return (a);
    }
    return (b);
 }

would be the equivalent of test $1 -gt $2, and using in in shell:

max () { printf '
    define x(a, b) {
        if (a > b) {
           return (a);
        }
        return (b);
     }
     x(%s, %s)
    ' $1 $2 | bc -l
}

is still almost 2.5 times faster than printf | sort | head (for two numbers).

If you can rely on GNU extensions in bc, then youcan also use the read() function to read the numbers directly into the bc sript.

Share:
95,059

Related videos on Youtube

Minix
Author by

Minix

Coming from Germany, identifiable by my love, of, commas.

Updated on September 18, 2022

Comments

  • Minix
    Minix almost 2 years

    I was looking for a command to limit numbers read in from stdin.

    I wrote a little script for that purpose (critique is welcome), but I was wondering if there was not a standard command for this, simple and (I think) common use case.

    My script which finds the minimum of two numbers:

    #!/bin/bash
    # $1 limit
    
    [ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }
    
    read number
    
    if [ "$number" -gt "$1" ]; then
            echo "$1"
    else
            echo "$number"
    fi
    
    • Basile Starynkevitch
      Basile Starynkevitch about 4 years
      Writing a tiny C program doing that is conforming to the unix philosophy, and you could write a tiny script doing that in lua, python, gawk, guile, etc....
    • Basile Starynkevitch
      Basile Starynkevitch about 4 years
      If you care about floating point, be aware that NAN is not comparable. See floating-point-gui.de for more
  • mikeserv
    mikeserv over 9 years
    My thoughts exactly - I was just ironing this out, but you beat me to it: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx" - oh, except that dc does the whole thing (excepting the echo, though it could) - it reads stdin and prints either $max or the input number depending on which is smaller. Anyway, I don't really care to explain it and your answer is better than I was gonna write. So have my upvote, please.
  • peterph
    peterph over 9 years
    @mikeserv actually having an explained dc script would be really nice, RPN is not seen that often these days.
  • peterph
    peterph over 9 years
    Reverse Polish notation (aka Postfix notation). Plus if dc can do the I/O on it's own it would be even more elegant than.
  • Lie Ryan
    Lie Ryan over 9 years
    Note that this is O(n log(n)) while an efficient implementation of max would be O(n). It is our little significance is n=2 though, since the spawning of two processes are much bigger overhead.
  • Minix
    Minix over 9 years
    +1 for having no idea what just happened after reading it twice. Will have to take my time to delve into this.
  • orion
    orion over 9 years
    Most shells don't do floating point arithmetics. But it does work for integers.
  • David Hoelzer
    David Hoelzer over 9 years
    While true, @glenn-jackman, I'm not sure it matters given the question. There was no request for the most efficient way to do it. I think the question was more about convenience.
  • muru
    muru over 9 years
    Might be better if you used sys.argv: python2 -c 'import sys; print (max(sys.argv))' "$@"
  • mikeserv
    mikeserv over 9 years
    The arguments that sort + head is overkill but python is not do not compute.
  • mikeserv
    mikeserv over 9 years
    @Minix - meh - no need to go delving into the oldest Unix programming language if you find it confusing. Maybe just pipe a few numbers at dc every once in awhile to keep it on its toes, though.
  • orion
    orion over 9 years
    All the methods above the line are designed to handle huge sets of numbers and explicitly suggest this sort of use (reading from a pipe or a file). min/max for 2 arguments is question that feels differently -- it calls for a function instead of a stream. I just meant that stream approach is overkill - the tool you use is arbitrary, I just used python because it's neat.
  • mikeserv
    mikeserv over 9 years
    I would call this suggested solution neater, but that might be because I'm a python bigot (or because it doesn't require a fork and an additional gigantic interpreter). Or maybe both.
  • Charles Duffy
    Charles Duffy over 9 years
    This function is needlessly POSIX-incompatible. Change function maxnum { to maxnum() { and it'll work for far more shells.
  • mikeserv
    mikeserv over 9 years
    @DavidHoelzer - this isn't the most efficient way to do this even among the answers offered here. If working with number sets there is at least one other answer here that is more efficient than this (by orders of magnitude), and if only working with two integers there is another answer here more efficient than that (by orders of magnitude). It is convenient though (but I would probably leave out the shell array, personally).
  • mikeserv
    mikeserv over 9 years
    If I'm not mistaken, such functions can also be compiled in with the executable if necessary. I think most bcs are just dc frontends to this day, though, even if GNU bc is no longer such (but GNU dc and GNU bc share a prodigious amount of their codebase). Anyway, this might be the best answer here.
  • orion
    orion over 9 years
    @mikeserv I'd use that too if I knew they were integers. All these solutions I mentioned are under assumption that the numbers may be floats - bash doesn't do floating point and unless zsh is your native shell, you will need a fork (and possibly a knife).
  • mikeserv
    mikeserv over 9 years
    But with just 2 numbers? You just slice em on . (or whatever the locale's divider is). Verify input w/ case and just split it w/ IFS=.. That way - with set - you can even compare the len first for a really quick check. Anyway, in my opinion, python is never neat. Or, for just two numbers: dc -e "[$1]s1 $2d $1<1p" - though w/ dc you might want to set the precision with 10k first or whatever.
  • Minix
    Minix over 9 years
    @mikeserv It's too late for me. I hope future generation take my as a cautionary tale. Square brackets and letters everywhere...
  • mikeserv
    mikeserv over 9 years
    @Minix - what do you mean? You went for it? Very good - dc is a fickle beast, but it might just be the fastest and most weirdly capable common utility on any Unix system. When paired w/ sed it can do some extraordinary things. I've been playing with it and dd lately so that I might replace the monstrosity that is readline. Here's a tiny sample of some stuff I've been doing. Doing a rev in dc is almost child's play.
  • mikeserv
    mikeserv over 9 years
    @Minix - careful w/ the brackets, though. There's no way to put a square bracket within a string - the best you can do is [string]P91P93P[string]P. So I have this little bit of sed you might find useful: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g' which should always replace the squares correctly with a string close bracket, then a P, then the square's decimal ascii value and another P; then an open [ square bracket to continue the string. Dunno if you've messed around w/ dc's string/numeric conversion capabilities, but - especially when combined w/ od - it can be pretty fun.
  • tanius
    tanius almost 9 years
    To conveniently call this within a shell script's file, you can pipe in the function definition to bc as well, right before the function call. No second file needed then :)
  • Arlene Mariano
    Arlene Mariano about 8 years
    Nice answer. Please, a minor mod: could this be used too for ">=" ?
  • Digital Trauma
    Digital Trauma about 8 years
    @SopalajodeArrierez I'm not entirely sure what you mean. You can also do max=$(( a >= b ? a : b )), but the result is entirely the same - if a and b are equal, then it doesn't really matter which one is returned. Is that what you're asking?
  • Arlene Mariano
    Arlene Mariano about 8 years
    Indeed, thanks you, DIgital Trauma. I just was wondering if the boolean operator ">=" was possible here.
  • Digital Trauma
    Digital Trauma about 8 years
    @SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi - is that what you're asking for? (note the use of (( )) here instead of $(( )))
  • Arlene Mariano
    Arlene Mariano about 8 years
    Ah, yeah, OK. I understand now. I don't know much about shell expansion, so I usually get confused between conditions. Thanks you again.
  • ngreen
    ngreen about 5 years
    This can be done without arrays as follows: numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
  • ngreen
    ngreen about 5 years
    A more efficient approach is probably this: max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
  • PeterT
    PeterT over 3 years
    "In my opinion most of the answers missed OP's point as they are all custom scripts" NIH: not invented here: people like to show off their lame code skills, rather than check to see if a solution already exists. :) ... like datamash. Although I expected datamash to be older, since Unix/GNU solved many CLI problems in the 80's...
  • xeruf
    xeruf over 2 years
    Fixed - expr treats 0 as null value, so I had to add parentheses. But there are usecases that don't come upon this anyways, like argument counts, that is why it was intentionally simple.
  • Kamil Maciorowski
    Kamil Maciorowski over 2 years
    Fixed(?): expr \( "$a" \& "$a" \> "$b" \) \| \( "$b" \& "$a" \<= "$b" \). Quite complicated. Not sure if the simplest solution.