¿Hay un comando unix que dé el mínimo / máximo de dos números?

37

Estaba buscando un comando para limitar los números leídos stdin.

Escribí un pequeño guión para ese propósito (la crítica es bienvenida), pero me preguntaba si no había un comando estándar para esto, simple y (creo) un caso de uso común.

Mi script que encuentra el mínimo de dos números:

#!/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
Minix
fuente

Respuestas:

20

Puede comparar solo dos números con me dcgusta:

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

... dónde "$1"está su valor máximo y "$2"es el número que imprimiría si es menor que "$1". Eso también requiere GNU dc, pero puede hacer lo mismo de forma portátil como:

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

En los dos casos anteriores, puede establecer la precisión en algo diferente a 0 (el valor predeterminado) como ${desired_precision}k. Para ambos, también es imprescindible que verifique que ambos valores son definitivamente números porque dcpueden hacer system()llamadas con el !operador.

Con el siguiente pequeño script (y el siguiente), también debe verificar la entrada, como grep -v \!|dcalgo o algo para manejar de manera robusta la entrada arbitraria. También debe saber que dcinterpreta los números negativos con un _prefijo en lugar de un -prefijo, porque este último es el operador de resta.

Aparte de eso, con este script dcleerá tantos \nnúmeros secuenciales separados por línea de cable como le gustaría proporcionarlo, e imprimirá para cada uno su $maxvalor o la entrada, dependiendo de cuál sea el menor de los wo:

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

Así que ... cada uno de esos [corchetes cuadrados ]extensiones es una dc cadena de objeto que se Saved cada uno a su respectiva serie - cualquiera de T, ?o M. Además de algunas otras cosas que dcpodrían hacer con una cadena , también puede xejecutar una como macro. Si lo arreglas correctamente, un pequeño dcscript completamente funcional se ensambla simplemente.

dcFunciona en una pila . Todos los objetos de entrada se apilan cada uno sobre el último: cada nuevo objeto de entrada empuja el último objeto superior y todos los objetos debajo de él en la pila uno a medida que se agrega. La mayoría de las referencias a un objeto son al valor de la pila superior, y la mayoría de las referencias resaltan esa parte superior de la pila (que tira todos los objetos debajo de uno hacia arriba) .

Además de la pila principal, también hay (al menos) 256 matrices y cada elemento de la matriz viene con una pila propia. No uso mucho de eso aquí. Simplemente almaceno las cadenas como se menciona para poder lcargarlas cuando lo desee y xejecutarlas condicionalmente, y srasgué $maxel valor en la parte superior de la mmatriz.

De todos modos, este poquito dchace, en gran medida, lo que hace tu script de shell. Utiliza la -eopción GNU-ism , ya que dcgeneralmente toma sus parámetros de la entrada estándar, pero puede hacer lo mismo como:

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

... si se $scriptparecía al bit de arriba.

Funciona como:

  • lTx- Esto lcarga y ecupera xla macro almacenada en la parte superior de T (para prueba, supongo, generalmente elijo esos nombres arbitrariamente) .
  • z 0=?- Test entonces prueba la profundidad de la pila w / zy, si la pila está vacía (léase: contiene 0 objetos) llama a la ?macro.
  • ? z0!=T q- La ?macro lleva el nombre del ? dccomando incorporado que lee una línea de entrada desde stdin, pero también le agregué otra zprueba de profundidad de pila, para que pueda qusar todo el pequeño programa si tira de una línea en blanco o golpea EOF. Pero si no lo hace !y en su lugar llena la pila con éxito, vuelve a llamar a Test.
  • d lm<M- Test luego dduplicará la parte superior de la pila y la comparará con $max (como está almacenada en m) . Si mes el valor menor, dcllama a la Mmacro.
  • s0 lm- Msolo hace estallar la parte superior de la pila y la descarga al escalar ficticio 0, solo una forma barata de hacer estallar la pila. También se lcarga mnuevamente antes de regresar a Test.
  • p- Esto significa que si mes menor que la parte superior actual de la pila, entonces lo mreemplaza (el dduplicado, de todos modos) y está aquí pborrado, de lo contrario no lo hace y lo que sea que se haya pborrado la entrada .
  • s0- Después (porque pno hace estallar la pila) volcamos la parte superior de la pila 0nuevamente, y luego ...
  • lTx- lOcurre recursivamente Tuna vez más y luego xvuelva a ejecutarlo.

Por lo tanto, podría ejecutar este pequeño fragmento y escribir números de forma interactiva en su terminal e dcimprimirle el número que ingresó o el valor de $maxsi el número que escribió fue mayor. También aceptaría cualquier archivo (como una tubería) como entrada estándar. Continuará el ciclo de lectura / comparación / impresión hasta que encuentre una línea en blanco o EOF.

