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

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 ))

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

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.