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 \n
ewline 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 S
aved 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 ex
ecute 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 l
oad them when wanted and ex
ecute them conditionally, and I s
tore $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
- Thisl
oads and ex
ecutes the macro stored in the top ofT
(for test, I guess - I usually pick those names arbitrarily).z 0=?
-T
est 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 anotherz
stack depth test to it, so that it canq
uit 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 callsT
est again.d lm<M
-T
est will thend
uplicate the top of stack and compare it to$max
(as stored inm
). Ifm
is the lesser value,dc
calls theM
macro.s0 lm
-M
just pops the top of stack and dumps it to the dummy scalar0
- just a cheap way of popping the stack. It alsol
oadsm
again before returning toT
est.p
- This means that ifm
is less than the current top of stack, thenm
replaces it (thed
uplicate of it, anyway) and is herep
rinted, else it does not and whatever the input was isp
rinted instead.s0
- Afterward (becausep
doesn't pop the stack) we dump the top of stack into0
again, and then...lTx
- recursivelyl
oadT
est once more then ex
ecute 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 i
nput or o
utput 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.