Sin embargo, algunas notas sobre esto: escribí esto solo para emular el comportamiento en su función de shell, por lo que solo maneja de manera robusta un número por línea. dcsin embargo, puede manejar tantos números separados por espacios por línea como desee lanzarle. Sin embargo , debido a su pila, el último número en una línea termina siendo el primero en el que opera, por lo que, tal como está escrito, dcimprimiría su salida en reversa si imprimiera / escribiera más de un número por línea. manejar eso es almacenar una línea en una matriz y luego trabajarla.

Me gusta esto:

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"

Pero ... No sé si quiero explicar eso con tanta profundidad. Baste decir que, a medida que se dclee en cada valor de la pila, almacena su valor o $maxel valor de ella en una matriz indexada y, una vez que detecta que la pila está nuevamente vacía, imprime cada objeto indexado antes de intentar leer otro línea de entrada.

Y así, mientras que el primer guión sí ...

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

El segundo hace:

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

Puede manejar flotantes de precisión arbitraria si primero lo configura con el kcomando. Y puede alterar los iradios nput o output de forma independiente, lo que a veces puede ser útil por razones que podría no esperar. Por ejemplo:

echo 100000o 10p|dc
 00010

... que primero establece dcla raíz de salida de 100000 y luego imprime 10.

mikeserv
fuente
3
+1 por no tener idea de lo que acaba de pasar después de leerlo dos veces. Tendré que tomarme mi tiempo para profundizar en esto.
Minix
@Minix - meh - no es necesario profundizar en el lenguaje de programación Unix más antiguo si lo encuentras confuso. Sin dcembargo, tal vez solo coloque algunos números de vez en cuando para mantenerlo alerta.
mikeserv
1
@mikeserv Es muy tarde para mí. Espero que la generación futura tome mi historia de advertencia. Corchetes y letras en todas partes ...
Minix
@ Minix: ¿qué quieres decir? ¿Fuiste por ello? Muy bien: dces una bestia voluble, pero podría ser la utilidad común más rápida y extrañamente capaz en cualquier sistema Unix. Cuando se combina con sedél, puede hacer algunas cosas extraordinarias. He estado jugando con él y ddúltimamente para poder reemplazar la monstruosidad que es readline. Aquí hay una pequeña muestra de algunas cosas que he estado haciendo. Haciendo una reven dccasi un juego de niños.
mikeserv
1
@Minix - cuidado con los corchetes, sin embargo. No hay forma de poner un corchete dentro de una cadena; lo mejor que puedes hacer es [string]P91P93P[string]P. Entonces, tengo un poco de esto sedque puede ser útil: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'que siempre debe reemplazar los cuadrados correctamente con un corchete de cierre de cadena, luego a P, luego el valor decimal ascii del cuadrado y otro P; luego un [corchete abierto para continuar la cuerda. No sé si ha jugado con dclas capacidades de conversión de cadena / numérico de w / , pero, especialmente cuando se combina con w / od, puede ser bastante divertido.
mikeserv
88

Si sabe que está tratando con dos enteros ay b, entonces estas expansiones aritméticas de shell simples que usan el operador ternario son suficientes para dar el máximo numérico:

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

y min numérico:

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

P.ej

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

Aquí hay un script de shell que demuestra esto:

#!/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 ))
Trauma digital
fuente
Buena respuesta. Por favor, una modificación menor: ¿podría usarse esto también para "> ="?
Sopalajo de Arrierez
@SopalajodeArrierez No estoy completamente seguro de lo que quieres decir. También puede hacerlo max=$(( a >= b ? a : b )), pero el resultado es completamente el mismo: si a y b son iguales, entonces realmente no importa cuál se devuelva. ¿Es eso lo que estás preguntando?
Trauma digital
De hecho, gracias, DIgital Trauma. Me preguntaba si el operador booleano "> =" era posible aquí.
Sopalajo de Arrierez
@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi: ¿es eso lo que estás pidiendo? (tenga en cuenta el uso de (( ))aquí en lugar de $(( )))
Digital Trauma
Ah, si, ok. Entiendo ahora. No sé mucho sobre la expansión de shell, por lo que generalmente me confundo entre las condiciones. Gracias de nuevo.
Sopalajo de Arrierez
24

sorty headpuede hacer esto:

numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1       # => 21
Glenn Jackman
fuente
2
Tenga en cuenta que esto es O(n log(n))mientras que una implementación eficiente de max sería O(n). Sin n=2embargo, es nuestra pequeña importancia , ya que la generación de dos procesos es mucho más grande.
Mentira Ryan
1
Si bien es cierto, @ Glenn-Jackman, no estoy seguro de que importe dada la pregunta. No se solicitó la forma más eficiente de hacerlo. Creo que la pregunta era más sobre conveniencia.
David Hoelzer
1
@DavidHoelzer: esta no es la forma más eficiente de hacerlo, incluso entre las respuestas que se ofrecen aquí. Si trabaja con conjuntos de números, hay al menos otra respuesta aquí que es más eficiente que esta (por órdenes de magnitud) , y si solo trabaja con dos enteros, hay otra respuesta aquí más eficiente que eso (por órdenes de magnitud) . Sin embargo, es conveniente (pero probablemente dejaría de lado la matriz de shell, personalmente) .
mikeserv
1
Esto se puede hacer sin matrices de la siguiente manera:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen
1
Un enfoque más eficiente es probablemente este:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
ngreen
6

Puede definir una biblioteca de funciones matemáticas predefinidas para bcluego usarlas en la línea de comandos.

Por ejemplo, incluya lo siguiente en un archivo de texto como ~/MyExtensions.bc:

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

Ahora puedes llamar bcpor:

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

Para su información, hay funciones gratuitas de biblioteca matemática como esta disponible en línea.

Usando ese archivo, puede calcular fácilmente funciones más complicadas como GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Ari
fuente
Si no me equivoco, tales funciones también se pueden compilar con el ejecutable si es necesario. Sin embargo, creo que la mayoría de los bcmensajes de texto son solo dcfront-end hasta el día de hoy, incluso si GNUbc ya no es así (pero GNU dcy GNU bccomparten una cantidad prodigiosa de su base de código) . De todos modos, esta podría ser la mejor respuesta aquí.
mikeserv
Para llamar convenientemente a esto dentro del archivo de un script de shell, también puede canalizar la definición de la función bc, justo antes de la llamada a la función. No se necesita entonces segundo archivo :)
Tanius
5

Demasiado tiempo para un comentario:

Si bien puede hacer estas cosas, por ejemplo, con los combos sort | heado sort | tail, parece bastante subóptimo en cuanto a recursos y manejo de errores. En lo que respecta a la ejecución, el combo significa generar 2 procesos solo para verificar dos líneas. Eso parece ser un poco exagerado.

El problema más grave es que, en la mayoría de los casos, debe saber que la entrada es correcta, es decir, contiene solo números. La solución de @ glennjackmann resuelve inteligentemente esto, ya que printf %ddebería vomitar en los no enteros. Tampoco funcionará con flotantes (a menos que cambie el especificador de formato a %f, donde se encontrará con problemas de redondeo).

test $1 -gt $2 le dará una indicación de si la comparación falló o no (el estado de salida de 2 significa que hubo un error durante la prueba. Dado que esto generalmente es un shell incorporado, no se genera ningún proceso adicional; estamos hablando de un orden de cientos veces una ejecución más rápida. Sin embargo, solo funciona con enteros.

Si tiene que comparar un par de números de coma flotante, una opción interesante podría ser bc:

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

sería el equivalente de test $1 -gt $2y en in in shell:

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

sigue siendo casi 2,5 veces más rápido que printf | sort | head(para dos números).

Si puede confiar en las extensiones de GNU bc, entonces también puede usar la read()función para leer los números directamente en el bcsript.

Peterph
fuente
Mis pensamientos exactamente: solo estaba resolviendo esto, pero me dijiste: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- oh, excepto que eso dchace todo (excepto el eco, aunque podría) - lee stdin e imprime cualquiera $maxo el número de entrada dependiendo de qué es más pequeño. De todos modos, no me importa explicarlo y tu respuesta es mejor de lo que iba a escribir. Así que ten mi voto a favor, por favor.
mikeserv
@mikeserv en realidad tener un dcscript explicado sería realmente bueno, RPN no se ve tan a menudo en estos días.
Peter
Notación polaca inversa (también conocida como notación Postfix). Además, si dcpuede hacer la E / S por sí mismo, sería aún más elegante que.
Peter
4

Para obtener el mayor valor de $ a y $ b, use esto:

[ "$a" -gt "$b" ] && $a || $b

Pero necesitas algo alrededor de eso, probablemente no quieras ejecutar el número, así que para mostrar el mayor valor de los dos usa "echo"

[ "$a" -gt "$b" ] && echo $a || echo $b

Lo anterior encaja perfectamente en una función de shell, por ejemplo

max() {
   [ "$1" -gt "$2" ] && echo $1 || echo $2
}

Para asignar la mayor de las dos a variable, use esta versión modificada:

[ "$a" -gt "$b" ] && biggest=$a || biggest=$b

o use la función definida:

biggest=$( max $a $b )

La variación de la función también le brinda la oportunidad de agregar la comprobación de errores de entrada de forma ordenada.

Para devolver el máximo de dos números decimales / coma flotante, puede usar awk

decimalmax() { 
   echo $1 $2 | awk '{if ($1 > $2) {print $1} else {print $2}}'; 
}

EDITAR: Con esta técnica, puede crear una función de "límite" que opera al revés según su edición / nota. Esta función devolverá la menor de las dos, por ejemplo:

limit() {
   [ "$1" -gt "$2" ] && echo $2 || echo $1
}

Me gusta poner las funciones de utilidad en un archivo separado, llamarlo myprogram.funcsy usarlo en un script de la siguiente manera:

#!/bin/bash

# Initialization. Read in the utility functions
. ./myprogram.funcs

# Do stuff here
#
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }

read number
echo $( limit $1 $number )

FWIW sigue haciendo lo que hiciste, y tu versión, aunque es más detallada, es igual de eficiente.

La forma más compacta no es realmente mejor, pero evita el desorden en sus scripts. Si tiene muchas construcciones simples de if-then-else-fi, el script se expande rápidamente.

Si desea reutilizar el cheque para números más grandes / más pequeños varias veces en un solo script, póngalo en una función. El formato de función facilita la depuración y la reutilización y le permite reemplazar fácilmente esa parte del script, por ejemplo, con un comando awk para poder manejar números decimales no enteros.

Si es un caso de uso único, solo codifíquelo en línea.

Johan
fuente
4

Puede definir una función como

maxnum(){
    if [ $2 -gt $1 ]
    then
        echo $2
    else
        echo $1
    fi
}

Llámalo como maxnum 54 42y hace eco 54. Puede agregar información de validación dentro de la función (como dos argumentos o números como argumentos) si lo desea.

unxnut
fuente
La mayoría de las conchas no hacen aritmética de coma flotante. Pero funciona para enteros.
orion
1
Esta función es innecesariamente incompatible con POSIX. Cambie function maxnum {a maxnum() {y funcionará para muchos más proyectiles.
Charles Duffy
2

Desde un script de shell, hay una manera de usar cualquier método estático público de Java (y, por ejemplo, Math.min () ). De bash en Linux:

. jsbInit
jsbStart 
A=2 
B=3 
C=$(jsb Math.min "$A" "$B")
echo "$C"

Esto requiere Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Muy rápido, porque las llamadas al método se canalizan internamente ; No se requiere proceso.

Fil
fuente
0

La mayoría de la gente simplemente haría sort -n input | head -n1(o cola), es lo suficientemente bueno para la mayoría de las situaciones de secuencias de comandos. Sin embargo, esto es un poco torpe si tiene números en una línea en lugar de una columna; debe imprimirlo en un formato adecuado ( tr ' ' '\n'o algo similar).

Los shells no son exactamente ideales para el procesamiento numérico, pero puede simplemente conectarse a otro programa que sea mejor. Dependiendo de su propia preferencia, puede llamar al máximo dc(un poco ofuscado, pero si sabe lo que está haciendo, está bien, vea la respuesta de mikeserv), o awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. O posiblemente perlo pythonsi lo prefieres. Una solución (si está dispuesto a instalar y usar un software menos conocido) sería ised(especialmente si sus datos están en una sola línea: solo tiene que hacerlo ised --l input.dat 'max$1').


Debido a que está pidiendo dos números, todo esto es exagerado. Esto debería ser suficiente:

python -c "print(max($j,$k))"
Orión
fuente
1
Podría ser mejor si sys.argvpython2 -c 'import sys; print (max(sys.argv))' "$@"
usaras
1
Los argumentos que sort + headson excesivos pero pythonno lo son no computan.
mikeserv
Todos los métodos sobre la línea están diseñados para manejar grandes conjuntos de números y sugerir explícitamente este tipo de uso (lectura desde una tubería o un archivo). min / max para 2 argumentos es una pregunta que se siente de manera diferente: requiere una función en lugar de una secuencia. Solo quise decir que el enfoque de transmisión es excesivo: la herramienta que usa es arbitraria, simplemente la utilicé pythonporque es ordenada.
orion
Llamaría a esta solución sugerida más ordenada , pero eso podría deberse a que soy pythonintolerante (o porque no requiere un tenedor y un intérprete gigantesco adicional) . O tal vez ambos.
mikeserv
@mikeserv También lo usaría si supiera que son enteros. Todas estas soluciones que he mencionado están bajo suposición de que los números pueden ser flotadores - fiesta no hace de punto flotante y menos zsh es su shell nativo, se necesita un tenedor (y posiblemente un cuchillo).
orion